PHP Conference Japan 2024

浮點數

浮點數(也稱為「浮點數」、「雙精度數」或「實數」)可以使用以下任何語法指定:

<?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)。

從其他類型轉換

對於其他類型的值,轉換過程是先將值轉換為 整數,然後再轉換為 浮點數。有關更多資訊,請參閱 轉換為整數

注意:

由於某些類型在轉換為 整數 時具有未定義的行為,因此在轉換為 浮點數 時也是如此。

比較浮點數

如上文警告中所述,由於浮點數值的內部表示方式,測試浮點數值是否相等會產生問題。然而,有一些方法可以比較浮點數值來解決這些限制。

為了測試浮點數值是否相等,會使用捨入造成的相對誤差上限。這個值稱為機器ε(machine epsilon)或單位捨入誤差,是計算中最小的可接受差異。

$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 則註釋

catalin dot luntraru at gmail dot com
11 年前
$x = 8 - 6.4; // 等於 1.6
$y = 1.6;
var_dump($x == $y); // 結果不是 true

PHP 認為 1.6(來自差值)不等於 1.6。要使其運作,請使用 round()

var_dump(round($x, 2) == round($y, 2)); // 結果是 true

發生這種情況可能是因為 $x 並非真正的 1.6,而是 1.599999...,而 var_dump 將其顯示為 1.6。
feline at NOSPAM dot penguin dot servehttp dot com
20 年前
一般計算提示:如果您要追蹤金錢,請為您自己和您的使用者著想,在內部以分為單位處理所有內容,並盡可能以整數進行數學運算。如果可能,請以分為單位儲存值。以分為單位進行加減運算。在每個涉及浮點數的運算中,請問問自己「如果我在這裡得到一分錢的分數,在現實世界中會發生什麼?」如果答案是此運算將產生以整數分為單位的交易,請不要嘗試攜帶虛構的分數精度,這只會在以後搞砸事情。
www.sarioz.com
21 年前
只是對「浮點數精度」插圖中的一句話發表評論,該插圖如下:「這與 .... 0.3333333 有關。」

雖然作者可能知道他們在說什麼,但這種精度損失與十進位表示法無關,它與在有限暫存器中以浮點二進位表示有關,例如,雖然 0.8 以十進位終止,但它是二進位中重複的 0.110011001100...,會被截斷。0.1 和 0.7 在二進位中也是非終止的,因此它們也被截斷,這些截斷數字的總和不等於 0.8 的截斷二進位表示形式(這就是為什麼 (floor)(0.8*10) 產生不同、更直觀的結果的原因)。但是,由於 2 是 10 的因數,因此任何以二進位終止的數字也以十進位終止。
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
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),但它們並非如此。那裡有一些隱藏的精度讓我抓狂。也許這應該添加到文件中?
lwiwala at gmail dot com
7 年前
要比較兩個數字,請使用

$epsilon = 1e-6;

if(abs($firstNumber-$secondNumber) < $epsilon){
// 相等
}
Luzian
19 年前
在字串中使用浮點數值時要小心,這些字串稍後會被用作程式碼,例如在產生 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() 來完成!
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 - 所以如果您像我們一樣,用小數字測試這個,您就不會像我們一樣,理解為什麼您的腳本突然停止工作,直到您像我們一樣,花費大量的時間除錯它。

所以,一切都很好。
magicaltux at php dot net
14 年前
在某些情況下,您可能希望取得浮點數的最大值而不得到「INF」。

var_dump(1.8e308); 通常會顯示:float(INF)

我寫了一個小函式,它會迭代以找到最大的非無限浮點數值。它帶有一個可配置的乘數和仿射值,因此您可以分享更多 CPU 以獲得更準確的估計。

我沒有看到使用更多仿射值得到更好的結果,但是,這個可能性就在這裡,所以如果您真的認為它值得 CPU 時間,就嘗試更多仿射值。

最佳結果似乎是 mul=2/affine=1。您可以調整這些值並查看結果。好處是這個方法適用於任何系統。

<?php
函式 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;
}
?>
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
?>

還有,請注意大端序和小端序…
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