私は単純なC#関数を持っています:
public static double Floor(double value, double step)
{
return Math.Floor(value / step) * step;
}
これは、「値」以下の、「ステップ」の倍数である高い数値を計算します。ただし、次のテストで見られるように、精度が不足しています。
[TestMethod()]
public void FloorTest()
{
int decimals = 6;
double value = 5F;
double step = 2F;
double expected = 4F;
double actual = Class.Floor(value, step);
Assert.AreEqual(expected, actual);
value = -11.5F;
step = 1.1F;
expected = -12.1F;
actual = Class.Floor(value, step);
Assert.AreEqual(Math.Round(expected, decimals),Math.Round(actual, decimals));
Assert.AreEqual(expected, actual);
}
1番目と2番目のアサートは問題ありませんが、結果は小数点以下6桁までしか等しくないため、3番目は失敗します。何故ですか?これを修正する方法はありますか?
更新テストをデバッグすると、Math.Roundによって不正確さが生じたためか、値が小数点以下6桁ではなく小数点以下8桁まで等しいことがわかります。
注テストコードでは、「D」(ダブル)を意味する「F」サフィックス(明示的なフロート定数)を記述したので、これを変更すると、より正確になります。
すべてのF後置を省略した場合(つまり、-12.1
の代わりに -12.1F
)さらに数桁の平等が得られます。 F
により、定数(特に期待値)は浮動小数点になります。わざとそうしているのなら説明してください。
しかし、残りの部分については、倍精度値または浮動小数点値が等しいかどうかを比較する他の回答に同意します。信頼性が低いだけです。
私は実際、彼らがfloatとdoubleの==演算子を実装していなかったらいいのにと思いました。 doubleまたはfloatが他の値と等しいかどうかを尋ねるのは、ほとんどの場合間違っています。
コンピューター上の浮動小数点演算は精密科学ではありません:)。
事前定義された小数の正確な精度が必要な場合は、doubleではなくDecimalを使用するか、マイナー間隔を受け入れます。
精度が必要な場合は、System.Decimalを使用してください。速度が必要な場合は、System.Double(またはSystem.Float)を使用してください。浮動小数点数は「無限精度」の数ではないため、同等性を主張するには許容誤差を含める必要があります。あなたの数が有効数字の妥当な数を持っている限り、これは大丈夫です。
精度が数学演算の結果にどのように影響するかについての詳細な分析については、 this 回答(これも私による)を参照してください。
http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems
たとえば、0.1と0.01(2進数)の表現不可能性は、0.1を2乗しようとした結果が、0.01でもそれに最も近い表現可能な数でもないことを意味します。
マシンによる数値システムの解釈(バイナリ)が必要な場合にのみ、浮動小数点を使用してください。 10セントを表すことはできません。
この質問への回答を確認してください: 浮動小数点値が0に等しいかどうかを確認しても安全ですか?
本当に、「許容範囲内...」をチェックするだけです。
floatとdoubleは、すべての数値を正確に格納できるわけではありません。これは、IEEE浮動小数点システムの制限です。忠実な精度を得るには、より高度な数学ライブラリを使用する必要があります。
特定のポイントを超えて精度が必要ない場合は、おそらく10進数の方が適しています。ダブルよりも精度が高いです。
同様の問題については、次の実装を使用することになります。これは、ほとんどのテストケース(最大5桁の精度)で成功するようです。
public static double roundValue(double rawValue, double valueTick)
{
if (valueTick <= 0.0) return 0.0;
Decimal val = new Decimal(rawValue);
Decimal step = new Decimal(valueTick);
Decimal modulo = Decimal.Round(Decimal.Divide(val,step));
return Decimal.ToDouble(Decimal.Multiply(modulo, step));
}