首页 > 作差,为什么会有这么多小数

作差,为什么会有这么多小数

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.

水平有限, 请各位大神斧正

【热门文章】
【热门文章】