小数点を含む数値
- 解説
- 1. 小数とは
- 2. 誤差と精度
- 3. 浮動小数点数型:float型
- 4. 浮動小数点数型の問題点
- 5. 任意精度数学関数を使おう
- 6. このページで学習した型
解説
1. 小数とは
小数とは小数点を含む数値のことです。身近な小数には、円周率「3.14」や税込計算をする際の「1.08」などがあります。
割り算の結果では、小数点以下に同じ数字が無限に続く無限小数や、同じパターンが無限に続く循環小数という割り切れない小数が発生することがあります。その場合、どこかの桁で切り捨て/切り上げ/四捨五入を行う必要があり、これを丸めると言います。
この際、丸める桁までの桁数が多ければ多いほど本当の結果に近づくことになります。では、どのくらいの桁数を確保すればよいのかを考えてみましょう。
2. 誤差と精度
例えば長さ20cmのケーキを3等分する場合を考えてみましょう。
計算上は「20cm ÷ 3 = 6.66666…cm」ですが、友だちと食べるだけなので、小数点のすぐ右の位(小数第一位)で四捨五入をして整数に丸め、両端から「7cm」ずつ切ることにました。
ここで、本来の値との差分である「7cm - 6.66666…cm = 0.33333…cm」を誤差と呼びます。また、「四捨五入した桁までの桁数」のことを精度と言います。
最初に行った計算の精度が低かったために大きな誤差を生み、それが積み上がった結果、真ん中のケーキだけ6cmとなってしまいました!とはいっても、このケースでは切った人が我慢すれば良い程度のことであり、許容できる誤差であったと言えます。
お店で出すケーキならば、もっと高い精度で計算する必要があります。小数第二位で四捨五入を行うと、誤差は「6.7cm - 6.66666…cm = 0.03333…cm」となり、10分の1に抑えることができました。この誤差であれば、お店もお客さんも満足できそうです。
しかし、この精度で200cmのケーキを30個に切り分ける場合には、6.7cmで29個切った後、結局最後の1個は5.7cmとなってしまいます。
精密機器で誤差「6.6667cm - 6.66666…cm = 0.00003…cm」という高い精度で切り分けることも可能ですが、今度はそのコスト(金額や切り分けるスピード)を許容できません。
このように、許容できる誤差=必要な精度は目的によって異なり、かつコスト(データ量と計算速度)とのバランスも考慮する必要があるのです。
小数と、その計算において発生し得る誤差と、それをコントロールする精度について少し理解を深めたところで、PHPにおける小数の管理について学習しましょう。
3. 浮動小数点数型:float型
変数に「小数」を代入して使う場合には、変数を「float」型として宣言します。
値は小数点にピリオド(.)を使用し、クォーテーションなどでは括りません。
static function practiceFloat()
{
$floatValue = 3.14;
Logger::echo($floatValue);
}
「float」の語源は「floating point number.(浮動小数点数)」であり、フロート型・浮動小数点数型と呼びます。
浮動小数点数とは、その名の通り小数点の位置を浮動させてデータを管理するということを示しています。その仕組みは難解ですが、整数型と比べながら理解を深めてみましょう。
整数型は「0か1のビットの並び」である2進数でデータを管理していましたが、浮動小数点数型も同様に2進数で管理します。これがコンピューターにとって一番小さなデータ量で高速に扱えるからです。
10進数では数字を0~9までの10個使えるため、9の次で繰り上がりとなり、「1の位」「10の位」「100の位」と10倍毎に桁が増える・・・ということは、言うまでもありませんね。
2進数では数字を0と1の2個しか使えないため、1の次にもう繰り上がりとなり、「1の位」「2の位」「4の位」「8の位」と2倍毎に桁が増えるのでした。わからなければ、前のページに戻ってみましょう!
小数の場合も同じで、10進数で10分の1毎に小数点以下の桁が増えるように、2進数も2分の1毎に小数点以下の桁が増えます。
2進数 | 10進数 |
---|---|
1 | 1 |
0.1(1の1/2) | 0.5(1の1/2) |
0.01(0.1の1/2) | 0.25(0.5の1/2) |
0.001(0.01の1/2) | 0.125(0.25の1/2) |
「10進数の小数を表現できないから2進数に変換」したのにまた小数・・・と混乱しますが、一旦このルールで「10進数の11.625」を変換してみましょう。
このようにしてしまえば、小数点の位置さえどうにかすれば、整数と同じようにビットのみで表せそうです。小数点の位置は一番左の「1」の右になるように移動させます。「1011.101」であれば「1.011101」のように左へ3回移動(+3)します。10進数の0.625は「0.101」となりますので、その場合には「1.01」のように右へ1回移動(-1)します。
その時に移動した回数もまた整数でありビット変換できますので、これで、すべてビットのみで表せるようになりました。小数点の位置を浮動させてデータを管理するという意味がなんとなく分かったでしょうか。
下図が実際のfloatのデータレイアウトです。64ビットの中を符号部/指数部/仮数部に分け、
変換と小数点の移動が済んだ2進数部分を仮数部で、小数点の移動回数を指数部で管理することで、ビットのみでの管理を実現しているのです。
※プラットフォームによっては、32ビットのデータで表現されます。
4. 浮動小数点数型の問題点
下記の例を確認してみましょう。
static function practiceFloat()
{
$floatValue1 = 0.6;
$floatValue2 = 0.7;
Logger::echo($floatValue1);
Logger::echo($floatValue2);
Logger::echo($floatValue1 + $floatValue2); // 1.3
Logger::echo(($floatValue1 + $floatValue2) * 10); // 1.3 * 10
Logger::echo(floor(($floatValue1 + $floatValue2) * 10)); // 端数を切り捨てる
}
[2020/12/31 12:34:56] 0.6 // 間違いなく0.6(に見える)
[2020/12/31 12:34:56] 0.7 // 間違いなく0.7(に見える)
[2020/12/31 12:34:56] 1.3 // 予測通りの加算結果(に見える)
[2020/12/31 12:34:56] 13 // 予測通りの加算結果(に見える)
[2020/12/31 12:34:56] 12 // 端数を切り捨てると結果が変わる
「0.6 + 0.7」の加算結果である「1.3」を「10倍」して、端数を切り捨てる。すると予想に反して結果が12となってしまいます。
「10進数の0.5」の2進数変換結果は「0.1」であり問題ありませんが、「10進数の0.6」の場合は 「0.10011001100110011…」、「10進数の0.7」の場合も「0.10110011001100110…」と、結局割り切れない循環小数になってしまい、内部的には下記のような値になっています。
$a = number_format(0.6, 30); // 少数を30桁まで表示
$b = number_format(0.7, 30); // 少数を30桁まで表示
Logger::echo($a); // 0.599999999999999977795539507497
Logger::echo($b); // 0.699999999999999955591079014994
Logger::echo(number_format($a + $b, 30)); // 1.299999999999999822364316059975
そして、PHPは計算結果を評価する際に丸め処理をおこなっているので、そのまま加算結果を表示すると「13」が表示されます。そして、評価される前に端数の切り捨て処理をおこなうことで「12」が出力されるのです。
浮動小数点数型は、高度な計算を高速に行うといった用途には適しているのですが、通常の用途では思いがけない計算結果となってしまうなどのミスにつながります。
5. 任意精度数学関数を使おう
例えばゲームを作っていて「ジャンプしているときの0.1秒ごとの高さ」のような計算をする場合には、精度よりもリアルタイムに計算できることを優先し、floatを使用しましょう。
しかしそれ以外の場合は、基本的には任意精度数学関数(BCMath)や、GMPといった関数を使用して計算するようにしましょう。
static function Bcmath() {
$floatValue = 0.6 + 0.7;
$floatValue = floor($floatValue * 10);
Logger::echo($floatValue); // 12
$bcmathValue = bcadd(0.6, 0.7, 1); // 小数点以下1桁になるように丸める
$bcmathValue = floor($bcmathValue * 10);
Logger::echo($bcmathValue); // 13
}
「bcadd()」の括弧の中(一番右)で、計算結果の小数点以下の桁数を指定することができます。
任意精度数学関数を使ったからと言って、無限小数や循環小数、小数点以下の桁数が多い小数が発生しないわけではありません。しかし、どの桁でどのように丸めるかを完全に使う側が制御できるようになっており、計算結果を保証することが可能です。
6. このページで学習した型
型 | 読み方 呼び方 | 用途 | データ 範囲 |
---|---|---|---|
float | フロート型・ 浮動小数点数型 | 小数計算 | ±1.8×10の308乗 (±3.4×10の38乗) |