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

# はじめに

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京)までの整数が登録可能なので、この最小値、最大値にした、というわけ。

https://dev.mysql.com/doc/refman/5.6/ja/integer-types.html

```php
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
php artisan test tests/Unit/Models/UserAttributeTest.php
```

が、しかしエラーが発生

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

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

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

# 原因

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

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

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

```php
<?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)
?>
```

https://www.php.net/manual/ja/language.types.integer.php#language.types.integer.overflow

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

```php
>>> PHP_INT_MAX
=> 9223372036854775807

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

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

# 対処法

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

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

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

```php
(int)18446744073709551615
=>float(1.8446744073709551615)

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

```

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

## その２：PHP_INT_MAXの値をテストの最大値とした...〇

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

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

# おわりに

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

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