2

PHP でオッズ コンバーターを作成していて、小数オッズを小数オッズに変換するためのこのコードに出くわしました。

function dec2frac($dec) { 
    $decBase = --$dec; 
    $div = 1; 
    do { 
        $div++; 
        $dec = $decBase * $div; 
    } while (intval($dec) != $dec); 
    if ($dec % $div == 0) { 
        $dec = $dec / $div; 
        $div = $div / $div; 
    } 
    return $dec.'/'.$div; 
} 

コードをテストしたところ、計算に成功することもあれば、しばらくの間ページをロードしようとしても失敗することもあったため、何らかの形でループに陥ったと考えました。制限時間を 1 秒に設定し、ループ内にエコーを設定したところ、私の疑いが確認されました。この 2 つのエコーをループに追加して、何が問題なのかを確認しました。

echo "$dec $div<br/>";
echo var_dump(intval($dec) == $dec)." $dec is int<br/>";

10 進数 = 1.6 を使用した失敗時の出力例

1.2 2
bool(false) 1.2 は int
1.8 3
bool(false) 1.8 は int
2.4 4
bool(false) 2.4 は int
3 5
bool(false) 3 は int //ここでブレークして 3/5 を返す必要があります
3.6 6
bool( false) 3.6 は int
4.2 7
bool(false) 4.2 は int

10 進数 = 1.8 を使用した、成功時の出力例

1.6 2
bool(false) 1.6 は int
2.4 3
bool(false) 2.4 は int
3.2 4
bool(false) 3.2 は int
4 5
bool(true) 4 は int

時々整数を認識しないのはなぜですか? 整数が見つかったときにループを終了するように修正するにはどうすればよいですか?

4

1 に答える 1

2

浮動小数点の丸め誤差のようです。intval($dec) と $decは等しいように見えますが、実際には違います。違いはごくわずかですが、厳密な平等は失敗します。

私の64ビットシステムでは、1E-15の精度で関数が機能し、1E-16の精度でループします。

一般に、2 つの浮動小数点数の厳密な比較は避ける必要があります。2 つの float は、その差がしきい値より小さい場合、「等しい」と見なされます。このしきい値を計算する方法はいくつかあります (Google で「機械の精度を決定する」)。どこでも同じというわけではありません。

私のPHP.INIにはデフォルト値があるので

; The number of significant digits displayed in floating point numbers.
; http://php.net/precision
precision = 14

次に、一方が 3 で他方が 3.0000000000044 であっても、2 つの数値は等しく表示され、caltrop は見過ごされます。

精度が 18 の場合、$dec期待どおりにならないことが示されています。

1.20000000000000018
1.80000000000000027
2.40000000000000036
3.00000000000000044

試す:

function dec2frac($dec) {
    $decBase = --$dec;
    $div = 1;
    do {
        $div++;
        $dec = $decBase * $div;
    } while (abs(intval($dec) - $dec) > 1e-15);
    if (($dec % $div) == 0) {
        $dec = $dec / $div;
        $div = $div / $div;
    }
    return $dec.'/'.$div;
}
于 2012-09-30T21:53:35.257 に答える