var a ='19'
undefined
var b = '18.51'
undefined
parseFloat(a) -parseFloat(b)
0.48999999999999844
a -b
0.48999999999999844
19 - 18.51
0.48999999999999844
a
"19"
b
"18.51"
Number(19)-Number(18.51)
0.48999999999999844
为什么不是0.49呢
19-18.51
0.48999999999999844
19-0.51
18.49
19-1.51
17.49
19-5.51
13.49
19-17.51
1.4899999999999984
19-13.51
5.49
有的又是可以计算出来的
把浮点数转化成整数再运算
你可以先了解下浮点型的存储原理
http://.com/q/1010000002545341/a-1020000002545639
http://www.laruence.com/2013/03/26/2884.html
另外,javascript所有数字类型直接量都是以浮点型保存的, 所以你不需要parseFloat,直接做运算结果也是一样的。
这个浮点数存储有关,涉及到浮点数计算,保留多少为小数都是自己定的。
这的确就是著名的浮点陷阱, 是IEEE754浮点标准的一个缺陷, 根本原因是十进制有限小数大都不能化为二进制有限小数.
直观的解释
二进制有限小数, 必须由2^n的和组成, 如0.625 = 0.5 + 0.125 = 2^(-1) + 2^(-3), 这样0.625可以无误差地记录在计算机里. 但是这样的数字不多, 甚至连0.1都无法表示成二进制. 在表示, 比如0.9时, 会产生一个误差, 我们对比一下(为了reinterpret_cast和bitset, 用c++说明):
cpp
#include <bitset> #include <cstdio> using namespace std; #define toBin(x) (bitset<64>(reinterpret_cast<unsigned long long &>(x)) \ .to_string().c_str()) #define printFloat(x) printf("%.24lf =\n%s\n", x, toBin(x)) int main() { double a = 0.2, b = 0.7, c = a + b, d = 0.9, e = 0.625; printFloat(a); // 0.200000000000000011102230 = // 0011111111001001100110011001100110011001100110011001100110011010 printFloat(b); // 0.699999999999999955591079 = // 0011111111100110011001100110011001100110011001100110011001100110 printFloat(c); // 0.899999999999999911182158 = // 0011111111101100110011001100110011001100110011001100110011001100 printFloat(d); // 0.900000000000000022204460 = // 0011111111101100110011001100110011001100110011001100110011001101 printFloat(e); // 0.625000000000000000000000 = // 0011111111100100000000000000000000000000000000000000000000000000 return 0; }
可以看到Double(64-bit)可以表示的精度就到十进制小数点后16位就到尽头了, 剩下后面的都是垃圾数字. 这些垃圾数字, 有时会产生一些相当恶心的误差, 比如0.7 + 0.2 = 0.899.... 比如8.999...1和9.000...2, 看到其实就是最后一位的差别, 9.0其实就在这两个数字之间. 在矩阵的乘方计算时, 如果不用相似对角阵, 有时乘方下来的误差会放得相当大. 浮点问题, 一直是学术界头疼得问题之一.
IEEE 754的描述, 可以再网上找到很多, 官方见http://grouper.ieee.org/groups/754/. 这里不展开讨论.
解决方案
数学上等价, 但能产生更小的浮点误差的方案举例
P.J. Schneider, D.H. Eberly的书GTfCG里提到, 经典的一元二次方程理论根的公式, 并不能直接用在程序中, 如ax^2+bx+c, 它的其中一个理论根为:
$$ x_1 = {{-b + \sqrt{b^2 - 4ac}}\over{2a}} $$
但是当b^2比4ac大得多得时候, sqrt(b^2 - 4ac) 近似于 b, 结果是分子很小, 相对误差就比较大了. 如果这时分母2a也很小, 那么就会放大这个误差. 在b^2 >> 4ac得情况下, 先把x1化为下面的形式, 再进行计算:
$$ x_1 = {{-2c}\over{b + \sqrt{b^2 - 4ac}}} $$
然而这式子在x2上则又产生了较大的误差. 最终的方法是, 比较b和sqrt(b^2 - 4ac)的大小, 再应用具体的公式. 这里不详述.
BigDecimal
BigDecimal是Java提供的大·小数(对就是大小数)算术方案, 其内建的是10进制的浮点方案, 这样就可以适应人类的需求了. 当然, 这其中的运算速度, 恐怕要比ALU直接进行浮点运算要慢好几倍.
对于其它语言, 如C/C++, 有优秀的GMP算术库https://gmplib.org/可以使用, PHP有内建的BC Math, Python有decimal.
水平有限, 请各位大神斧正