浮点数

浮点数(也称为“浮点数”、“双精度数”或“实数”)可以使用以下任意语法指定

<?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.10.7,在内部使用的二进制中没有精确的表示,无论尾数的大小如何。因此,它们无法转换为其内部二进制对应物,而不会损失一小部分精度。这会导致令人困惑的结果:例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期的 8,因为内部表示将类似于 7.9999999999999991118...

因此,永远不要相信浮点数结果的最后一位,不要直接比较浮点数以查看是否相等。如果需要更高的精度,可以使用 任意精度数学函数gmp 函数。

要获得“简单”的解释,请参阅 » 浮点指南,该指南也称为“为什么我的数字加不起来?”

转换为浮点数

从字符串转换

如果字符串是 数字 或以数字开头,则它将解析为相应的浮点数,否则它将转换为零 (0)。

从其他类型转换

对于其他类型的值,转换是通过首先将值转换为 int,然后转换为 float 来执行的。有关更多信息,请参阅 转换为整数

注意:

由于某些类型在转换为 int 时具有未定义的行为,因此在转换为 float 时也是如此。

比较浮点数

如上面的警告中所述,由于浮点数在内部的表示方式,测试浮点数是否相等是有问题的。但是,有一些方法可以进行浮点数比较,以绕过这些限制。

要测试浮点数是否相等,可以使用由于舍入造成的相对误差的上限。该值称为机器ε或单位舍入,是计算中可接受的最小差异。

$a$b 相等,精度为 5 位小数。

<?php
$a
= 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;

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

NaN

某些数值运算可能会导致由常量 NAN 表示的值。此结果表示浮点计算中未定义或不可表示的值。此值与任何其他值(包括自身)之间的任何宽松或严格比较(除了 true)将得出 false 的结果。

由于 NAN 表示任何数量的不同值,因此不应将 NAN 与其他值(包括自身)进行比较,而应使用 is_nan() 进行检查。

添加笔记

用户贡献笔记 11 条笔记

258
catalin dot luntraru at gmail dot com
10 年前
$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。
115
feline at NOSPAM dot penguin dot servehttp dot com
20 年前
一般计算提示:如果您正在跟踪金钱,请给自己和您的用户一个方便,在内部以美分处理所有内容,并尽可能使用整数进行数学运算。如果可能,以美分存储值。以美分加减。在涉及浮点数的每个运算中,问问自己“如果我在此处得到几分钱,现实世界会发生什么”,如果答案是此运算将生成以整数美分进行的交易,请不要尝试保留虚构的小数精度,这只会导致以后出现问题。
67
www.sarioz.com
21 年前
只是对“浮点精度”补充说明中的一句话进行评论,这句话是:“这与……0.3333333 有关。”

虽然作者可能知道自己在说什么,但这项精度损失与十进制表示无关,而是与在有限寄存器中以浮点二进制表示有关,例如,虽然 0.8 在十进制中是终止的,但它在二进制中是重复的 0.110011001100...,它会被截断。0.1 和 0.7 在二进制中也是非终止的,因此它们也会被截断,这些截断数字的总和不等于 0.8 的截断二进制表示(这就是为什么 (floor)(0.8*10) 会产生不同的、更直观的、结果)。但是,由于 2 是 10 的因子,因此任何在二进制中终止的数字在十进制中也终止。
3
251701981 at qq dot com
1 年前
<?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
22
backov at spotbrokers-nospamplz dot com
21 年前
我想指出 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),但它们并不相同。那里有一些隐藏的精度,让我抓狂。也许应该在文档中添加这一点?
6
lwiwala at gmail dot com
7 年前
要比较两个数字,请使用

$epsilon = 1e-6;

if(abs($firstNumber-$secondNumber) < $epsilon){
// 相等
}
10
Luzian
18 年前
在将浮点数用作以后代码的字符串时要小心,例如在生成 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,任何用作其他地方代码的字符串)。如果使用“.”运算符而不是在字符串中评估变量,则问题仍然存在。

因此,如果您 REALLY 需要确保字符串格式正确,请使用 number_format() 来完成!
7
magicaltux at php dot net
14 年前
在某些情况下,您可能希望获得浮点数的最大值,而不会得到“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;
}
?>
6
james dot cridland at virginradio dot co dot uk
21 年前
实际上,“浮点数精度”框意味着

<? 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 - 因此,如果您像我们一样,使用较小的数字测试它,您就不会像我们一样,不明白为什么您的脚本突然停止工作,直到您像我们一样,花了很多时间调试它。

所以,这真是太棒了。
5
zelko at mojeime dot com
13 年前
<?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
?>

请注意大小端男孩...
2
rick at ninjafoo dot com
19 年前
考虑以下内容

(19.6*100) != 1960

echo gettype(19.6*100) 返回 'double',但是即使 .....

(19.6*100) !== (double)1960

19.6*100 无法与任何东西比较,除非首先将其手动
强制转换为其他类型。

(string)(19.6*100) == 1960

经验法则,如果它有小数点,请使用 BCMath 函数。
To Top