PHP中浮点数比较的问题

7

我试图创建单元测试用例来检查我的表格值是否正确或错误。

这是我的代码

echo $a = 2/9;
echo "<br>".$b=0.22222222222222;

echo "<br>".gettype($a);
echo "<br>".gettype($b);
if($a==$b){
    echo "<br>". "equal";
}else echo "<br>". "Not equal";


if((string)$a==(string)$b){
    echo "<br>". "equal";
}else echo "<br>". "Not equal";

为什么我的第一个if条件不起作用?我找不到原因,请帮忙。

2
可能是 https://dev59.com/_XA75IYBdhLWcg3ws7Yi 的重复。永远不要使用“==”来比较浮点数。此外,“2/9”并不等于“0.22222222222222”。小数部分是无限的,因此即使 PHP 对浮点数进行符号计算,您的比较也应该失败。 - Basti
3个回答

11

这个测试违反了浮点编程的基本规则:不要进行等式比较。

由于浮点数的小数部分只有有限数量的位,因此会出现许多问题。这些问题通常被称为"舍入误差",尽管在大多数情况下它们并不是错误,而是格式限制。

例如,由于我们在编程时使用十进制字符串来表示数字...大多数我们能够写下的数字如果有小数部分,在浮点格式中就没有相应的表示。其二进制小数部分会重复。

这基本上排除了精确比较浮点数,除了整数值之间具有的比较,具有讽刺意味的是,您需要实现一种模糊比较,如 abs(a - b) < epsilon

事实上,您的2/9是一个特殊情况,它既没有一个十进制字符串的有限表示,也没有一个二进制字符串的有限表示!1

为了成功地将2/9与常量相等地比较,程序、解释器和库需要更高的完美度要求,这是不能保证的。

例如,您需要输入比您实际需要的更多的2,解释器必须在知道比格式更多精度的情况下舍入常量的低位位。机器在执行操作时确实有一些额外的知识,但在将常量从源代码转换为内部形式时,解释器可能没有这种知识。此外,运行时舍入受到各种选项的影响,像PHP这样的语言甚至可能没有明确指定不可表示常量从源代码到内部形式是如何进行舍入的。

实际上情况比这更糟,因为十进制字符串中每个0.2/10n分量也没有确切的二进制等价物。因此,非常有可能完美而忠实地转换0.22222222222222实际上并不等于对实际的2/9进行最佳努力表示的结果。在任何特定(有限)位数中,你无法将最接近2/9的确切二进制分数表示为有限的十进制字符串。

(我们一定要有一个标准答案,关于不使用浮点数进行相等比较。)


1. 每个机器分数都是x/2n形式的有理数。现在,这些常数是十进制的,每个十进制常数都是x/(2n*5m)形式的有理数。5m是奇数,所以它们中没有一个具有2n因子。只有当m == 0时,在分数的二进制和十进制扩展中才有一个有限的表示。例如,1.25是精确的,因为它是5/(22*50),但0.1不是,因为它是1/(20*51)。对于有理数2/9,既没有2n因子,也没有5m因子。


5

浮点数很棘手,您需要限制小数点的位数。

$a = 2/9;
$b=0.22222222222222;

$a = number_format($a, 9);
$b = number_format($b, 9);

echo "a = " . $a . " and b = " . $b;

echo "<br>".gettype($a);
echo "<br>".gettype($b);
if($a==$b){
    echo "<br>". "equal";
}else echo "<br>". "Not equal";


if((string)$a==(string)$b){
    echo "<br>". "equal";
}else echo "<br>". "Not equal";

3
如果你查看 PHP 浮点数 的文档(包括双精度),你会很快发现,由于浮点数的性质,比较是极其困难的。

因此,永远不要信任浮点数结果的最后一位,并且不要直接将浮点数进行相等比较。

文档也提供了一个例子:

<?php

$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;

if(abs($a-$b) < $epsilon) {
    echo "true";
}

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