PHP基础数学中的浮点数错误

6
可能重复:
为什么十进制数字不能在二进制中精确表示?
浮点数值问题
$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并使用>代替==就可以解决问题。虽然代码粗糙,但确实解决了原始问题。谢谢大家的其他回复,我会阅读并跟进以改善我的编码。
再次感谢。

也许可以发布一下那个“疯狂的科学答案”是什么? - mathematician1975
可能是浮点数问题的重复,理解PHP中的浮点数,等等。 - fresskoma
浮点数问题的一个很好的解释:https://dev59.com/WXNA5IYBdhLWcg3wHp6B - mtrw
7个回答

5

5
这个可以得到期望的结果:
$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/>';
}

2
我投票支持这个答案,因为它使用我的代码完美地实现了一个快速简单的解决方案。我已经赞同了所有其他回答,因为它们都涉及到了“为什么”。我稍后会再回来看看,但现在时间不多。 - Dorjan

3

二进制浮点数会选择近似值来表示无法表示的十进制浮点数。因此,比较浮点数和浮点数是不准确的(因为你不能确定它的行为)。

你需要发明自己的十进制浮点数。如果你知道你想要哪种精度,那么发明起来并不难。在你的情况下,你只需要一位精确数字。我们的十进制整数公式为:值/我们的精确数字10的幂次方,即你的公式为= 值/ 10

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

输出与将你的表示转换为正常表示一样容易。在你的情况下,echo $var1 . '<br>'; 将变成 echo $var1 / 10 . '<br>';

$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;
  }
}

1
一个合适的解决方案是使用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');
}

这个输出正确的结果


0

if语句可能永远不会执行,仅仅是由于浮点算术的一般性质 - 使用浮点值进行严格相等测试总是存在这种风险。为什么不将条件更改为检查变量是否位于以0.5为中心的小区间内,以确保您捕获它?


-1

保持简单,愚蠢并使用整数算术。

例如,您可以从1.1开始。

为什么不呢:

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

3
“哦,你的车坏了?那就用这辆摩托车吧!” - fresskoma
@x3ro,感谢您让我知道为什么是-1 :-) 然而,这里的问题在于二进制浮点数的小数比较。一个完全有效的解决方案是避免这个问题:使用一种不进行这种比较的算法形式。我建议使用类比:我不能把我的车开到那条狭窄的小路上——你有一辆摩托车,为什么不用它呢? - TerryE
我会点赞,因为没有理由踩。一个问题有多种解决方案。如果一个解决方案需要很多的变通方法(在这种情况下,比较两个浮点数的不准确性),为什么不使用其他更简单且更快的解决方案呢? - invisal
1
@TerryE 我同意你的观点,解决问题的一个有效方法可能是避免它,但要得出你想要避免所遇到的问题的结论,你必须首先理解它。如果你的回答是“你不能走这条路,因为它太窄了,而你的车太宽了,改骑这辆摩托车吧!”,我不会给你投反对票。然而,你的回答更像是“你一开始就开错了车,太蠢了。从现在开始用摩托车吧!”而没有解释“为什么?”也没有向 OP 解释他做错了什么 :) - fresskoma
然而,获得多个赞同的被接受答案也没有解释为什么。另一个获得多个赞同的答案用不太优雅的方式建议了相同的解决方案。 - Jeremy Rosenberg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接