PHP は、型を保存するために必然的にシームレスで透過的な暗黙的な型変換を必要とします。 zval の値は次のとおりです (例として 5.2):
リーリー上記の構造で、実際に値自体を格納するのは zvalue_value ユニオンです:
リーリー今日のトピックでは、そのうちの 2 つ、lval と dval にのみ焦点を当てます。long lval はコンパイラと OS のワード長に応じて不確実な長さを持ち、Double dval (倍精度) であることを認識する必要があります。 IEEE 754 によって指定されており、固定長であり、64 ビットである必要があります。
これにより、一部の PHP コードが「非プラットフォーム非依存」になるため、以下の説明では、特に指定がない限り、long が 64 ビットであることを前提としています。
ここでは IEEE 754 の浮動小数点カウント方法を引用しません。興味があれば、ご自身で確認してください。重要な点は、倍精度浮動小数点の仮数が 52 ビットで格納されることです。ビット、合計は 53 ビットです。ここで、非常に興味深い疑問が生じます。例として C コードを使用してみましょう (long が 64 ビットであると仮定します)。 リーリー
すみません、a の値がどの範囲内にある場合、上記のコードは成功すると言えますか? (答えは記事の最後に残しておきます)
それでは、本題に戻りましょう。PHP は、スクリプトを実行する前に、まずスクリプトを読み取り、スクリプトを分析する必要があります。たとえば、次のスクリプトの場合は、次のとおりです。 リーリー
出力:リーリー
言い換えると、字句解析の段階で、PHP はリテラル値が現在のシステムの長いテーブル値の範囲を超えているかどうかを判断し、超えていない場合は lval を使用して保存し、それ以外の場合は dval を使用します。つまり、zval IS_FLOAT.最大の整数値より大きい値を使用すると、精度が失われる可能性があるため、注意する必要があります。
リーリー
出力は false です。冒頭の議論の続きですが、前述したように、PHP の整数は 32 ビットまたは 64 ビットであるため、64 ビットで正常に実行できる一部のコードは、目に見えない型変換、精度が原因で失敗する可能性があると判断されています。損失が発生し、コードが 32 ビット システムで適切に実行されなくなります。
したがって、この重要な値には注意する必要があります。幸いなことに、この重要な値は PHP で定義されています。 リーリー
もちろん、安全を期すために、文字列を使用して大きな整数を保存し、bcmath などの数学関数ライブラリを使用して計算を実行する必要があります。
さらに、私たちを混乱させるもう 1 つの重要な設定があります。この設定は php.precision です。この設定は、PHP が float 値を出力するときに出力する有効桁数を決定します。
最後に、上で挙げた質問を振り返ってみましょう。つまり、float に変換してから long に戻すときに精度が失われないようにするための、long 整数の最大値はいくらですか?たとえば、整数の場合、そのバイナリ表現は 101 であることがわかります。次に、2 ビットを右シフトして 1.01 にし、上位の暗黙的な有効ビット 1 を破棄して、バイナリ値 5 を取得します。二重に:
0/*符号ビット*/ 10000000001/*指数ビット*/ 010000000000000000000000000000000000000000000000000
5 のバイナリ表現は、損失なく仮数部に格納されます。この場合、double から long に変換するときに精度が失われることはありません。double は仮数を表すために 52 ビットを使用することがわかっています。暗黙の最初の 1 を数えると、合計は 53 ビットの精度になります。したがって、長整数の場合、値は次の値より小さいと結論付けることができます。
2^53 - 1 == 9007199254740991; //ここでは 64 ビット長であると仮定していることに注意してくださいそうすれば、long->double->long 値の変換が発生したときに、この整数の精度が失われることはありません。
浮動小数点数に関しては、次のよくある質問に対する答えとなる別のポイントがあります。
リーリー
bugs.php.net でよく質問されることは言うまでもなく、同様の質問をする人がたくさんいるので、多くの学生がそのような質問をしたことがあると思います...
この理由を理解するには、まず浮動小数点数の表現 (IEEE 754) を知る必要があります。
浮動小数点数は、64 ビット長 (倍精度) を例として、1 つの符号ビット (E)、11 の指数ビット (Q)、および 52 ビットの仮数ビット (M) で表されます (合計 64 ビット)。 .
符号ビット: 最上位ビットはデータの符号を表し、0 は正の数を表し、1 は負の数を表します。
指数ビット: データの累乗を基数 2 で表し、指数はオフセット コードで表されます
仮数: データの小数点以下の有効数字を示します。
ここで重要なのは、バイナリでの小数を表現する方法については、ここでは詳しく説明しません。0.58 は無限に長いということです。バイナリ表現 (以下の数字は暗黙の 1 を省略します)..
0.58 のバイナリ表現は基本的に (52 ビット): 0010100011110101110000101000111101011100001010001111
0.57 のバイナリ表現は基本的に (52 ビット): 0010001111010111000010100011110101110000101000111101
そして、これら 52 ビットだけで計算すると、2 つの 2 進数は次のようになります:
0.58 -> 0.57999999999999996
0.57 -> 0.56999999999999995
具体的な浮動小数点の乗算 0.58 * 100 については詳しくは考えませんが、興味のある方は暗算で見てみてください... 0.58 * 100 = 57.999999999
それでは、計算すると自然に57になります…
この問題の重要なポイントは、「一見有限に見える 10 進数は、コンピューターの 2 進数表現では実際には無限である」ということです。
だから、これはもう PHP のバグだと思わないでください。これが実際のバグです…