PHPの最新状況:PHPの最新状況: PHP 8.4の開発が後半に (第39回)

PHP

廣川類

 9月も半ばを過ぎ、涼しい日々も増えてきました。引き続き、11月のリリースに向けてPHPバージョン8.4の開発が行われており、β版のリリースが開始されました。今回の記事では、PHP 8.4で強化される任意精度演算などについて紹介します。

PHP 8.4の開発状況

PHP 8.4の開発は11月21日の一般公開に向けて順調に進んでおり、計画通り8月13日にフィーチャーフリーズが宣言されています。この宣言の後は、新機能の追加は原則として行われず、バグ修正のみが行われることになります。

また、8月15日からβ版のリリースが開始されています。8月15日にβ3、8月29日にβ4がリリースされ、9月12日に最後のβ版リリースが行われる予定です。この後、リリース候補版が合計4回リリースされ、11月21日に正式リリースの予定です。

 以下、前回までに続きPHP 8.4における変更点を紹介します。

廃止対象を指定するアトリビュートの追加

PHPの内部関数では、関数をコールすると廃止対象を示す警告(Deprecated)が発生するケースがあります。PHP 8.4では、このような廃止対象を示す警告を発生する機能をユーザが定義した関数やクラスなどにも追加できるようになります。 以下のようにアトリビュート(#[\Deprecated])を付与することで、指定された対象を実行した時点でDeprecatedが発生します。

<?php
#[\Deprecated]
function test() {
}

test(); // Deprecated: Function test() is deprecated in ...

この場合、test関数の上に#[\Deprecated]アトリビュートが指定されているため、この関数を実行すると、Deprecatedが発生します。

 クラス内部の定数やメソッドに指定することも可能です。

class Foo {
    #[\Deprecated]
    public const OLD = 'boo';

    #[\Deprecated("use test2() instead")]
    function test() {}
}

$foo = new Foo();
$foo::OLD; // Deprecated: Constant Foo::OLD is deprecated in ...
$foo->test(); // Deprecated: Method Foo::test() is deprecated, use test2() instead in ...

#[\Deprecated]には、「use test2() instead」や 「since : “2024-09-30”」のような付加的な情報を指定することも可能です。これらの文字列は、上記のように Deprecatedを発生した際に、表示されます。これらの情報は、代替手段や、いつから廃止対象となったのかを知る手段として、有用です。

Reflection機能を利用して、テストを行うことも可能です。

$r = new ReflectionFunction('test');
var_dump($r->isDeprecated()); // bool(true)

この例では、前記のtest関数を調査し、isDeprecatedメソッドにより、Deprecatedかどうかを確認しています。ここでは、test関数に #[\Deprecated] アトリビュートが指定されているため、trueが返されます。

 従来、trigger_error() で E_USER_DEPRECATED を発生させるコードを記述することは可能でしたが、特定のメソッドや定数などを廃止対象として指定することはできませんでした。

 新機能導入後も後方互換性の維持のために古いメソッドなどを一定期間維持するケースはありえますが、この #[\Deprecated] アトリビュートを活用することで、コードのメンテナンス性の向上が期待できます。

BCMathの強化

 PHPでは,任意精度演算を行う BCMathエクステンションをサポートしており、(メモリの制約がなければ,)最大2147483647桁の演算を行うことができます。このような大きな桁の数は,通常の整数や浮動小数点演算で正確に表すことはできません。BCMathでは,数値を(桁数に制約がない)文字列で表すことが特徴です。ここで、BCMathの引数にfloatを指定することも可能ですが、内部的に文字列に変換する際に精度が失われる可能性があります。引数の数字は、必ず文字列で指定するよう注意してください。

 BCMathエクステンションにおける機能強化は、以下の2点です。

  • 丸め処理を行う関数(bcround、bcfloor、bcceil)の追加
  • オブジェクト処理、演算子オーバーロードのサポート

早速、例を見てみましょう。

$v = bcround('0.285', 2, RoundingMode::HalfAwayFromZero);
var_dump($v);

bcround()関数により、数値を小数点以下2桁で丸めています。第三引数に丸めモードを指定できます。ここでは、四捨五入を表すクラス定数 RoundingMode::HalfAwayFromZero を指定しています。

出力は以下のようになります。

string(4) "0.29"

この関数は、通常のfloat数を扱うround関数と類似しています。

var_dump(round(0.285, 2, PHP_ROUND_HALF_UP));

この例の出力は以下となり、明示的なbcroundとの違いは戻り値の型となります。

float(0.29)

同様にfloor、ceilに相当する関数として、bcfloor、bcceil関数も追加されています。

var_dump(bcfloor('13.0915'));
var_dump(bcceil('24.00001'));

出力は以下のようになります。

string(2) "13"
string(2) "25"

同じ処理は、bcround関数により記述することができます。

var_dump(bcround('13.0915', 0, RoundingMode::NegativeInfinity));
var_dump(bcround('24.00001', 0, RoundingMode::PositiveInfinity));

クラス定数RoundingMode::NegativeInfinityは、負の無限大方向、RoundingMode::PositivieInfinityは、正の無限大方向への丸めを表します。

以下の8つの丸めモードがサポートされています。

クラス定数PHP定数動作
RoundingMode::HalfAwayFromZeroPHP_ROUND_HALF_UP四捨五入
RoundingMode::HalfTowardsZeroPHP_ROUND_HALF_DOWN五捨六入
RoundingMode::HalfEvenPHP_ROUND_HALF_EVEN四捨六入(5は偶数になる場合のみ切り捨て)
RoundingMode::HalfOddPHP_ROUND_HALF_ODD四捨六入(5は奇数になる場合のみ切り捨て)
RoundingMode::TowardsZeroPHP_ROUND_AWAY_TOWARD_ZEROゼロ方向に丸め
RoundingMode::AwayFromZeroPHP_ROUND_AWAY_FROM_ZEROゼロと逆方向に丸め
RoundingMode::NegativeInfinityPHP_ROUND_FLOOR切り捨て
RoundingMode::PositivieInfinityPHP_ROUND_CEILING切り上げ

いわゆる四捨六入の処理を行うHalfEvenによる丸め処理の例を見てみましょう。

var_dump(bcround(0.04, 1, RoundingMode::HalfEven)); // string(2) "0.0"
var_dump(bcround(0.05, 1, RoundingMode::HalfEven)); // string(2) "0.0"
var_dump(bcround(0.06, 1, RoundingMode::HalfEven)); // string(2) "0.1"
var_dump(bcround(0.15, 1, RoundingMode::HalfEven)); // string(2) "0.2"

5については、HalfEvenとHalfOddで処理が異なります。HalfEvenを指定した場合、偶数になる場合は切捨て、そうでない場合は切上げの処理が行われます。この例で、0.05と0.15で丸めの方向が異なるのはそのためです。

 次に、オブジェクトについて説明します。PHP 8.4では、BCMathエクステンションにオブジェクトによるアクセスが追加され、演算子のオーバーロードもサポートされます。

以下のように、名前空間 BCMathのNumberクラスにより、数値を保持するオブジェクトを生成します。

use BCMath\Number;
$num1 = new Number('2');
$num2 = new Number('4');

このオブジェクトは、比較を含む演算が可能です。

var_dump($num1 > $num2); // false
$result = $num1 + $num2;
var_dump($result->value); // string(1) "6"

valueプロパティにより値を文字列として取得できます。 

文字列出力(__toString)も定義されており、以下のようにシンプルに記述することも可能です。

echo $num1 + $num2; // '6'

その他の四則演算や剰余、指数演算のオーバーロードもサポートされています。

var_dump(($num1 - $num2)->value); // string(2) "-2"
var_dump(($num1 / $num2)->value); // string(3) "0.5"
var_dump(($num1 % $num2)->value); // string(1) "2"
var_dump(($num1 * $num2)->value); // string(1) "8"
var_dump(($num1 ** $num2)->value); // string(2) "16"

演算子のオーバーロードではなく、オブジェクトのメソッドを利用することも可能です。加算の場合は、addを利用します。

$result = $num1->add($num2);
var_dump($result->value); // string(1) "6"

その他、減算はsub、乗算はmul、除算はdiv、剰余は modを使用します。

平方根を求めることも可能です。sqrtメソッドの引数に小数点以下の有効桁数を指定することができます。

var_dump($num1->sqrt(3)->value); // string(5) "1.414"

各演算の有効桁数を第二引数に指定することができます。

$pi = new BcMath\Number('3.14159');
$pi2 = new BcMath\Number('6.28');
var_dump($pi->div($pi2, 2)->value); // string(4) "0.50"

この例では、除算の結果を有効数字2桁で計算しています。

roundメソッドによる丸め桁数の指定も可能です。ただし、以下のように演算の結果に適用すると、演算そのものの桁数を指定したことにならず、表示結果が上の例と異なる(0.50ではなく0.5)ことに注意してください。

var_dump($pi->div($pi2)->round(2)->value); // string(3) "0.5"

BCMathには、この他、除算の商と余りを求める bcdivmod関数を追加する提案も行われており、PHP 8.4で追加される可能性があります。

 今回は、前回までに続き、11月のリリースに向けてβ版のリリースが開始された PHP 8.4の開発状況について紹介し、BCMathエクステンションの機能強化を中心に紹介しました。次回以降もPHP 8.4のその他の変更点について紹介していきます。

関連記事

Webサイト運用の課題解決事例100選 プレゼント

Webサイト運用の課題を弊社プロダクトで解決したお客様にインタビュー取材を行い、100の事例を108ページに及ぶ事例集としてまとめました。

・100事例のWebサイト運用の課題と解決手法、解決後の直接、間接的効果がわかる

・情報通信、 IT、金融、メディア、官公庁、学校などの業種ごとに事例を確認できる

・特集では1社の事例を3ページに渡り背景からシステム構成まで詳解