77809


静的プロパティの学習をしていました。  
遊び心なのですが、サイトを見て円周率 $pi に30桁ほど代入してみました。

すると返って来た結果は小数点以下13桁で止まっています。  
コンピュータは無限ではなくどこかで丸められるのはわかっています。  
ブロックアウト(`//`)してある部分が自分の予想でした。

計算結果ならいざ知らず、代入した値もどこかで丸められるのですか？

少し気になります。  
ご存じの方いらっしゃいましたら教えて下さい。  
よろしくお願いいたします。

(Area1.php)

[code]

    <?php
    class Area {
      public static $pi = 3.141592653589793238462643383279;
    
      public static function circle (float $radius):float{
        return pow($radius, 2) * self::$pi;
      }
    }
    ?>
    
[/code]

(Static1.php)

[code]

    <?php
    require_once('Area1.php');
    
    print '円周率:'.Area::$pi.'<br>';
    //円周率:3.141592653589793238462643383279;
    print '円の面積:'.Area::circle(10).'cm^2';
    // 円の面積:314.159…cm^2
    ?>
    
[/code]

<表示結果>

[code]

    円周率:3.1415926535898
    円の面積:314.15926535898cm^2
    
[/code]


CC BY-SA 4.0
2021-06-26T07:33:53.387
77799
2021-06-26T17:54:14.850
2021-06-26T07:39:50.130
3060
42150
question
php
PHP で小数点以下の桁数はどこで丸められるのですか？


PHPでは、小数は「浮動小数点」で表されます。  
コード内でも`float`型を使われているので、それはご存じだと思います。  
PHPにおける浮動小数点数の扱いの詳細はマニュアルの以下のページに記載されています。  
<https://www.php.net/manual/ja/language.types.float.php>  
浮動小数点数の仕様そのものについてはWikipediaをはじめ詳解しているドキュメントがあるのでそちらも合わせてお読みください。  
<https://ja.wikipedia.org/wiki/%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0>  
<https://ja.wikipedia.org/wiki/IEEE_754>  
本件ではこのような情報は質問の本質ではないと思われるので詳細解説は割愛します。

質問は「小数を含む数値がどのタイミングで丸められるか」と言うのが本題と読み取ります。  
丸められるタイミングは、「式として評価(evaluate)」された時です。

例えば、

[code]

    $pi = 3.141592653589793238462643383279;
    
[/code]

という式が与えられたとします。本文のコードそのままですね。  
変数に値を代入する際、まず `=` の右辺の式を評価します。  
つまり、`3.141592653589793238462643383279` という数値リテラルがまず評価されます。  
この数値リテラルは小数を表すのでfloat型に自動的に変換されます。  
その結果、`3.1415926535898` になります（floatの内部表現を10進数に変換して表示したものなので、内部の数値を正確には表していません）

右辺式の評価が終了したので、評価結果の値を左辺の変数へ代入します。  
この時、`$pi`と先ほどの左辺の評価結果は同じ値になります。

### 結論

> 計算結果ならいざ知らず、代入した値もどこかで丸められるのですか？

という質問に対しては、  
「代入する前に既に丸められている」  
が回答となります。それは数値リテラルを評価する際にfloat型に収まるよう丸められるからです。そして、式は評価されるたびに結果型に適合するように丸められます。


CC BY-SA 4.0
2021-06-26T08:55:45.283
77802
2021-06-26T08:55:45.283
46944
77799
answer


Lexical analysis(字句解析)の時点で丸め処理が行われます。具体的には以下の部分です。

[php-src/Zend/zend_language_scanner.l](https://github.com/php/php-
src/blob/master/Zend/zend_language_scanner.l#L2131)

[code]

    <ST_IN_SCRIPTING>{DNUM}|{EXPONENT_DNUM} {
                       :
    
        ZVAL_DOUBLE(zendlval, zend_strtod(dnum, &end));
                       :
    
        RETURN_TOKEN_WITH_VAL(T_DNUMBER);
    }
    
[/code]

[zend_strtod()](https://github.com/php/php-
src/blob/master/Zend/zend_strtod.c#L2524)は1000行を超える長大な関数ですが、浮動小数点数(文字列)を抽出する処理は以下になります(この後に
IEEE 754 format に変換する処理が続きます)。

[zend_strtod](https://github.com/php/php-
src/blob/master/Zend/zend_strtod.c#L2609)

[code]

      for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++)
        if (nd < 9)
          y = 10*y + c - '0';
        else if (nd < DBL_DIG + 2)
          z = 10*z + c - '0';
                :
    
      if (c == '.') {
                :
    
        if (!nd) {
          for(; c == '0'; c = *++s)
            nz++;
          if (c > '0' && c <= '9') {
               :
    
            goto have_dig;
          }
          goto dig_done;
        }
        for(; c >= '0' && c <= '9'; c = *++s) {
     have_dig:
          nz++;
          if (c -= '0') {
            nf += nz;
            for(i = 1; i < nz; i++)
              if (nd++ < 9)
                y *= 10;
              else if (nd <= DBL_DIG + 2)
                z *= 10;
            if (nd++ < 9)
              y = 10*y + c;
            else if (nd <= DBL_DIG + 2)
              z = 10*z + c;
            nz = nz1 = 0;
          }
        }
      }
     dig_done:
    
[/code]

この処理部分にある `DBL_DIG` ですが、PHP の定義済み定数である `PHP_FLOAT_DIG` に割り当てられています。

[php-src/main/main.c](https://github.com/php/php-
src/blob/master/main/main.c#L2140)

[code]

      REGISTER_MAIN_LONG_CONSTANT("PHP_FLOAT_DIG", DBL_DIG, CONST_PERSISTENT | CONST_CS);
    
[/code]

[PHP: Predefined
Constants](https://www.php.net/manual/en/reserved.constants.php)

> **PHP_FLOAT_DIG** (int)
>
> Number of decimal digits that can be rounded into a float and back without
> precision loss. Available as of PHP 7.2.0.
[code]

    $ php -r 'echo PHP_FLOAT_DIG . PHP_EOL;'
    15
    
[/code]

`15` という値は IEEE 754 format(倍精度)の仮数部が 52 bits 長である事に依るものです(`log10(2^(52+1)) ≒
15.9546`)。

[code]

    echo "円周率: 3.141592653589793238462643383279\n";
    printf("円周率: %.14f\n", Area::$pi);
    printf("円周率: %.15f\n", Area::$pi);
    printf("円周率: %.16f\n", Area::$pi);
    
    // 出力結果
    円周率: 3.141592653589793238462643383279
    円周率: 3.14159265358979
    円周率: 3.141592653589793
    円周率: 3.1415926535897931
    
[/code]


CC BY-SA 4.0
2021-06-26T17:54:14.850
77809
2021-06-26T17:54:14.850
77799
answer