美文网首页计算机基础
浮点数加法运算的困惑

浮点数加法运算的困惑

作者: 江枫 | 来源:发表于2014-09-19 21:24 被阅读2171次

    缘起:

    公司一小姑娘群发邮件求助,邮件内容是关于浮点数之间运算结果莫名其妙的多了一些小数位,小姑娘想不通所以想问问有人知道原因不?涉及的代码是下面的样子

    function addTwoFloatString(stra , strb){
        return parseFloat(stra) + parseFloat(strb);
    }
    
    addTwoFloatString('2222222','269567.53');
    ->2491789.5300000003
    

    大家看到这个问题后献计献策,有说不应该那样,应该这样

    Number( 123.5678.toFixed(2) )
    还有说应该用BigDecimal做浮点数相加运算的。

    看到这个求助后,直觉告诉我这个和浮点数的表示法肯定有关系,至于为什么,详细的答案是真说不好。如果非要搪塞一下,我会说浮点数之间的运算会有精度问题,如果深究为什么,那就糗了。

    验证

    首先看看是否其他语言中也存在类似的问题,ok,java中也存在同样的问题,那么基本可以断定这个现象和编程语言关系不大。

    接着需要确认的问题是,JavaScript中浮点数是如何定义的:

    JavaScript中的浮点数采用IEEE-754格式的规定。更具体的说是一个双精度格式,这意味着每个浮点数占64位。虽然它不是二进制表示浮点数的唯一途径,但它是目前最广泛使用的格式,另外,JavaScript中的数值不区分整数值和浮点数值,所有数字都采用的是64位浮点数表示法。

    似乎看到了希望,IEEE754

    wiki中对IEEE754中64位浮点数有下面的描述,总长度占用64位,其中1为符号位,标明浮点数是正数还是负数,11位为指数为,52位为尾数位。

    知道这些现在还不能准确的表述一个数,得有一个约定,约定尾数的形式,不然同一个数字就有多种表示,这个过程称为规格化,形象化的描述就是一个数需要表示成这样的格式: 1.M* 2E,这样的数叫做规格化数。

    另外,IEEE还规定了指数的存储形式,64位浮点数指数部分以偏正值形式表示,偏正值为实际的指数大小与1023的和。

    到此,就可以将文章开头的两个数用IEEE754的定义表示出来。

    对2222222做规格化, 符号位为0,表示是一个正数,2222222对应的二进制是1000011110100010001110,有22位,满足规格化需小数点左移21为,指数就为21 +1023 = 1044,尾数为1.000011110100010001110

    最终2222222 的64位二进制浮点数就表示为

    0 10000010100 0000111101000100011100000000000000000000000000000000
    

    269567.53的64位浮点数就表示为

    0 10000010001 0000011100111111111000011110101110000101000111101100
    

    Java中有对应的函数可以将根据IEEE 754规定,返回指定浮点值的表示形式

    Long.toBinaryString(Double.doubleToRawLongBits(2222222))
    

    接下来就是浮点数加减法运算步骤了,如下

    1. 对阶,使两个数的小数点位置对齐

    2. 尾数求和,将运算后的两个尾数求和

    3. 将求和后的尾数规格化

    4. 舍入,考虑尾数右移时的丢失的数值位

    5. 判断结果是否溢出

    • 对阶的原则是小阶向大阶看齐,阶码较小的数尾数向右移,每移一位,阶码加一,在269567.53 + 2222222中,前者向后者看齐,前者尾数右移三位。对阶后两数分别为

       010000010100 0010000011100111111111000011110101110000101000111101100
      
       010000010100 0000111101000100011100000000000000000000000000000000
      
    • 尾数相加

       0100000101000010000011100111111111000011110101110000101000111101100
      
       0100000101000000111101000100011100000000000000000000000000000000
      
       0100000101000011000000101100011011000011110101110000101000111111100
      
    • 规格化

    满足规格化

    • 舍入

        0100000101000011000000101100011011000011110101110000101000111111100
      

    浮点数舍入的方式有多种,但是不可避免的,都可能会发生数位的丢失,这个就是直接导致浮点数之间运算后出现精度问题的直接原因。

    java和javascript中最终舍入后的表示形式为

    0100000101000011000000101100011011000011110101110000101000111110
    

    结论

    由于二进制表示本身的缺陷,我们不得不面对一个充满舍入误差的规范,进而导致计算结果超乎预期。

    相关文章

      网友评论

        本文标题:浮点数加法运算的困惑

        本文链接:https://www.haomeiwen.com/subject/zwkutttx.html