由于二进制精度影响,JS在计算浮点数时容易造成偏差,而这个问题在很多程序语言中都存在,所以部分程序语言专门提供了高精度函数,为的就是解决浮点数据计算。
JS 中貌似还没有出现这类函数库,因此在浮点数计算中,很容易出现精度问题,当然JS是弱类型语言,很多时候程序自己转类型因此不注意容易忽视,比如:
1
|
alert(0.57*100)
|
这种精度问题只会在浮点数中出现,也就是说只要把浮点数提取为成整数计算就可以避免这个问题:
-
当拆分为整数时,注意借位进位,正负数处理,相对于说会减小计算的数量大小,能更好的精确,但处理过程复杂。
-
转成对等整数计算,不处理进位,正负数,只需要处理好小数点位数,计算数量偏小,处理方便,大数据量不宜使用。
下面为第二种方法解决浮点数计算的代码:(程序未进行严格测试,如有问题欢迎留言!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
(
function
(win) {
var
parameToString =
function
(parame) {
//转字符串
return
typeof
parame ===
'string'
? parame : parame.toString();
}, parameSplit =
function
(parame) {
//拆分浮点数
var
split = parameToString(parame).split(
'.'
, 2), integer, decimal =
''
;
integer = parameToNumber(split[0]);
if
(split.length === 2) {
decimal = split[1];
}
return
[integer, decimal];
}, parameToNumber =
function
(parame) {
//转数值
parame = parseInt(parame);
return
typeof
parame ===
'number'
? parame : 0;
}, parameCompleteAfterNumber =
function
(parame, length) {
//数字后补齐
if
(length > 0) {
return
parame + Math.pow(10, length - parame.length).toString().substr(1);
}
return
''
;
}, parameCompleteBeforeNumber =
function
(parame, length) {
//数字前补齐
if
(parame.length < length) {
var
insufficient = length - parame.length;
return
parameToString(insufficient * 10).substr(1) + parame;
}
else
if
(parame.length === length) {
return
parame;
}
return
false
;
}, resultSplit =
function
(parame, decimalLength) {
//结果小数拆分
if
(decimalLength === 0) {
return
integer;
}
var
parames = parameSplit(parame), integer = parameToString(parames[0]), result;
if
(integer.length > decimalLength) {
var
diff = integer.length - decimalLength;
result = integer.substr(0, diff) +
'.'
+ integer.substr(diff);
}
else
if
(integer.length === decimalLength) {
result =
'0.'
+ integer;
}
else
{
result =
'0.'
+ parameCompleteBeforeNumber(integer, decimalLength);
}
return
Number(result + parames[1]);
}, parameCompleteParse =
function
(parame1, parame2) {
//参数补全解析处理
parame1 = parameSplit(parame1);
parame2 = parameSplit(parame2);
var
length = Math.max(parame1[1].length, parame2[1].length);
return
[parameToNumber(parame1[0] + parameCompleteAfterNumber(parame1[1], length)), parameToNumber(parame2[0] + parameCompleteAfterNumber(parame2[1], length)), length];
}, math = {
//加法计算
bcadd:
function
(parame1, parame2) {
var
parames = parameCompleteParse(parame1, parame2), result = parames[0] + parames[1];
return
resultSplit(result, parames[2]);
},
//比较两个数字
bccomp:
function
(parame1, parame2) {
var
parames = parameCompleteParse(parame1, parame2);
if
(parames[0] > parames[1]) {
return
1;
}
else
if
(parames[0] === parames[1]) {
return
0;
}
else
{
return
-1;
}
},
//除法计算
bcdiv:
function
(parame1, parame2) {
var
parames = parameCompleteParse(parame1, parame2);
return
parames[0] / parames[1];
},
//取模
bcmod:
function
(parame, modulus) {
var
parames = parameCompleteParse(parame, modulus), result = parames[0] % parames[1];
if
(parames[2] > 0) {
return
resultSplit(result, parames[2]);
}
return
result;
},
//乘法计算
bcmul:
function
(parame1, parame2) {
var
parames = parameCompleteParse(parame1, parame2), result = parames[0] * parames[1];
if
(parames[2] > 0) {
return
resultSplit(result, parames[2] * 2);
}
return
result;
},
//二次方根
bcsqrt:
function
(parame) {
var
split = parameSplit(parame), length = split[1].length, result;
if
(length > 0) {
length += length % 2;
parame = Number(split[0] + parameCompleteAfterNumber(split[1], length));
}
result = Math.sqrt(parame);
if
(length > 0) {
//小数点向前移动
return
resultSplit(result, length / 2);
}
return
result;
},
//减法
bcsub:
function
(parame1, parame2) {
var
parames = parameCompleteParse(parame1, parame2), result = parames[0] - parames[1];
return
resultSplit(result, parames[2]);
}
};
for
(
var
key
in
math) {
win[key] = math[key];
}
})(window);
//使用
alert(bcmul(0.57, 101.1));
alert(bcmod(2.1, 0.5));
alert(bcsqrt(0.024));
|
是什么原因造成的?最主要原因是计算机只识别二进制数,所有数据都会转成二进制数运算得来,并不是程序不会计算小数形式的二进制数,而是在程序中进制数转换造成的。
十进制转二进制:
整数部分(除2取余,逆序排列):
即不断整除2,取余数(0或1)为二进制数,商再除2如此循环(直到商为0),把得出来的余数(二进制数)以先后计算倒序取出余数,即为对应二进制数。如:
10 转二进制结果是:
10/2 = 5 余 0
5/2 = 2 余 1
2/2 =1 余 0
1/2 =0 余 1
以先后计算倒序取出余数则结果为: 1010
小数部分(乘2取整,顺序排列):
即不断乘2取整数部分(0或1),取结果中小数部分再乘2如此循环(直到结果无小数部分,当然大部分无法计算到无小数部分,所以就会出现截断,最终影响计算精度),把得出来的整数(二进制数)以先后计算顺序取出,即为对应小数二进制数。如:
0.35 转二进制结果是:
0.35*2 = 0.7 整0小0.7
0.7*2 = 1.4 整1小0.4
0.4*2 = 0.8 整0小0.8 ---无限循环开始
0.8*2 = 1.6 整1小0.6
0.6*2 = 1.2 整1小0.2
0.2*2 = 0.4 整0小0.4 --- 循环结束
0.4*2 = 0.8 整0小0.8
0.8*2 = 1.6 整1小0.6
0.6*2 = 1.2 整1小0.2
0.2*2 = 0.4 整0小0.4
0.4*2 = 0.8 整0小0.8
.
.
.
以先后计算顺序取出整数则结果为:0.01011001100..... 截取长度取决于程序支持的长度。
二进制转十进制:
按权相加2n,个位为n=0,小数部分n-1,整数(个位以上)部分n+1,乘以对应位二进制数。如:
1101.101 转十进制结果是:
23*1 + 22*1 +21*0 + 20*1 + 2-1*1 + 2-2*0 + 2-3*1 = 8 + 4 + 0 + 1 + 0.5 + 0 + 0.125 = 13.625
注:浮点数从转换中可以看出来二进制数转十进制数不存在精确度丢失,而十进制数转二进制数很容易造成精确度丢失,从而影响计算结果精度。
本文转自 ttlxihuan 51CTO博客,原文链接:http://blog.51cto.com/php2012web/1735982