この記事では主にPHP浮動小数点数について知っておくべき知識を紹介します。 この記事では、PHP浮動小数点数、PHP数値の重要な値、精度の損失などについて説明します。参考にしてみてください
PHP は弱い型指定言語であり、そのような機能では必然的にシームレスで透過的な暗黙的な型変換が必要になります。PHP は内部で zval を使用して、任意の型の値を格納します (例として 5.2)。
コードは以下の通りです:struct _zval_struct {
/* 可変情報 */
zvalue_value 値 /* 値 */
zend_uint refcount;
zend_uchar 型; /* アクティブ型 */
zend_uchar is_ref;
};
上記の構造で、実際に値そのものを格納するのは zvalue_value ユニオンです:
コードは以下の通りです:
typedef Union _zvalue_value {
long lval /* 長い値 */
double dval /* double 値 */
構造体{
Char *val;
int len;
}str;
HashTable *ht; /* ハッシュテーブル値 */
zend_object_value obj;
} zvalue_value;
今日のトピックでは、そのうちの 2 つ、lval と dval にのみ焦点を当てます。long lval はコンパイラと OS のワード長に応じて不確実な長さを持ち、Double dval (倍精度) であることを認識する必要があります。 IEEE 754 によって指定されており、固定長であり、64 ビットである必要があります。
これにより、一部の PHP コードは「プラットフォームに依存しない」ものになるため、以下の説明では、特に指定がない限り、long が 64 ビットであることを前提としています。
ここでは IEEE 754 の浮動小数点カウント方法を引用しません。興味があれば、ご自身で確認してください。重要な点は、二重仮数が 1 つの隠れた有効ビットをカウントすることです。合計は 53 ビットです。
ここで、非常に興味深い疑問が生じます。例として C コードを使用してみましょう (long が 64 ビットであると仮定します)。
コードは以下の通りです:long a = x;
assert(a == (long)(double)a);
すみません、a の値がどの範囲内にある場合、上記のコードは成功すると言えますか? (答えは記事の最後に残しておきます)
それでは、本題に戻りましょう。PHP は、スクリプトを実行する前に、まずスクリプトを読み取り、スクリプトを分析する必要があります。たとえば、次のスクリプトの場合は、次のとおりです。
コードは以下の通りです:
$a = 9223372036854775807; //64ビット符号付き数値の最大値
$b = 9223372036854775808; //最大値+1
;var_dump($a);
var_dump($b);
出力:
コードは以下の通りです:
int(9223372036854775807)
float(9.22337203685E+18)
つまり、字句解析の段階で、PHPはリテラル値が現在のシステムの長いテーブル値の範囲を超えているかどうかを判断し、超えていない場合はlvalを使用して保存し、それ以外の場合はzvalを使用します。 dval は、zval IS_FLOAT を意味します。
精度が失われる可能性があるため、最大の整数値より大きい値には注意する必要があります。
コードは以下の通りです:
$a = 9223372036854775807;
$b = 9223372036854775808;
var_dump($a === ($b - 1));
出力は false です。
冒頭の議論の続きですが、前述したように、PHP の整数は 32 ビットまたは 64 ビットの場合があるため、64 ビットで正常に実行できる一部のコードは、目に見えない型変換、精度の影響を受ける可能性があると判断されます。損失が発生し、コードが 32 ビット システムで適切に実行されなくなります。
したがって、この重要な値には注意する必要があります。幸いなことに、この重要な値は PHP で定義されています。
コードは以下の通りです:
エコー PHP_INT_MAX;
?>
もちろん、安全のために、大きな整数を格納するには文字列を使用し、計算を実行するにはbcmathなどの数学関数ライブラリを使用する必要があります。
さらに、私たちを混乱させるもう 1 つの重要な設定があります。この設定は php.precision です。この設定は、PHP が float 値を出力するときに出力する有効桁数を決定します。
最後に、上で挙げた質問を振り返ってみましょう。つまり、float に変換してから long に戻すときに精度の低下がないよう、long 整数の最大値はいくらですか?たとえば、整数の場合、そのバイナリ表現は 101 であることがわかっています。次に、2 ビットを右シフトして 1.01 にし、上位の暗黙的な有効ビット 1 を破棄して、バイナリ値 5 を取得します。二重に:
コードは以下の通りです:
0/*符号ビット*/ 10000000001/*指数ビット*/ 01000000000000000000000000000000000000000000000000
5のバイナリ表現は、損失なく仮数部に格納されます。この場合、doubleからlongに変換するときに精度が失われることはありません。
double は仮数を表すために 52 ビットを使用することがわかっています。暗黙の最初の 1 を数えると、合計は 53 ビットの精度になります。したがって、long 整数の値が次の値より小さいと結論付けることができます。
コードは以下の通りです:2^53 - 1 == 9007199254740991; //ここでは 64 ビット長であると仮定していることに注意してください
そうすると、この整数は、long->double->long 値の変換が発生したときに精度が失われることはありません