PHP-浮点数精度

PHP-浮点数精度,php,floating-point,precision,floating-accuracy,Php,Floating Point,Precision,Floating Accuracy,结果为0.0099999998 怎么回事?我想知道为什么我的程序总是报告奇怪的结果 为什么PHP不返回预期的0.01?使用PHP的round()函数: 这个答案解决了问题,但不能解释原因。我认为这是显而易见的(我也是C++编程的,所以对我来说是显而易见的),但是如果不是,假设PHP有自己的计算精度,在那个特定的情况下它返回了关于该计算的最符合的信息。< /P> < P>因为0.01不能精确地表示为二进制序列的和的总和。这就是浮动存储在内存中的方式 我想这不是你们想听的,但这是对问题的回答。有关如

结果为0.0099999998

怎么回事?我想知道为什么我的程序总是报告奇怪的结果

为什么PHP不返回预期的0.01?

使用PHP的
round()
函数:


这个答案解决了问题,但不能解释原因。我认为这是显而易见的(我也是C++编程的,所以对我来说是显而易见的),但是如果不是,假设PHP有自己的计算精度,在那个特定的情况下它返回了关于该计算的最符合的信息。< /P> < P>因为0.01不能精确地表示为二进制序列的和的总和。这就是浮动存储在内存中的方式


我想这不是你们想听的,但这是对问题的回答。有关如何修复,请参阅其他答案。

因为浮点运算!=实数算术。对于某些浮动
a
b
(a+b)-b!=a
。这适用于任何使用浮动的语言

因为是有限精度的二进制数,所以有一个有限的数量,这导致了这样的惊喜。下面是另一个有趣的阅读:


回到你的问题上来,基本上没有办法精确地用二进制表示34.99或0.01(就像十进制一样,1/3=0.3333…),所以使用近似值代替。要解决此问题,您可以:

  • 对结果使用四舍五入至小数点后2位

  • 使用整数。如果这是货币,比如说美元,那么将$35.00存储为3500,将$34.99存储为3499,然后将结果除以100


  • 遗憾的是,PHP没有像do那样的十进制数据类型。

    浮点数字和所有数字一样,必须作为0和1的字符串存储在内存中。这都是计算机的一部分。浮点与整数的不同之处在于,当我们想要查看0和1时,我们如何解释它们

    一位是“符号”(0=正,1=负),8位是指数(范围从-128到+127),23位是称为“尾数”(分数)的数字。所以(S1)(P8)(M23)的二进制表示的值是(-1^S)M*2^P

    “尾数”有一种特殊的形式。在正常的科学记数法中,我们显示“某人的位置”和分数。例如:

    4.39 x 10^2=439

    在二进制中,“某人的位置”是一个位。因为我们忽略了科学记数法中所有最左边的0(我们忽略了任何不重要的数字),所以第一位保证是1

    1.101 x 2^3=1101=13

    因为我们保证第一位是1,所以我们在存储数字时会删除该位以节省空间。所以上面的数字只存储为101(对于尾数)。假定为前导1

    以二进制字符串为例

    $a = '35';
    $b = '-34.99';
    echo ($a + $b);
    
    将其分解为以下组件:

    00000010010110000000000000000000
    
    应用我们的简单公式:

    Sign    Power           Mantissa
     0     00000100   10110000000000000000000
     +        +4             1.1011
     +        +4       1 + .5 + .125 + .0625
     +        +4             1.6875
    
    换句话说,000000100101100000000000000000是27的浮点值(根据IEEE-754标准)

    然而,对于许多数字,没有精确的二进制表示。很像1/3=0.333。。。。永远重复,1/100是0.0000001010001110101110000。。。。。重复“1010001110101110000”。然而,32位计算机不能以浮点形式存储整个数字。所以它做出了最好的猜测

    (-1^S)M*2^P
    (-1^0)(1.6875)*2^(+4)
    (1)(1.6875)*(16)
    27
    
    (请注意,负7是使用2的补码生成的)

    应该立即清楚,01111110010100011111101011100001010看起来与0.01完全不同

    但是,更重要的是,它包含重复小数的截断版本。原始小数点包含重复的“1010001110101110000”。我们已将其简化为01000111101011100001010

    通过我们的公式将浮点数转换回十进制,我们得到0.00999979(请注意,这是针对32位计算机的。64位计算机的精度要高得多)

    十进制的等价物 如果它有助于更好地理解这个问题,那么在处理重复小数时,让我们看看十进制科学记数法

    假设我们有10个“盒子”来存储数字。因此,如果我们想存储1/16这样的数字,我们会写:

    0.0000001010001111010111000010100011110101110000
    
    Sign    Power           Mantissa
     +        -7     1.01000111101011100001010
     0    -00000111   01000111101011100001010
     0     11111001   01000111101011100001010
    01111100101000111101011100001010
    
    这显然是
    6.25e-2
    ,其中
    e
    *10^(
    )的缩写。我们为十进制分配了4个框,尽管我们只需要2个(用零填充),我们为符号分配了2个框(一个用于数字的符号,一个用于指数的符号)

    使用这样的10个框,我们可以显示从
    -9.9999e-9
    +9.9999e+9

    这适用于任何小数点后4位或更少的数字,但是当我们尝试存储像
    2/3
    这样的数字时会发生什么

    +---+---+---+---+---+---+---+---+---+---+
    | + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 |
    +---+---+---+---+---+---+---+---+---+---+
    
    这个新的数字
    0.66667
    并不完全等于
    2/3
    。事实上,它以
    0.000003333…
    为准。如果我们尝试在基数3中写入
    0.66667
    ,我们将得到而不是
    0.2

    如果我们使用较大的重复小数,例如
    1/7
    ,这个问题可能会变得更明显。它有6个重复数字:
    0.142857142857…

    将其存储到十进制计算机中,我们只能显示其中的5位:

    +---+---+---+---+---+---+---+---+---+---+
    | + | 6 | . | 6 | 6 | 6 | 7 | e | - | 1 |
    +---+---+---+---+---+---+---+---+---+---+
    
    此编号为
    0.14286
    ,已被
    .000002857…

    它“接近正确”,但它不是完全正确的,因此如果我们尝试将这个数字写在基数7中,我们会得到一些可怕的数字,而不是
    0.1
    。事实上,将其插入Wolfram Alpha,我们会得到:

    这些微小的分数差异对于您的
    0.00999979
    (与
    0.01
    )来说应该很熟悉。

    在这里可能很有用

    +---+---+---+---+---+---+---+---+---+---+
    | + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 |
    +---+---+---+---+---+---+---+---+---+---+
    
    
    
    (为了清晰起见,输出效率低下)

    第一行给我0.00999999998。
    第二个给了我0.01

    这里有很多关于浮点数为什么会这样工作的答案

    但很少有人谈论武断的预演
    <?PHP
    
    $a = '35';
    $b = '-34.99';
    
    echo $a + $b;
    echo '<br />';
    echo bcadd($a,$b,2);
    
    ?>
    
    $number = '12345678901234.1234567890';
    $number2 = '1';
    echo bcadd($number, $number2);
    
    2250004.5000023
    
    2250004.50000225
    
    0.33333.
    
    0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 +  3*10^-5.
    
    $a = '35';
    $b = '-34.99';
    echo ($a + $b);
    
    0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary)
    
    So 0.01 = 2^x + 2^y... 2^-z
    0.01 * (2^(x+y+...z)) =  (2^x + 2^y... 2^z)*(2^(x+y+...z)). This expression is true when (2^(x+y+...z)) = 100*x1. There are not integer n = x+y+...+z exists. 
    
    => So 0.01 (decimal) must be infine in binary.