代码之家  ›  专栏  ›  技术社区  ›  T.Todua Laurent W.

在PHP中对浮点数进行算术运算

  •  -12
  • T.Todua Laurent W.  · 技术社区  · 6 年前

    我想要最简单的计算:

    $example = 90000000000009132 - 1;
    echo sprintf('%.0f',  $example);
    

    令人惊讶的是,我得到了:

               90000000000009136
    

    我的问题是 - 如何用PHP获得正确答案 做这种手术? 你能给我一个正确的密码吗 ?

    另外,在搜索了网络之后,我找到了一些解释,说“这不是一个bug”和一些关于 float 等等…好吧,我不会开始证明为什么我认为这真的 一个明显的错误 我们只是不在乎 在PC的背景下会发生什么,但显而易见的是,我们在减法时得不到正确的数字。 X Y 所以这绝对是错误的!(但请不要和我争论这个问题,因为我的问题是不同的——上面写得很清楚)。

    P.S.成为这个问题之后 不投票异常 ,在引用的链接中找不到真正的解决方案。他们都没用,至少在我的主机上是这样。所以,所有这些“答案”都声称是解决方案,在某些水管上似乎毫无用处。

    1 回复  |  直到 6 年前
        1
  •  3
  •   T.Todua Laurent W.    6 年前

    当然,PHP浮点精度的问题经常是一个话题,但我仍然不明白为什么这个问题会被否决18票,因为这个问题被非常精确地问到具体浮点数的具体问题。我以一种人人都能清楚理解问题所在的方式解决了这个问题(不仅仅是那些不精确的推理)。

    我们来看看下面的结果:

    $example = 90000000000009132 - 1;
    
    var_dump(PHP_INT_MAX);
    var_dump($example); 
    var_dump(PHP_FLOAT_MAX);
    var_dump(sprintf('%.0g',  $example)); 
    var_dump(sprintf('%.0f',  $example));
    

    结果:

    int(9223372036854775807) 
    int(90000000000009131) 
    float(1.7976931348623E+308) 
    string(7) "9.0e+16" 
    string(17) "90000000000009136"
    

    这意味着:

    • 您不超过最大整数大小 算术运算失败
    • 用整数计算的结果是正确的
    • 您没有超过php_float_max(需要php 7.2),但其大小取决于您的环境。
    • 计算的浮点表示法意外错误(因为printf语句转换为float,然后转换为string)

    为什么会有这样奇怪的结果呢?让我们做一些测试:

    var_dump(sprintf('%.0f',  90000000000009051));
    var_dump(sprintf('%.0f',  90000000000009101));
    var_dump(sprintf('%.0f',  90000000000009105));
    var_dump(sprintf('%.0f',  90000000000009114));
    var_dump(sprintf('%.0f',  90000000000009121));
    var_dump(sprintf('%.0f',  90000000000009124));
    var_dump(sprintf('%.0f',  90000000000009128));
    var_dump(sprintf('%.0f',  90000000000009130));
    var_dump(sprintf('%.0f',  90000000000009131));
    var_dump(sprintf('%.0f',  90000000000009138));
    var_dump(sprintf('%.0f',  90000000000009142));
    var_dump(sprintf('%.0f',  90000000000009177));
    

    结果是:

    string(17) "90000000000009056"
    string(17) "90000000000009104"
    string(17) "90000000000009104"
    string(17) "90000000000009120"
    string(17) "90000000000009120"
    string(17) "90000000000009120"
    string(17) "90000000000009120"
    string(17) "90000000000009136"
    string(17) "90000000000009136"
    string(17) "90000000000009136"
    string(17) "90000000000009136"
    string(17) "90000000000009184"
    

    看看那些数字。结尾的数字总是可以16分。信息技术中的16是什么意思?一个字节。十六进制。它告诉我们什么? 您可能已经超过了浮动基本上是精确的限制(在算术运算中不一定如此)。

    如果我们把这些数字减少一个零到16位呢?

    var_dump(sprintf('%.0f',  9000000000009051));
    var_dump(sprintf('%.0f',  9000000000009101));
    var_dump(sprintf('%.0f',  9000000000009124));
    var_dump(sprintf('%.0f',  9000000000009138));
    var_dump(sprintf('%.0f',  9000000000009142));
    var_dump(sprintf('%.0f',  9000000000009177));
    

    string(16) "9000000000009051"
    string(16) "9000000000009101"
    string(16) "9000000000009124"
    string(16) "9000000000009138"
    string(16) "9000000000009142"
    string(16) "9000000000009177"
    

    输出总是正确的。好。如果我们把它提高一个零位数呢?

    var_dump(sprintf('%.0f',  900000000000009051));
    var_dump(sprintf('%.0f',  900000000000009101));
    var_dump(sprintf('%.0f',  900000000000009124));
    var_dump(sprintf('%.0f',  900000000000009138));
    var_dump(sprintf('%.0f',  900000000000009142));
    var_dump(sprintf('%.0f',  900000000000009177));
    

    string(18) "900000000000009088"
    string(18) "900000000000009088"
    string(18) "900000000000009088"
    string(18) "900000000000009088"
    string(18) "900000000000009088"
    string(18) "900000000000009216"
    

    有趣的是,这又是16个因素——更不精确。

    现在让我们来解开这个谜:

    var_dump(strlen(sprintf('%.0f',  9000000000009051)));
    

    猜猜结果是什么:

    利息(16)

    真的。这意味着在PHP中浮点数基本上可以精确地降到16位。( 如果你超过这个神奇的数字,它就会变得不精确。 )

    那么,你的计算结果是怎样的呢?

    它在结尾处被四舍五入到下一个16位,因为您超过了16位数字1。

    关于php中浮点精度的更多信息,可以在stackoverflow的许多问题中找到,当然也可以在php手册中找到。

    要回答如何使代码正确工作:

    如果你希望你的数字是精确的,不要转换成浮点数。例如,对于整数,应该可以进行更高的计算。

    如果超过16位数,那么简单的算术(如-1或+1)也会失败(至少在当前版本的PHP中是这样)。