数学的に、この問題の有理数を考えてください
8725724278030350 / 2**48
ここ**
で、分母は累乗を表します。つまり、分母は2
348
乗です。(分数は最低条件ではなく、2 で減数できます。) この数はとして正確に表現できSystem.Double
ます。その10進展開は
31.0000000000000'49'73799150320701301097869873046875 (exact)
ここで、アポストロフィは欠落している数字を表すのではなく、単に15に丸める境界を示すだけです。17桁が実行されます。
次の点に注意してください: この数値が 15 桁に丸められる場合、次の桁 ( ) が (切り捨てを意味する) で始まるため、結果は31
(その後に 13 が続きます) になります。しかし、数値が最初に 17 桁に丸められ、次に15 桁に丸められると、結果は. これは、最初の丸めでは桁数を(次の桁は) に増やすことで切り上げられ、2 回目の丸めでは再び切り上げられる可能性があるためです (中点の丸めルールで「ゼロから四捨五入する」と指定されている場合)。0
49...
4
31.0000000000001
49...
50 (terminates)
73...
(もちろん、上記の特徴を持つ数字は他にもたくさんあります。)
ここで、この数値の .NET の標準文字列表現は"31.0000000000001"
. 質問: これはバグではありませんか? 標準的な文字列表現とはString
、parameterlesDouble.ToString()
インスタンス メソッドによって生成される を意味します。これは、 によって生成されるものと同じですToString("G")
。
注目すべき興味深い点は、上記の数値を にキャストすると、正確にSystem.Decimal
が得られるdecimal
ことです! aを toにキャストすると最初に 15 桁に丸められるという驚くべき事実については、この Stack Overflow の質問31
を参照してください。これは、 to にキャストすると 15 桁に正しく丸められるのに対し、呼び出すと正しくない丸めが行われることを意味します。Double
Decimal
Decimal
ToSting()
要約すると、ユーザーへの出力時には である浮動小数点数が得られます31.0000000000001
が、変換するとDecimal
( 29桁が使用可能)、31
正確になります。これは残念です。
問題を確認するための C# コードを次に示します。
static void Main()
{
const double evil = 31.0000000000000497;
string exactString = DoubleConverter.ToExactString(evil); // Jon Skeet, http://csharpindepth.com/Articles/General/FloatingPoint.aspx
Console.WriteLine("Exact value (Jon Skeet): {0}", exactString); // writes 31.00000000000004973799150320701301097869873046875
Console.WriteLine("General format (G): {0}", evil); // writes 31.0000000000001
Console.WriteLine("Round-trip format (R): {0:R}", evil); // writes 31.00000000000005
Console.WriteLine();
Console.WriteLine("Binary repr.: {0}", String.Join(", ", BitConverter.GetBytes(evil).Select(b => "0x" + b.ToString("X2"))));
Console.WriteLine();
decimal converted = (decimal)evil;
Console.WriteLine("Decimal version: {0}", converted); // writes 31
decimal preciseDecimal = decimal.Parse(exactString, CultureInfo.InvariantCulture);
Console.WriteLine("Better decimal: {0}", preciseDecimal); // writes 31.000000000000049737991503207
}
上記のコードは Skeet のToExactString
方法を使用しています。彼のもの (URL から見つけることができます) を使用したくない場合は、上記の依存関係のコード行を削除してくださいexactString
。Double
問題の ( evil
) がどのように丸められてキャストされるかをまだ確認できます。
添加:
わかりましたので、さらにいくつかの数値をテストしました。これが表です。
exact value (truncated) "R" format "G" format decimal cast
------------------------- ------------------ ---------------- ------------
6.00000000000000'53'29... 6.0000000000000053 6.00000000000001 6
9.00000000000000'53'29... 9.0000000000000053 9.00000000000001 9
30.0000000000000'49'73... 30.00000000000005 30.0000000000001 30
50.0000000000000'49'73... 50.00000000000005 50.0000000000001 50
200.000000000000'51'15... 200.00000000000051 200.000000000001 200
500.000000000000'51'15... 500.00000000000051 500.000000000001 500
1020.00000000000'50'02... 1020.000000000005 1020.00000000001 1020
2000.00000000000'50'02... 2000.000000000005 2000.00000000001 2000
3000.00000000000'50'02... 3000.000000000005 3000.00000000001 3000
9000.00000000000'54'56... 9000.0000000000055 9000.00000000001 9000
20000.0000000000'50'93... 20000.000000000051 20000.0000000001 20000
50000.0000000000'50'93... 50000.000000000051 50000.0000000001 50000
500000.000000000'52'38... 500000.00000000052 500000.000000001 500000
1020000.00000000'50'05... 1020000.000000005 1020000.00000001 1020000
最初の列は、 が表す正確な (切り捨てられた) 値をDouble
示します。"R"
2 番目の列は、フォーマット文字列からの文字列表現を示します。3 列目は通常の文字列表現です。最後に、4 番目の列は、System.Decimal
this を変換した結果の を示しますDouble
。
結論は次のとおりです。
- 15 桁に
ToString()
丸め、変換で 15 桁に丸めるとDecimal
、非常に多くの場合、一致しません。 - への変換
Decimal
も多くの場合、正しく丸められず、これらの場合のエラーは「2 回のラウンド」エラーとは言えません。 - 私の場合、それらが一致しない場合、変換
ToString()
よりも大きな数値が得られるようです(2 つのラウンドのどちらが正しくても)。Decimal
上記のようなケースでのみ実験しました。他の「フォーム」の数に丸め誤差があるかどうかは確認していません。