$x = 8 - 6.4; // 等于 1.6
$y = 1.6;
var_dump($x == $y); // 不为真
PHP 认为 1.6(来自差值)不等于 1.6。要使其正常工作,请使用 round()
var_dump(round($x, 2) == round($y, 2)); // 为真
这可能是因为 $x 实际上不是 1.6,而是 1.599999..,而 var_dump 将其显示为 1.6。
浮点数(也称为“浮点型”、“双精度型”或“实数”)可以使用以下任何语法指定:
<?php
$a = 1.234;
$b = 1.2e3;
$c = 7E-10;
$d = 1_234.567; // 从 PHP 7.4.0 开始
?>
从 PHP 7.4.0 开始正式支持(之前不允许使用下划线)
LNUM [0-9]+(_[0-9]+)* DNUM ({LNUM}?"."{LNUM}) | ({LNUM}"."{LNUM}?) EXPONENT_DNUM (({LNUM} | {DNUM}) [eE][+-]? {LNUM})
浮点数的大小取决于平台,尽管最大值约为 1.8e308,精度约为 14 位十进制数字是一个常见值(64 位 IEEE 格式)。
浮点数的精度有限。尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,这将导致由于舍入而产生的最大相对误差约为 1.11e-16。非基本算术运算可能会产生更大的误差,当然,当多个运算复合时,必须考虑误差传播。
此外,可以用十进制浮点数精确表示的有理数,例如 0.1
或 0.7
,无论尾数的大小如何,都不能用内部使用的二进制浮点数精确表示。因此,它们不能在不损失少量精度的情况下转换为其内部二进制对应物。这可能导致令人困惑的结果:例如,floor((0.1+0.7)*10)
通常会返回 7
而不是预期的 8
,因为内部表示将类似于 7.9999999999999991118...
。
因此,永远不要相信浮点数结果的最后一位数字,并且不要直接比较浮点数是否相等。如果需要更高的精度,可以使用 任意精度数学函数 和 gmp 函数。
有关“简单”解释,请参阅 » 浮点指南,标题也为“为什么我的数字加不起来?”
如果字符串是 数字型 或以数字开头,则它将解析为相应的浮点值,否则它将转换为零(0
)。
如上面的警告中所述,由于浮点数的内部表示方式,测试浮点值是否相等是有问题的。但是,有一些方法可以进行浮点值的比较,从而规避这些限制。
为了测试浮点值是否相等,使用了舍入引起的相对误差的上限。此值称为机器ε或单位舍入,是计算中可接受的最小差异。
$a 和 $b 精度为 5 位。
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
?>
$x = 8 - 6.4; // 等于 1.6
$y = 1.6;
var_dump($x == $y); // 不为真
PHP 认为 1.6(来自差值)不等于 1.6。要使其正常工作,请使用 round()
var_dump(round($x, 2) == round($y, 2)); // 为真
这可能是因为 $x 实际上不是 1.6,而是 1.599999..,而 var_dump 将其显示为 1.6。
一般计算提示:如果您正在跟踪金钱,请为您自己和您的用户提供一个便利,即以美分作为内部单位处理所有内容,并尽可能多地进行整数运算。如果可能,以美分存储值。以美分加减。在每个将涉及浮点数的操作中,问问自己“如果我在此处得到几分之一美分,现实世界中会发生什么”,如果答案是此操作将生成整数美分的交易,则不要尝试携带虚构的小数精度,这只会导致以后出现问题。
只是对“浮点精度”插入内容的一个评论,内容如下:“这与……0.3333333 相关。”
虽然作者可能知道自己在说什么,但这种精度损失与十进制表示法无关,而是与在有限寄存器中作为浮点二进制数的表示有关。例如,0.8在十进制中是有限的,但在二进制中是无限循环的0.110011001100...,会被截断。0.1和0.7在二进制中也是无限循环的,因此也会被截断,这些被截断的数字之和并不等于0.8的被截断的二进制表示(这就是为什么(floor)(0.8*10)会产生一个不同、更直观的结果的原因)。但是,由于2是10的因数,任何在二进制中有限的数在十进制中也是有限的。
<?php
//请考虑以下代码
printf("%.53f\n",0.7+0.1); // 0.79999999999999993338661852249060757458209991455078125
var_dump(0.7+0.1); // float(0.8)
var_dump(0.799999999999999); //float(0.8)
var_dump(0.7999999); // float(0.7999999)
//结论:PHP最多支持53位小数,但在某些输出函数(如var_Dump)中,输出超过14位小数时,会对第15位进行四舍五入,这会导致严重的误导
//实验环境:linux x64,php7.2.x
我想指出PHP浮点支持的一个“特性”,这里没有任何地方说明清楚,这让我抓狂。
这个测试(其中var_dump说$a=0.1且$b=0.1)
if ($a>=$b) echo "blah!";
在某些情况下会失败,因为存在隐藏的精度(标准的C语言问题,PHP文档中没有提到,所以我认为他们已经解决了这个问题)。我应该指出,我最初认为这是浮点数作为字符串存储的问题,所以我强制它们成为浮点数,但它们仍然没有被正确评估(可能那里有两个不同的问题)。
为了修复,我不得不使用这个糟糕的权宜之计(无论如何都是等效的)
if (round($a,3)>=round($b,3)) echo "blah!";
这有效。显然,即使var_dump说变量是相同的,而且它们应该是相同的(从0.01开始,重复添加0.001),但它们实际上并不相同。那里有一些隐藏的精度让我抓狂。也许应该添加到文档中?
要比较两个数字,请使用
$epsilon = 1e-6;
if(abs($firstNumber-$secondNumber) < $epsilon){
// 相等
}
在以后用作代码的字符串中使用浮点值时要小心,例如在生成JavaScript代码或SQL语句时。浮点数实际上是根据浏览器的区域设置进行格式化的,这意味着“0.23”将变成“0,23”。想象一下这样的情况
$x = 0.23;
$js = "var foo = doBar($x);";
print $js;
这将导致不同区域设置的用户得到不同的结果。在大多数系统上,这将打印
var foo = doBar(0.23);
但是,例如,当德国用户访问时,它将有所不同
var foo = doBar(0,23);
这显然是函数的不同调用。JavaScript不会声明错误,附加参数会被忽略,但函数doBar(a)将获得0作为参数。类似的问题也可能出现在其他任何地方(SQL,任何用作其他地方代码的字符串)。如果您使用“.”运算符而不是在字符串中评估变量,则问题仍然存在。
因此,如果您真的需要确保字符串格式正确,请使用number_format()!
实际上,“浮点精度”问题意味着
<? echo (69.1-floor(69.1)); ?>
认为这将返回0.1?
它不会 - 它返回0.099999999999994
<? echo round((69.1-floor(69.1))); ?>
这返回0.1,这是我们使用的解决方法。
请注意
<? echo (4.1-floor(4.1)); ?>
*确实*返回0.1 - 因此,如果您像我们一样使用较小的数字测试此代码,您就不会像我们一样理解为什么您的脚本突然停止工作,直到您像我们一样花费大量时间调试它。
所以,一切都很好。
在某些情况下,您可能希望获取浮点数的最大值,而不会得到“INF”。
var_dump(1.8e308); 通常会显示:float(INF)
我编写了一个小型函数,它将迭代以查找最大的非无限浮点值。它带有一个可配置的乘数和仿射值,因此您可以共享更多CPU以获得更精确的估计。
我没有看到使用更多仿射值获得更好的结果,但是,可能性就在这里,所以如果您真的认为它值得CPU时间,请尝试更多仿射值。
最佳结果似乎是mul=2/affine=1。您可以使用这些值并查看您得到的结果。好消息是这种方法适用于任何系统。
<?php
function float_max($mul = 2, $affine = 1) {
$max = 1; $omax = 0;
while((string)$max != 'INF') { $omax = $max; $max *= $mul; }
for($i = 0; $i < $affine; $i++) {
$pmax = 1; $max = $omax;
while((string)$max != 'INF') {
$omax = $max;
$max += $pmax;
$pmax *= $mul;
}
}
return $omax;
}
?>
<?php
$binarydata32 = pack('H*','00000000');
$float32 = unpack("f", $binarydata32); // 0.0
$binarydata64 = pack('H*','0000000000000000');
$float64 = unpack("d", $binarydata64); // 0.0
?>
对于32位和64位数字,我都得到0。
但是,请不要使用您自己的“函数”来“转换”浮点数和二进制数。PHP中的循环性能很糟糕。使用pack/unpack,您可以使用处理器的编码,这总是正确的。在C++中,您可以将相同的32/64位数据作为浮点数/双精度数或32/64位整数访问。没有“转换”。
要获取二进制编码
<?php
$float32 = pack("f", 5300231);
$binarydata32 =unpack('H*',$float32); //"0EC0A14A"
$float64 = pack("d", 5300231);
$binarydata64 =unpack('H*',$float64); //"000000C001385441"
?>
还有我半年前的例子
<?php
$binarydata32 = pack('H*','0EC0A14A');
$float32 = unpack("f", $binarydata32); // 5300231
$binarydata64 = pack('H*','000000C001385441');
$float64 = unpack("d", $binarydata64); // 5300231
?>
请注意大端和小端字节序……
考虑以下情况
(19.6*100) != 1960
echo gettype(19.6*100) 返回'double',但是即使……
(19.6*100) !== (double)1960
19.6*100 无法与任何东西进行比较,除非先手动
将其强制转换为其他类型。
(string)(19.6*100) == 1960
经验法则:如果它有小数点,请使用BCMath函数。