「数値型の計算誤差」の版間の差分

提供: AliceScript Wiki
ナビゲーションに移動 検索に移動
40行目: 40行目:
   }
   }
==一度整数にした後で変換する==
==一度整数にした後で変換する==
他には、例えば一度整数にしてから計算する方法が考えられます。次の例では、二つの数を整数にすることができる数<code>dis ** 10</code>を求めて二数を整数にした後、計算を行うことで誤差を防ぎます。ただしこの場合でも、有効数字15桁以上の数は正確に扱うことができません。
他には、例えば一度整数にしてから計算する方法も考えられます。次の例では、二つの数を整数にすることができる数<code>dis ** 10</code>を求めて二数を整数にした後、計算を行うことで誤差を防ぎます。ただしこの場合でも、有効数字15桁以上の数は正確に扱うことができません。
  import “Alice.Math”;
  import “Alice.Math”;
   
   

2022年4月12日 (火) 01:53時点における版

AliceScriptの数値型は、倍精度浮動小数点数数値型と定められており、この規格はIEEE754として標準化されています。 しかしこの数値型で小数の計算をしているとき、その計算結果が期待通りでないことがあります。この記事では、その理由について説明します。

計算誤差の例

0.1 + 0.2 == 0.3

数学では、上記の式は正しくtrueと評価されますし、ほとんどの開発者も、それが正しいことを期待します。 では、AliceScriptでこの式を評価してみます。

print(0.1 + 0.2 == 0.3);

このコードを実行すると、falseが出力されます。

理由

これらの計算誤差はバグではありません。AliceScriptの数値型のような浮動小数点数数値型は、値を2進数で格納していますが、ほとんどの10進数の小数は2進数で表すことができず、その近似値として表現されます。近似値の誤差が例に示したような計算誤差として現れます。

知的好奇心旺盛なあなたのために、もう少し説明します。例えば10進数の0.1を2進数に変換すると0.0001100110011…となり、0011が永遠と循環します。そのため0.1を倍精度浮動小数点数数値型に格納するには、適当な桁で丸める必要があります。このとき、最も近い偶数に値を丸めます。その結果として0.1は数値型では2進数で0.0001100110011001100110011001100110011001100110011001101となります。これを10進数に戻す0.1000000000000000055511151231257827021181583404541015625となり、0.1ではなくなります。

ただし整数は、有効桁数15桁の範囲内であれば、正確に格納できます。また小数であっても、k/2^n (k,nは整数)で表すこともできる小数は2進数で表現できるため、正確に格納できます。

なおこのような誤差は、AliceScript固有のものではありません。IEEE 754の2進浮動小数点形式を採用しているシステムでは、同じことが起こりえます。

回避策

許容範囲を決めて値を比較する

二つの浮動小数点数数値型数を比較するとき、例えばそれらが等しいかを調べるときに==を使うのは危険です。両者が全く等しい場合のみ等しいと判断するのではなく、「両者の差の絶対値がある程度であれば等しいと認める」など許容範囲を決めて比較する方が安全です。次の例は、許容範囲を定めt、0.000001未満の差の場合に等しいと判断します。

import “Alice.Math”;

///二つの小数が等しいと認められるかどうかを評価します
///パラメータ a:一方の値
///         b:もう一方の値
/// tolerance:許容できる誤差の最大の値(この数値は常に正)     
function NumEqual(number a,number b,number tolerance)
 {
    return (math_abs(a - b) < tolerance);
 }

var numA = 0.1 + 0.2;
var numB = 0.3;
///許容範囲
var tolerance = 0.000001;
if(NumEqual(numA,numB,tolerance))
 {
    print(“二値は等しいと認められました”);
 }

一度整数にした後で変換する

他には、例えば一度整数にしてから計算する方法も考えられます。次の例では、二つの数を整数にすることができる数dis ** 10を求めて二数を整数にした後、計算を行うことで誤差を防ぎます。ただしこの場合でも、有効数字15桁以上の数は正確に扱うことができません。

import “Alice.Math”;

///与えられた数の小数点以下の桁数を取得します
function GetDisitsUnder1(number num)
 {
    var priceString = price.ToString().TrimEnd('0');
    int index = priceString.IndexOf('.');
    if (index == -1){return 0;}
    return priceString.Substring(index + 1).Length;
 }

///二つの小数が等しいかどうかを判断します
function NumEqual(number numA,numbner numB)
 {
    //numA,numBの小数点以下の桁数(すなわち、10の何乗倍すれば整数になるか)
    var nAd = GetDisitsUnder1(numA);
    var nBd = GetDisitsUnder1(numB);
    //二つの小数の小数点以下桁数のうち多い方
    var dis = math_max(nAd,nBd);
    
    //numA,numBを整数化したもの
    var rA = numA * (dis ** 10);
    var rB = numB * (dis ** 10);
    
    return (rA == rB);
 }

///二つの小数の和を求めます
function Sum(number numA,number numB)
 {
    //numA,numBの小数点以下の桁数(すなわち、10の何乗倍すれば整数になるか)
    var nAd = GetDisitsUnder1(numA);
    var nBd = GetDisitsUnder1(numB);
    //二つの小数の小数点以下桁数のうち多い方
    var dis = math_max(nAd,nBd);
    
    //numA,numBを整数化したもの
    var rA = numA * (dis ** 10);
    var rB = numB * (dis ** 10);
    //整数化されたものの計算結果
    var result = rA + rB;

    return (result / (dis ** 10));
 }