2013-09-05 24 views
41

コード:最後の番号(1)が印刷されるのはなぜですか?

<?php 
$start = 0; 
$stop = 1; 
$step = ($stop - $start)/10; 
$i = $start + $step; 
while ($i < $stop) { 
    echo($i . "<br/>"); 
    $i += $step; 
} 
?> 

出力:fiddle

つ以上を作成し

0.1 
0.2 
0.3 
0.4 
0.5 
0.6 
0.7 
0.8 
0.9 
1 <-- notice the 1 printed when it shouldn't 

:あなたは$start = 1$stop = 2を設定した場合、それは正常に動作します。

使用:php 5.3.27

1が印刷されているのはなぜ?

+1

を驚くほど、あなたは20に間隔を分割する場合:=($停止 - $スタート)$ステップ/ 20;それもOKです – user4035

+3

おそらく丸め浮動小数点エラーです。 – Blazemonger

+1

http://stackoverflow.com/questions/3726721/php-math-precisionに関連する – j08691

答えて

53

浮動小数点演算には欠陥があるだけでなく、時にはその表現is flawed tooがあるため、ここに該当します。

あなたが実際に0.1、0.2、...得ることはありません - それは確認することは非常に簡単です:

$start = 0; 
$stop = 1; 
$step = ($stop - $start)/10; 
$i = $start + $step; 
while ($i < $stop) { 
    print(number_format($i, 32) . "<br />"); 
    $i += $step; 
} 

唯一の違いはここに、あなたが見るように、echonumber_format呼び出しに置き換えられていることです。しかし、結果は大きく異なります:

0.10000000000000000555111512312578 
0.20000000000000001110223024625157 
0.30000000000000004440892098500626 
0.40000000000000002220446049250313 
0.50000000000000000000000000000000 
0.59999999999999997779553950749687 
0.69999999999999995559107901499374 
0.79999999999999993338661852249061 
0.89999999999999991118215802998748 
0.99999999999999988897769753748435 

を参照してください。実際には一度だけ0.5でした。その数はフロートコンテナに格納できるためです。他のすべては近似値だけでした。

これを解決するにはどうすればよいですか?まあ、1つの根本的なアプローチは、浮動小数点ではなく、整数を使用して同様の状況です。それは

$start = 0; 
$stop = 10; 
$step = (int)(($stop - $start)/10); 
$i = $start + $step; 
while ($i < $stop) { 
    print(number_format($i, 32) . "<br />"); 
    $i += $step; 
} 

...それはOK働くだろう...あなたはそれをこのように行っていることに気づくのは簡単です:

を別の方法として、あなたには、いくつかの文字列に浮動小数点数を変換するためにnumber_formatを使用することができ、その後、これを比較しますpreformatted floatの文字列です。このように:

$start = 0; 
$stop = 1; 
$step = ($stop - $start)/10; 
$i = $start + $step; 
while (number_format($i, 1) !== number_format($stop, 1)) { 
    print(number_format($i, 32) . "\n"); 
    $i += $step; 
} 
+1

これを防ぐための良い方法はありますか? –

+4

解決策の1つは、 '$ step'を整数にすることです:' $ start = 0; $ stop = 10; $ step = 1; 'とループ内の除算を行います。 – Blazemonger

+0

@RichBradshawこれを防ぐには、浮動小数点演算を整数に切り替えるには、stopを10倍し、iを0.1ではなく1だけ増やします。 – user4035

13

変数$ iの数値は1(印刷時)ではないという問題があります。その実際のちょうど1未満です。テスト($ i < $ stop)が真である場合、数値は小数点に変換され(丸め1になる)、表示されます。

なぜ$ iは正確に1ではないのですか? 10 * 0.1と言ってそこに着いたので、0.1はバイナリで完全に表現できないからです。有限の2のべき乗の和として表すことができる数だけを完全に表すことができる。

なぜ$は正確に1ですか?浮動小数点形式ではないためです。言い換えれば、それは最初から正確です - 浮動小数点10 * 0.1を使用するシステム内では計算されません。次のように

数学的には、我々はこれを書くことができる: enter image description here

を64ビットバイナリフロートはわずか0.1に近似和の最初の27の非ゼロ項を保持することができます。仮数部の他の26ビットは、ゼロ項を示すためにゼロのままです。理由0.1は物理的に表現可能ではないということは、必要な用語のシーケンスが無限であるということです。一方、1のような数字は、有限の有限数しか必要とせず、表現可能です。我々はそれがすべての数字の場合であることを望みます。このため、10進浮動小数点はそのような重要なイノベーションです(まだ広く利用できません)。それは、私たちが書き留めることができる任意の数を表すことができ、完全にそうすることができます。もちろん、利用可能な数字の数は有限のままです。

0.1にはループ変数のインクリメントがあり、実際には表現できないため、値1.0(表現可能ですが)は決してループ内で決して到達されません。あなたのステップは、常に10の要因となる場合

2

、次で迅速にこれを達成することができます:

<?php 
$start = 0; 
$stop = 1; 
$step = ($stop - $start)/10; 
$i = $start + $step; 
while (round($i, 1) < $stop) { //Added round() to the while statement 
    echo($i . "<br/>"); 
    $i += $step; 
} 
?> 
関連する問題