代码之家  ›  专栏  ›  技术社区  ›  dsp_099

为什么0.1+0.2在javascript中返回不可预测的float结果,而0.2+0.3不返回?

  •  2
  • dsp_099  · 技术社区  · 7 年前
    0.1 + 0.2
    // => 0.30000000000000004
    
    0.2 + 0.2
    // => 0.4
    
    0.3 + 0.2
    // => 0.5
    

    我知道这和浮点数有关,但这里到底发生了什么?

    根据@eric postischil的评论,这不是重复的:

    这个问题只涉及到为什么噪声会同时出现。这个 询问为什么__noise_ 另一个。另一个问题没有回答。因此,这 不是副本。事实上,造成这种差异的原因还没有到 到浮点运算本身,但由于ECMAScript 2017 7.1.12.1步骤5

    2 回复  |  直到 7 年前
        1
  •  3
  •   Eric Postpischil    7 年前

    to use just enough digits to uniquely distinguish the Number value p-4 意思是将前面的十六进制数字乘以2,乘以“4”的幂,这样数学家就可以把它写成1.9999999999。 十六 _2 _4 )

    以下是您在编写时得到的值 0.1 , 0.2 0.3 在源代码中,它们转换为javascript_的数字格式:

    • 0.1 0x1.999999999AP-4=0.10000000000000055511151231257827021181583404541015625。
    • 0.2_塟0x1.99999999999AP-3=0.200000000000000111022302462515654004236316680908203125。
    • .3_墦0x1.3333333333333P-2=0.29999999999988897769753748434595763683319091796875。

    当我们评估 0.1 + 0.2 ,我们正在添加0x1.999999999AP-4和0x1.9999999999999AP-3。要手动执行此操作,我们首先可以通过将其有效位(小数部分)乘以2并从其指数中减去1来调整后者,生成0x3.33333333334P-4。(你必须用十六进制来做这个运算。一 十六 _2=14 十六 ,所以最后一个数字是4,1被携带。然后9 十六 _2=12 十六 带着的1使它13 十六 . 它产生一个3位数和一个进位。)现在我们有了0x1.999999999AP-4和0x3.3333333333334P-4,我们可以添加它们。生产4.CCCCCCCEP-4。这是精确的数学结果,但是对于数字格式来说,它的位太多了。有效位中只能有53位。4(100)中有3位 )后13位中的每一位都有4位,所以总共有55位。计算机必须删除2位并对结果进行取整。最后一个数字,E 十六 ,是1110 ,所以10位必须消失。这些位正好是前一位的_½,因此在向上取整或向下取整之间是一个关系。打破联系的规则是四舍五入,所以最后一位是偶数,所以我们把11位四舍五入,使其变为100。e 十六 变为10 十六 使进位到下一位。结果为4.CCCCCCCCCD0P-4,相当于0.30000000000440890850062616169452667236328125。

    现在我们可以看到为什么要打印 .1 + .2 显示__0.30000000000000004_而不是__0.3_。对于数字值0.2999999999998889776975374844595763683319091796875,javascript显示_0.3_157;,因为该数字比任何其他数字更接近0.3。17岁时与0.3相差约1.1 小数点后的数字,而加法的结果在17时与0.3相差约4.4 数字。所以:

    • 源代码 零点三 生成0.2999999999998889776975374844595763683319091796875,打印为__0.3__。
    • 源代码 0.1+0.2 生成0.3000000000444089208500626616945267236328125,打印为__0.30000000000000004_。

    现在考虑 0.2 + 0.2 . 其结果为0.4000000000000002220446049250313080847263336181640625。这是最接近0.4的数字,所以javascript将其打印为__0.4_。

    最后,考虑 0.3 + 0.2 . 我们正在添加0x1.999999999AP-3和0x1.3333333333333P-2。我们再次调整第二个操作数,生成0x2.66666666666P-3。然后,加法生成0x4.000000000000P-3,即0x1P-1,即_½或0.5。所以它被打印成__0.5__。

    另一种方法是:

    • 源代码的值 零点一 零点二 这两个值分别略高于0.1和0.2,加上它们会产生一个大于0.3的数字,错误会加强,所以总错误足够大,足以将结果从0.3推到足以让javascript显示错误的位置。
    • 添加时 0.2+0.2 错误再次加强。但是,这种情况下的总错误不足以将结果推离0.4太远,以至于JavaScript显示的结果不同。
    • 源代码的值 零点三 略低于0.3。添加到时 零点二 ,略大于0.2,错误被取消,精确到0.5。

    脚注

    本规则来自ECMAScript 2017语言规范第7.1.12.1条第5步。

        2
  •  0
  •   Maciej Kwas    7 年前

    这不是JavaScript本身或任何其他语言的问题,而是两个不同种族之间的交流问题:如人类和机器以及 thinking 两者的力量。我们觉得很自然的事情(比如: tree -当我们说我们在头脑中创造了一些树的抽象表示时,对计算机来说是完全不自然的,而机器唯一能做的就是用机器容易理解的表示方式来存储一个单词“树”(任何你真正想要的方式,某人多年来一直在使用它)。o选择了一个带ASCII表的二进制代码,但现在它似乎是稳定的)。所以此后,机器就有了一个词的表示法。 存储在那里的某个地方,比如说 00000001 但是它不知道什么,对你来说,它有一些意义,对于一台机器来说,它只是一堆零和一。如果我们说每一个字最多有7位,因为否则计算机工作很慢,那么机器就会保存 0000000 切割最后一个钻头,因此它仍然能理解这个词。 在某种程度上。数字也是如此, 0.3 对你来说是自然的,但是当你看到 10101010001010101010101010111 您会立即将它转换为十进制系统来理解它代表什么,因为在二进制系统中看到数字对您来说是不自然的。重点是: 转换 .

    因此,对于你来说,数学看起来是这样的:

    .1 + .2 => .3

    对于使用二进制系统的机器,如下所示:

    数字1/10可以用十进制表示为0.1,但它是0.0001100110011001100110011001100110011001100110011001100110011001_。二进制。因为根据标准,一个数字只有53位的空间,从第54位开始,这个数字将被四舍五入。

    x = .1 converted to 00011001...01 and trimmed to 53 bits

    y = .2 converted to 00010110...10 and trimmed to 53 bits

    z = x + y => 0001100010101... trimmed to 53 bits

    result = 0.3000000000000000444089209850062616169452667236328125 converted from z = 0001100010101...

    这就像把欧元兑换成美元,有时你会得到半美分,有时你会多付半欧元作为美元代表,因为没有比美分更小的部分。可能有,但人们会因为口袋里的东西而发疯。

    所以真正的问题是 Why does 0.1 converted to binary and trimmed + 0.2 converted to binary and trimmed return unpredictable float results in JavaScript while 0.2 converted to binary and trimmed + 0.3 converted to binary and trimmed does not? 答案是:因为数学和计算所需的能量,类似于为什么 pi + 1 结果很奇怪但是 2 + 1 does not=>您可能输入了一些类似pi的表示形式 3.1415 因为你没有足够的数学能力(或者它不值得)来得出精确的结果。

    要了解更多信息,请在这里完成一个伟大的数学部分: https://medium.com/dailyjs/javascripts-number-type-8d59199db1b6