$a = '35';
$b = '-34.99';
echo ($a + $b);
0.009999999999998 の結果
どうしたの?私のプログラムが奇妙な結果を報告し続けたのはなぜだろうと思いました。
PHP が期待される 0.01 を返さないのはなぜですか?
$a = '35';
$b = '-34.99';
echo ($a + $b);
0.009999999999998 の結果
どうしたの?私のプログラムが奇妙な結果を報告し続けたのはなぜだろうと思いました。
PHP が期待される 0.01 を返さないのはなぜですか?
浮動小数点演算!=実数演算だからです。不正確さによる違いの例は、いくつかのフロートa
とb
、(a+b)-b != a
です。これは、floatを使用するすべての言語に適用されます。
浮動小数点は有限の精度を持つ2進数であるため、表現可能な数には有限の量があり、これは精度の問題やこのような驚きにつながります。もう1つの興味深い読み物は次のとおりです。すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと。
問題に戻りますが、基本的に34.99または0.01を2進数で正確に表す方法はないため(10進数の場合と同様、1/3 = 0.3333 ...)、代わりに近似値が使用されます。問題を回避するには、次のことができます。
結果に使用round($result, 2)
して、小数点以下2桁に丸めます。
整数を使用します。それが通貨、たとえば米ドルの場合、$ 35.00を3500として、$ 34.99を3499として格納し、結果を100で割ります。
浮動小数点数は、すべての数値と同様に、0 と 1 の文字列としてメモリに格納する必要があります。それはコンピューターにとってすべてのビットです。浮動小数点が整数とどのように異なるかは、0 と 1 を見たいときにどのように解釈するかにあります。
1 ビットは「符号」(0 = 正、1 = 負)、8 ビットは指数 (-128 から +127 の範囲)、23 ビットは「仮数」(分数) と呼ばれる数値です。したがって、(S1)(P8)(M23) のバイナリ表現の値は (-1^S)M*2^P になります。
「仮数」は特別な形をとります。通常の科学表記法では、分数とともに「1 の位」を表示します。例えば:
4.39 × 10^2 = 439
バイナリでは、「1 の位」は 1 ビットです。科学表記法では左端の 0 をすべて無視するため (意味のない数字は無視します)、最初のビットは 1 であることが保証されます。
1.101 × 2^3 = 1101 = 13
最初のビットが 1 になることが保証されているため、スペースを節約するために数値を格納するときにこのビットを削除します。したがって、上記の数値は 101 (仮数部) として格納されます。先頭の 1 が想定されます
例として、バイナリ文字列を見てみましょう
00000010010110000000000000000000
それをコンポーネントに分割します。
Sign Power Mantissa
0 00000100 10110000000000000000000
+ +4 1.1011
+ +4 1 + .5 + .125 + .0625
+ +4 1.6875
簡単な公式を適用すると:
(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27
つまり、00000010010110000000000000000000 は浮動小数点で 27 です (IEEE-754 規格による)。
ただし、多くの数値について、正確な 2 進数表現はありません。1/3 = 0.333.... が永遠に繰り返されるのと同じように、1/100 は 0.00000010100011110101110000..... 繰り返し "10100011110101110000" です。ただし、32 ビットのコンピューターでは、数値全体を浮動小数点で格納することはできません。したがって、最善の推測を行います。
0.0000001010001111010111000010100011110101110000
Sign Power Mantissa
+ -7 1.01000111101011100001010
0 -00000111 01000111101011100001010
0 11111001 01000111101011100001010
01111100101000111101011100001010
(負の 7 は 2 の補数を使用して生成されることに注意してください)
01111100101000111101011100001010 が 0.01 のように見えないことはすぐに明らかです。
ただし、さらに重要なことは、これには繰り返し小数の切り捨てられたバージョンが含まれていることです。元の 10 進数には、「10100011110101110000」の繰り返しが含まれていました。これを 01000111101011100001010 に簡略化しました
この浮動小数点数を数式で 10 進数に変換すると、0.0099999979 が得られます (これは 32 ビット コンピューター用であることに注意してください。64 ビット コンピューターの方がはるかに正確です)。
問題をよりよく理解するのに役立つ場合は、小数の繰り返しを扱うときに小数の科学的表記法を見てみましょう。
数字を格納する「ボックス」が 10 個あるとします。したがって、1/16 のような数値を格納したい場合は、次のように記述します。
+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 |
+---+---+---+---+---+---+---+---+---+---+
これは明らかにただ6.25 e -2
の であり、 wheree
は の短縮形です*10^(
。必要なボックスは 2 (ゼロのパディング) だけでしたが、10 進数には 4 つのボックスを割り当て、符号には 2 つのボックスを割り当てました (1 つは数値の符号用、もう 1 つは指数の符号用)。
このように 10 個のボックスを使用して、-9.9999 e -9
~の範囲の数字を表示できます。+9.9999 e +9
これは、小数点以下の桁数が 4 以下であれば問題なく動作しますが、次のような数値を保存しようとするとどうなり2/3
ますか?
+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 6 | 6 | 6 | 7 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+
この新しい数0.66667
は正確には等しくありません2/3
。実際には、ずれてい0.000003333...
ます。0.66667
基数 3 で書き込もうとすると、0.2000000000012...
代わりに0.2
1/7
. _ これには 6 桁の繰り返しがあります。0.142857142857...
これを 10 進数のコンピューターに格納すると、これらの数字のうち 5 つしか表示できません。
+---+---+---+---+---+---+---+---+---+---+
| + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+
この数 は0.14286
、 によってずれています.000002857...
これは「ほぼ正しい」ですが、正確には ではありません。したがって、この数値を基数 7 で書き込もうとすると、 の代わりに恐ろしい数値が得られ0.1
ます。実際、これを Wolfram Alpha にプラグインすると、次のようになります。.10000022320335...
これらのわずかな違いは、0.0099999979
(とは対照的に0.01
)あなたにとって見慣れたものに見えるはずです。
浮動小数点数がそのように機能する理由について、ここにはたくさんの答えがあります...
しかし、任意の精度についての話はほとんどありません (Pickle が言及しました)。正確な精度が必要な場合 (または必要な場合)、それを行う唯一の方法 (少なくとも有理数の場合) は、BC Math拡張機能を使用することです (これは実際には単なるBigNum、Arbitrary Precisionの実装です...
2 つの数値を加算するには:
$number = '12345678901234.1234567890';
$number2 = '1';
echo bcadd($number, $number2);
12345678901235.1234567890
...という結果になります
これは任意精度演算と呼ばれます。基本的に、すべての数値はすべての操作で解析される文字列であり、操作は数字ごとに実行されます (長い除算を考えますが、ライブラリによって行われます)。つまり、(通常の数学構造と比較して) かなり遅いということです。しかし、それは非常に強力です。正確な文字列表現を持つ任意の数値を、乗算、加算、減算、除算、モジュロ検索、べき乗することができます。
1/3
したがって、100% の精度で 行うことはできません。小数が繰り返されるためです (したがって、合理的ではありません)。
1500.0015
ただし、二乗とは何かを知りたい場合は、次のようにします。
32 ビット浮動小数点数 (倍精度) を使用すると、次の推定結果が得られます。
2250004.5000023
しかし、bcmath は次の正確な答えを示します。
2250004.50000225
それはすべて、必要な精度に依存します。
また、ここで注意すべきことがあります。PHP は、32 ビットまたは 64 ビットの整数のみを表すことができます (インストールによって異なります)。したがって、整数がネイティブの int 型のサイズ (32 ビットの場合は 21 億、9.2 x10^18、または符号付き整数の場合は 92 億) を超える場合、PHP は int を float に変換します。これはすぐに問題になるわけではありませんが (システムの float の精度よりも小さいすべての int は、定義により float として直接表現できるため)、2 つを掛け合わせようとすると、精度が大幅に失われます。
たとえば、次のようになり$n = '40000000002'
ます。
数値としては に$n
なりますがfloat(40000000002)
、正確に表現されているので問題ありません。しかし、それを 2 乗すると、次のようになります。float(1.60000000016E+21)
文字列として (BC 数学を使用)、$n
正確に になります'40000000002'
。そして、それを二乗すると、次のようになります。string(22) "1600000000160000000004"
...
したがって、大きな数値または有理小数点の精度が必要な場合は、bcmath を調べることをお勧めします...
ここではbcadd()が役立つかもしれません。
<?PHP
$a = '35';
$b = '-34.99';
echo $a + $b;
echo '<br />';
echo bcadd($a,$b,2);
?>
(わかりやすくするために非効率的な出力)
最初の行は 0.009999999999998 です。2 番目は 0.01 です
0.01は、一連の2進分数の合計として正確に表すことができないためです。そして、それがフロートがメモリに保存される方法です。
聞きたいことではないと思いますが、質問への答えです。修正方法については、他の回答を参照してください。
PHP のround()
関数を使用します: http://php.net/manual/en/function.round.php
この回答は問題を解決しますが、理由は説明しません。[私も C++ でプログラミングしているので、私には明らかです ;]] と思いましたが、そうでない場合は、PHP には独自の計算精度があり、その特定の状況では、その計算に関する最も準拠した情報が返されたとしましょう。 .
使いやすくなりませんnumber_format(0.009999999999998, 2)
か$res = $a+$b; -> number_format($res, 2);
?