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

編集の要約なし
編集の要約なし
 
(同じ利用者による、間の16版が非表示)
1行目: 1行目:
{{Alert|この記事のより新しい版が[https://docs.wsoft.ws/products/alice/tutorial/calculation-error/ WSOFTDocs]にあります。AliceScriptWikiでは、この記事はもう更新されません。||04}}
[[Category:チュートリアル]]
[[Category:チュートリアル]]
AliceScriptの数値型は、倍精度浮動小数点数数値型と定められており、この規格はIEEE754として標準化されています。
AliceScriptの数値型は、倍精度浮動小数点数数値型と定められており、この規格はIEEE754として標準化されています。
13行目: 15行目:
知的好奇心旺盛なあなたのために、もう少し説明します。例えば10進数の<code>0.1</code>を2進数に変換すると<code>0.0001100110011…</code>となり、<code>0011</code>が永遠と循環します。そのため<code>0.1</code>を倍精度浮動小数点数数値型に格納するには、適当な桁で丸める必要があります。このとき、最も近い偶数に値を丸めます。その結果として<code>0.1</code>は数値型では2進数で<code>0.0001100110011001100110011001100110011001100110011001101</code>となります。これを10進数に戻す<code>0.1000000000000000055511151231257827021181583404541015625</code>となり、<code>0.1</code>ではなくなります。
知的好奇心旺盛なあなたのために、もう少し説明します。例えば10進数の<code>0.1</code>を2進数に変換すると<code>0.0001100110011…</code>となり、<code>0011</code>が永遠と循環します。そのため<code>0.1</code>を倍精度浮動小数点数数値型に格納するには、適当な桁で丸める必要があります。このとき、最も近い偶数に値を丸めます。その結果として<code>0.1</code>は数値型では2進数で<code>0.0001100110011001100110011001100110011001100110011001101</code>となります。これを10進数に戻す<code>0.1000000000000000055511151231257827021181583404541015625</code>となり、<code>0.1</code>ではなくなります。


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


なおこのような誤差は、AliceScript固有のものではありません。IEEE 754の2進浮動小数点形式を採用しているシステムでは、同じことが起こりえます。
なおこのような誤差は、AliceScript固有のものではありません。IEEE 754の2進浮動小数点形式を採用しているシステムでは、同じことが起こりえます。
=回避策=
=回避策=
==許容範囲を決めて値を比較する==
==許容範囲を決めて値を比較する==
24行目: 27行目:
  ///パラメータ a:一方の値
  ///パラメータ a:一方の値
  ///        b:もう一方の値
  ///        b:もう一方の値
  /// tolerance:許容範囲(この数値は常に正)     
  /// tolerance:許容できる誤差の最大の値(この数値は常に正)     
  function NumEqual(number a,number b,number tolerance)
  function NumEqual(number a,number b,number tolerance)
   {
   {
36行目: 39行目:
  if(NumEqual(numA,numB,tolerance))
  if(NumEqual(numA,numB,tolerance))
   {
   {
     print(“2値は等しいと認められました”);
     print(“二値は等しいと認められました”);
  }
==一度整数にした後で変換する==
他には、例えば一度整数にしてから計算する方法も考えられます。次の例では、二つの数を整数にすることができる数<code>dis ** 10</code>を求めて二数を整数にした後、計算を行うことで誤差を防ぎます。ただしこの場合でも、有効数字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));
  }
print(NumEqual(0.1+0.2,0.3));//出力例:true
print(Sum(0.1,0.2));//出力例:0.3