【问题标题】:PHP floating point errors with basic Maths [duplicate]基本数学的PHP浮点错误[重复]
【发布时间】:2012-07-07 04:01:10
【问题描述】:

可能重复:
Why can't decimal numbers be represented exactly in binary?
problem with floating values

$var1 = 1;

for ( $i=0; $i<30; $i++ ) {
  $var1 += 0.1;
  $var2 = floor($var1);
  $var3 = $var1-$var2;
  if ( $var3 == 0.5 ) {
    $var1 = $var2+1;
  }
}

这个循环的目的是计数1.0、1.1、1.2、1.3、1.4,然后跳转到2.0、2.1、2.2等

我遇到的问题是if 声明永远不会正确。此外,每十分之一的计算都会得出一些疯狂的科学答案。

我该如何解决这个问题?请帮忙!

编辑:我写的问题有点沮丧,而且不止一个,我现在明白了。

问题的第一部分确实是“我怎样才能绕过这个浮点怪癖来完成这项工作”和“为什么会发生这种怪癖!”

感谢您的所有精彩回复,我投票认为答案是正确的,它轻松回答了“如何使这项工作”这一核心问题。

使用 0.49 代替 0.5 和 > 代替 == 就可以了。粗糙而不是世界上最好的代码,但它确实解决了最初的问题。感谢大家的其他回复,我将阅读并跟进以改进我的编码。

再一次,非常感谢。

【问题讨论】:

标签: php math


【解决方案1】:

二进制浮点数将选择近似值来表示它无法表示的以 10 为底的浮点数。因此,将浮点数与浮点数进行比较是不准确的(因为您无法确定其行为)。

您需要发明自己的以 10 为底的浮点数。如果您知道自己想要哪种精确度,发明起来并不难。在您的情况下,您只需要一位精确的数字。我们的以 10 为底的整数的公式是:value / 10 的精确数字的幂,即 1,因此你的公式是 = value / 10

执行算术运算与执行普通算术一样容易。只需将正常表示(指计算机使用的表示)转换为我们发明的表示。在您的情况下,$var1 += 0.1 将转向$var1 += 1

输出就像将您的表示转换为正常表示一样简单。在您的情况下,echo $var1 . '&lt;br&gt;'; 将转向 echo $var1 / 10 . '&lt;br&gt;';

$var1 = 10;

for ( $i=0; $i<30; $i++ ) {
  echo $var1 / 10 . '<br>';
  $var1 += 1;
  if ($var1 % 10 == 5) {
    $var1 = $var1+5;
  }
}

【讨论】:

    【解决方案2】:

    关于为什么会发生这种情况以及一些解决方案的大量有用信息。 PHP 的基本设计目标之一是其松散类型设计和隐式转换。本着这种精神,我认为解决这个问题的最简单的方法之一就是使用字符串比较。在您的情况下,这是一个很好的解决方案:

    <?php
    $var1 = 1;
    
    for ( $i=0; $i<30; $i++ ) {
      $var1 += 0.1;
      $var2 = floor($var1);
      $var3 = $var1 - $var2;
      echo "$var1 | $var2 | $var3 \n";
      if (number_format($var3, 1) == '0.5') {
        echo "It's true\n";
        $var1 = $var2 + 1;
      }
    }
    

    【讨论】:

      【解决方案3】:

      正确的解决方案是使用bcmath 包:

      $var1 = '1.0'; bcscale(1);
      
      for ( $i = 0; $i < 30; $i++) {
          $var2 = $var1;
          for( $j = 0; $j < 5; $j++) {
              echo $var2 . "\n"; 
              $var2 = bcadd( $var2, '0.1');
          }
          $var1 = bcadd( $var1, '1');
      }
      

      这个outputs the correct result

      【讨论】:

        【解决方案4】:

        这个给出了预期的结果:

        $var1 = 1;
        for ( $i=0; $i<30; $i++ ) {
            $var1 += 0.1;
            $var2 = (int)($var1);
            $var3 = $var1-$var2;
            if ( $var3 >= 0.45 ) {
                $var1 = $var2+1;
            }
            echo $var1.' '.$var2.' '.$var3.'<br/>';
        }
        

        【讨论】:

        • 我认为这是正确的答案,因为它使用我的代码完全确定了一个快速简单的解决方案。我投票赞成所有其他答案,因为他们处理为什么。我稍后会重温这个,可惜我现在没有太多时间。
        【解决方案5】:

        保持简单、愚蠢并使用整数运算。

        例如你从 1.1 开始

        为什么不:

        for ( $i=1; $i<4; $i++ ) {
          for ( $j=0; $j<5; $j++ ) {
            $v = $i + ($j/10);
            # ...
          }
        }
        

        【讨论】:

        • -1 对于“哦,你的车坏了?好吧,改用这辆摩托车!”
        • @x3ro,感谢您让我知道为什么 -1 :-) 但是,这里的问题是二进制浮点的十进制比较。一个完全有效的解决方案是完全避免该问题:使用不进行此类比较的算法形式。我建议打个比方:我无法让我的车驶过那条狭窄的车道——你有一辆摩托车;为什么不使用它呢?.
        • 我会 +1,因为没有理由 -1。一个问题有多种解决方案。如果一个解决方案需要很多解决方法(在这种情况下,比较两个浮点数不准确),为什么不使用更简单且速度更快的其他解决方案。
        • @TerryE 我同意你的观点,一个问题的有效“解决方案”可能是避免它,但要得出你想避免你遇到的问题的结论,你必须先理解它.如果你的回答是“你不能沿着这条车道走,因为它太窄而且你的车太宽,改用这辆摩托车!”我不会投反对票的。然而,你的回答更像是“你一开始开车就很愚蠢。从现在开始使用摩托车!”没有解释“为什么?”并且没有向 OP 解释他做错了什么:)
        • 然而,多个赞成的接受答案也没有解释原因。另一个有多个赞成的答案以不太优雅的方式提出了相同的解决方案。
        【解决方案6】:

        if 语句可能永远不会仅仅因为浮点运算而执行 - 用浮点值测试严格相等总是会带来这种风险。为什么不更改条件以检查变量位于以 0.5 为中心的小区间内,以确保您抓住它???

        【讨论】:

          【解决方案7】:

          计算机无法准确表示浮点数,因为它们以二进制(以 2 为底)存储。所有的花车都会有一点点调整 +/-。

          请看这里:http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

          【讨论】:

            猜你喜欢
            • 2015-09-07
            • 1970-01-01
            • 1970-01-01
            • 2011-08-25
            • 1970-01-01
            • 2019-03-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多