LoginSignup
2
1

More than 1 year has passed since last update.

[PHP]int型の限界値越えるとfloat型に勝手に変換される

Last updated at Posted at 2022-03-12

はじめに

PHPでテストをしてて「ん?」っと若干つまづいたコトがあったので、メモ

※結論はタイトルの通り

前提

  • PHP 8.0.1
  • Laravel 8.0.2

やりたいこと

以下のメソッド(Laravelのアクセサ)についてUnitテストしたい

App/Models/UserAttributes.php
/**
 * payment_amount_sumの整形アクセサ.
 * @return string
 */
public function getPaymentAmountSumFormatAttribute()
{
    return number_format($this->payment_amount_sum).'円';
}

UserAttributeモデルのpayment_amount_sumプロパティをただ「*,***円」の形に整形するというメソッド

やったこと

以下の定数とテストメソッドを書いて実行した

App/Tests/Units/UserAttributes.php
/**
 * getPaymentAmountSumFormatAttributeメソッドのテスト.
 *
 * @param int $paymentAmountSum
 * @dataProvider paymentAmountSumDataProvider
 * @return void
 */
public function testGetPaymentAmountSumFormatAttribute(int $paymentAmountSum)
{
    $userAttribute = new UserAttribute();
    $userAttribute->payment_amount_sum = $paymentAmountSum;
    $this->assertSame(number_format($paymentAmountSum).'円', $userAttribute->payment_amount_sum_format);
}

/**
 * payment_payment_sumのデータプロバイダー
 * @return array
 */
public function paymentAmountSumDataProvider()
{
    return [
        'MIN_PAYMENT_AMOUNT_SUM' => [0],
        'MAX_PAYMENT_AMOUNT_SUM' => [18446744073709551615],
    ];
}
  • 引数に対してint型の指定
  • データプロバイダpaymentAmountSumDataProviderで引数を指定
  • 境界値テストに従ってテストしたかったので、テストの最小値を0、最大値を18446744073709551615にした

尚、payment_amount_sumプロパティ自体がDBに保存されるようになっており、カラムの型はbigint unsigned

bigint unsignedは0 ~ 18,446,744,073,709,551,615(約1845京)までの整数が登録可能なので、この最小値、最大値にした、というわけ。

unsignedTinyInteger   : 0                         255
unsignedSmallInteger  : 0                      65,535
unsignedMediumInteger : 0                  16,777,215
unsignedInteger       : 0               4,294,967,295
unsignedBigInteger    : 0  18,446,744,073,709,551,615

起こったこと

いざテスト実行

php artisan test tests/Unit/Models/UserAttributeTest.php

が、しかしエラーが発生

Argument #1 ($paymentAmountSum) must be of type int, float given,

「float型が入っている?どこにも小数点なんて使っていないですが?」となった

で引数についてデバッグしてみると最大値のMAX_PAYMENT_AMOUNT_SUMに18446744073709551615が表示されなかった...
(dumpしても何も表示されなかった...)

原因

で、調べてみるとPHPドキュメントに答えがあった

「『PHPが指定する整数値』を超えると自動的にfloat型に変換する」というPHPの仕様だった

公式DOCサンプルコードそのまま↓

<?php
$large_number = 9223372036854775807;
var_dump($large_number);                     // int(9223372036854775807)

$large_number = 9223372036854775808;
var_dump($large_number);                     // float(9.2233720368548E+18)

$million = 1000000;
$large_number =  50000000000000 * $million;
var_dump($large_number);                     // float(5.0E+19)
?>

尚、上記の「PHPの指定する整数値」というのは組み込み定数であるPHP_INT_MAXのこと

>>> PHP_INT_MAX
=> 9223372036854775807

=> 9,223,372,036,854,775,807 = 約922京

※扱っているのが32 ビットシステムなのか64ビットシステムなのかで自動でfloatにする整数値は異なるが、僕の場合は64ビットだった

対処法

さて、どうしようか、とやってみたことが2つ

その1:無理やりintに変換できないかトライ...×

bigint unsigned(=約1,845京)をint型にできないか試した

(int)18446744073709551615
=>float(1.8446744073709551615)

(int)'18446744073709551615'
=> 9223372036854775807 // (=PHP_INT_MAXの値。これはこれで興味深い)

が、intへの変換は無理だった。。。

その2:PHP_INT_MAXの値をテストの最大値とした...〇

そもそも約1845京もの大金を支払う人なんていないでしょ、という一般常識に立ち返った上で、PHP_INT_MAXの値をテストの最大値に変更して終了。
PHP_INT_MAXの約922京で十分すぎる。。。

public function paymentAmountSumDataProvider()
{
    return [
        'MIN_PAYMENT_AMOUNT_SUM' => [0],
        'MAX_PAYMENT_AMOUNT_SUM' => [PHP_INT_MAX], // 9223372036854775807
    ];
}

おわりに

  • 普段はフレームワークLaravelによる開発で、なかなか触れることができないPHPの言語仕様に触れることができいい機会だった
  • ドキュメント読めばすぐわかる内容ですが、今回のような暗黙的な型変換は結構つまづきやすいポイントだなーと思いメモ
  • 僕がアサインしているプロジェクトのように引数への型指定の徹底やphpstanの静的解析をしている場合はこうやって気づけるんですが、そうではないプロジェクトとかだとfloat型のままテストはOKになってしまうので、下手したら正常にテストができていないままマージされてしまうことを考えると非常にこわいな、、、と思った次第

最後までお読みいただきありがとうございました!

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1