GMPインスタンスは演算子オーバーロードされているため、普通の数値と混ぜて計算が可能です。
echo gmp_init("123") + 456; // 579
echo array_sum([gmp_init("123"), 456]); // 456 ← ???
なぜかarray_sumやarray_productではGMPが正しく計算されてくれません。
実はこれらの関数、算術演算子に処理を渡しているわけではなく独自の演算を行っています。
そのせいで異なる計算結果となり、これではわかりにくくて困ってしまいますね。
ということで、array_sum|product
の計算方法を算術演算子と同じにしようというRFCが提出されました。
以下は該当のRFC、Saner array_(sum|product)()の紹介です。
Saner array_(sum|product)()
Introduction
PHPの標準関数array_sumおよびarray_productは、入力の配列を演算して折り畳む高階関数です。
PHPはまた、任意の演算で配列の折り畳みを行うarray_reduceをサポートしています。
そのため、先の2関数をユーザランドで実装することも可能です。
// $output = array_sum($input)
$output = array_reduce($input, fn($carry, $value) => $carry + $value, 0);
// $output = array_product($input)
$output = array_reduce($input, fn($carry, $value) => $carry * $value, 1);
入力値の型がint
とfloat
だけであれば、組込関数とユーザランド実装は完全に同じ動作になります。
しかし、引数に他の型が混ざっていた場合は、異なる挙動になってしまいます。
そこでまず、array_reduce()
に算術演算子を与えたときの挙動を調べ、次にarray_sum()
とarray_product()
の現在の挙動を調べ、最後に不一致を修正するための変更を提案します。
Behaviour of the array_reduce() variants
算術演算子は数値ではない型の型変換を行いますが、マニュアルでは以下のように記載されています。
In this context if either operand is a float (or not interpretable as an int), both operands are interpreted as floats, and the result will be a float. Otherwise, the operands will be interpreted as ints, and the result will also be an int. As of PHP 8.0.0, if one of the operands cannot be interpreted a TypeError is thrown.
いずれかのオペランドがfloatであれば両方をfloatとして解釈し、返り値もfloatになります。
それ以外であればオペランドをintとして解釈し、返り値もintになります。
PHP8.0.0以降は、オペランドが解釈できないときにTypeErrorをthrowします。
intとfloat以外の型は、以下のようになります。
・null型は0。
・bool型は、falseは0、trueは1。
・string型は、数値型文字列であればintかfloatになる。
・object型は、do_operationハンドラが実装されていればそれを使う。cast_objectハンドラが実装されていればそれを使う。
・それ以外はすべてTypeError。
Ecample
以下はGMPによる例です。
$input = [gmp_init(6), gmp_init(3), gmp_init(5)];
// ユーザランド実装
array_reduce($input, fn($carry, $value) => $carry + $value, 0); // object(GMP)#5 (1) { ["num"]=> "14" }
array_reduce($input, fn($carry, $value) => $carry * $value, 1); // object(GMP)#5 (1) { ["num"]=> "90" }
// 正しくない例
$input = [true, STDERR, new stdClass(), [], gmp_init(6)];
array_reduce($input, fn($carry, $value) => $carry + $value, 0); // TypeError
array_reduce($input, fn($carry, $value) => $carry * $value, 1); // TypeError
以下はdo_operationハンドラを実装しているFFI\CData
型の例です。
$x = FFI::new("int[2]");
$x[0] = 10;
$x[1] = 25;
$input = [$x, 1];
$output = array_reduce($input, fn($carry, $value) => $carry + $value, 0);
var_dump($output); // object(FFI\CData:int32_t*)#4 (1) { [0] => 25 }
Current behaviour of the array_sum() and array_product() functions
関数の動作は比較的単純です。
まずarray_sum()
は返り値を0に、array_product()
は1にします。
その後配列を順に処理し、配列かオブジェクトだったら無視し、それ以外なら数値にキャストして戻り値に加えます。
このように、リソースや配列、非数値文字列、加算乗算ができないオブジェクトなどの型を与えた場合でもTypeErrorが発生しません。
通常の算術演算においてはPHP8.0.0でTypeErrorが発生するようになったため、この動作と矛盾しています。
Ecample
以下はGMPによる例です。
$input = [gmp_init(6), gmp_init(3), gmp_init(5)];
$output = array_sum($input); // int(0)
$output = array_product($input); // int(1)
// 正しくない例
$input = [true, STDERR, new stdClass(), [], gmp_init(6)];
$output = array_sum($input); // 4
$output = array_product($input); // 3
以下はFFIの例です。
$x = FFI::new("int[2]");
$x[0] = 10;
$x[1] = 25;
$input = [$x, 1];
$output = array_sum($input);
var_dump($output); // int(1)
Proposal
このRFCでは、array_sum()
とarray_product()
について、通常の算術演算と同じ挙動を適用します。
ただし、正しくない値を与えた場合には例外ではなくE_WARNINGを発行し、現在の動作となるべく互換を維持します。
ひとつ注意点として、オブジェクトの加算乗算については数値キャストします。
従って、上記の例は、今後は以下のような動作になります。
GMPの例。
$input = [gmp_init(6), gmp_init(3), gmp_init(5)];
$output = array_sum($input); // 14
$output = array_product($input); // 90
正しくない例。
$input = [true, STDERR, new stdClass(), [], gmp_init(6)];
$output = array_sum($input); // 10
// Warning: array_sum(): Addition is not supported on type resource in %s on line %d
// Warning: array_sum(): Addition is not supported on type stdClass in %s on line %d
// Warning: array_sum(): Addition is not supported on type array in %s on line %d
$output = array_product($input); // 18
// Warning: array_product(): Multiplication is not supported on type resource in %s on line %d
// Warning: array_product(): Multiplication is not supported on type stdClass in %s on line %d
// Warning: array_product(): Multiplication is not supported on type array in %s on line %d
FFIの例。
$x = FFI::new("int[2]");
$x[0] = 10;
$x[1] = 25;
$input = [$x, 1];
$output = array_sum($input); // 1
// Warning: array_sum(): Addition is not supported on type FFI\CData in %s on line %d
Backward Incompatible Changes
互換性のない変更点。
不正な型について、E_WARNINGが出るようになります。
オブジェクトを含む場合、異なる結果になる可能性があります。
$a = [10, 15.6, gmp_init(25)];
var_dump(array_sum($a));
// PHP8.2まで 25.6
// PHP8.3以降 50.6
Proposed PHP Version
PHP8.3
Proposed Voting Choices
投票者の2/3の賛成で受理されます。
投票期間は2023/02/20から2023/03/06です。
本RFCは、賛成27反対0の全会一致で可決されました。
Implementation
感想
ということで、array_sum|product
が今後はより便利に、というか正常になります。
ただまあ、ひとつ言いたいこととしては、そもそも型を混ぜて演算するんじゃない。