C#での出力用のdoubleの書式設定
-
07-07-2019 - |
質問
に関連する簡単な実験を実行しています。 >そして、C#文字列の書式設定に関するいくつかの記事を読んで、これを考えました:
{
double i = 10 * 0.69;
Console.WriteLine(i);
Console.WriteLine(String.Format(" {0:F20}", i));
Console.WriteLine(String.Format("+ {0:F20}", 6.9 - i));
Console.WriteLine(String.Format("= {0:F20}", 6.9));
}
このCコードに相当するC#になります:
{
double i = 10 * 0.69;
printf ( "%f\n", i );
printf ( " %.20f\n", i );
printf ( "+ %.20f\n", 6.9 - i );
printf ( "= %.20f\n", 6.9 );
}
ただし、C#は出力を生成します。
6.9
6.90000000000000000000
+ 0.00000000000000088818
= 6.90000000000000000000
デバッガで値が6.89999999999999946709(6.9ではなく)になっているにもかかわらず、表示されます。
フォーマットで要求される精度を示すCと比較して:
6.900000
6.89999999999999946709
+ 0.00000000000000088818
= 6.90000000000000035527
何が起こっているのですか?
(Microsoft .NET Frameworkバージョン3.51 SP1 / Visual Studio C#2008 Express Edition)
私は数値計算のバックグラウンドを持ち、さまざまなプラットフォームで間隔計算(複雑な数値システムの精度の限界による誤差を推定する手法)の実装経験があります。賞金を得るには、ストレージの精度について説明しようとしないでください。この場合、64ビットの倍精度の1つのULPの違いです。
報奨金を得るために、.NetがCコードで見えるように、要求された精度にdoubleをフォーマットする方法(またはどうか)を知りたいです。
解決
問題は、.NETが、フォーマットで要求される精度に関係なく、フォーマットを適用する前に、 double
を常に有効桁数15桁に丸める ことです。 2進数の正確な10進数値。
Visual Studioデバッガーには、内部の2進数に直接アクセスする独自のフォーマット/表示ルーチンがあるため、C#コード、Cコード、およびデバッガーの相違が推測されます。
double
の正確な10進数値にアクセスしたり、 double
を特定の10進数にフォーマットできるようにする組み込み機能はありません。ただし、内部の2進数を取り出して、10進数値の文字列表現として再構築することで、これを自分で行うことができます。
あるいは、ジョン・スキートの DoubleConverter
クラス(リンク彼の"バイナリ浮動小数点と.NET"の記事から)。これには、 double
の正確な10進値を返す ToExactString
メソッドがあります。これを簡単に変更して、出力を特定の精度に丸めることができます。
double i = 10 * 0.69;
Console.WriteLine(DoubleConverter.ToExactString(i));
Console.WriteLine(DoubleConverter.ToExactString(6.9 - i));
Console.WriteLine(DoubleConverter.ToExactString(6.9));
// 6.89999999999999946709294817992486059665679931640625
// 0.00000000000000088817841970012523233890533447265625
// 6.9000000000000003552713678800500929355621337890625
他のヒント
Digits after decimal point
// just two decimal places
String.Format("{0:0.00}", 123.4567); // "123.46"
String.Format("{0:0.00}", 123.4); // "123.40"
String.Format("{0:0.00}", 123.0); // "123.00"
// max. two decimal places
String.Format("{0:0.##}", 123.4567); // "123.46"
String.Format("{0:0.##}", 123.4); // "123.4"
String.Format("{0:0.##}", 123.0); // "123"
// at least two digits before decimal point
String.Format("{0:00.0}", 123.4567); // "123.5"
String.Format("{0:00.0}", 23.4567); // "23.5"
String.Format("{0:00.0}", 3.4567); // "03.5"
String.Format("{0:00.0}", -3.4567); // "-03.5"
Thousands separator
String.Format("{0:0,0.0}", 12345.67); // "12,345.7"
String.Format("{0:0,0}", 12345.67); // "12,346"
Zero
Following code shows how can be formatted a zero (of double type).
String.Format("{0:0.0}", 0.0); // "0.0"
String.Format("{0:0.#}", 0.0); // "0"
String.Format("{0:#.0}", 0.0); // ".0"
String.Format("{0:#.#}", 0.0); // ""
Align numbers with spaces
String.Format("{0,10:0.0}", 123.4567); // " 123.5"
String.Format("{0,-10:0.0}", 123.4567); // "123.5 "
String.Format("{0,10:0.0}", -123.4567); // " -123.5"
String.Format("{0,-10:0.0}", -123.4567); // "-123.5 "
Custom formatting for negative numbers and zero
String.Format("{0:0.00;minus 0.00;zero}", 123.4567); // "123.46"
String.Format("{0:0.00;minus 0.00;zero}", -123.4567); // "minus 123.46"
String.Format("{0:0.00;minus 0.00;zero}", 0.0); // "zero"
Some funny examples
String.Format("{0:my number is 0.0}", 12.3); // "my number is 12.3"
String.Format("{0:0aaa.bbb0}", 12.3);
MSDNリファレンスをご覧ください。ノートでは、数値は要求された小数点以下の桁数に丸められると記載されています。
代わりに" {0:R}"を使用する場合「往復」と呼ばれるものが生成されます。値については、この MSDNリファレンスをご覧ください。詳細は、これが私のコードと出力です:
double d = 10 * 0.69;
Console.WriteLine(" {0:R}", d);
Console.WriteLine("+ {0:F20}", 6.9 - d);
Console.WriteLine("= {0:F20}", 6.9);
出力
6.8999999999999995
+ 0.00000000000000088818
= 6.90000000000000000000
この質問はその間閉じられていますが、この残虐行為がどのように発生したかについて言及する価値があると思います。ある意味では、C#仕様のせいにすることができます。これは、doubleの精度が15桁または16桁(IEEE-754の結果)でなければならないことを示しています。さらに少し(セクション4.1.6)、実装はより高い精度を使用することが許可されていると述べられています。気をつけて:高い、低くはない。 IEEE-754から逸脱することさえ許可されています。タイプ x * y / z
の式で、 x * y
は +/- INF
しかし、分割後は有効な範囲になりますが、エラーになる必要はありません。この機能により、コンパイラはパフォーマンスが向上するアーキテクチャでより高い精度を使用しやすくなります。
しかし、「理由」を約束しました。 共有ソースCLI 、 clr / src / vm / comnumber.cpp
内:
"両方の数字を与えるために 表示しやすい ラウンドトリップ可能、数値を解析します 15桁を使用して、 同じ値に往復します。もし それは、そのNUMBERをに変換します 文字列、それ以外の場合は17を使用して再解析します 数字とそれを表示します。"
言い換えると、MSのCLI開発チームは、ラウンドトリップ可能で、読むのに苦労しないきれいな値を表示することにしました。良いか悪いか?オプトインまたはオプトアウトを希望します。
任意の数のこのラウンドトリップ可能性を見つけるために行うトリックは?ジェネリックNUMBER構造体(doubleのプロパティ用の個別のフィールドがある)への変換とその逆、および結果が異なるかどうかの比較。異なる場合は、正確な値が使用されます( 6.9-i
の中央の値のように)、同じ場合は" pretty value"使用されている。
すでにAndypへのコメントで述べたように、 6.90 ... 00
は 6.89 ... 9467
とビット単位で等しいです。これで 0.0 ... 8818
が使用される理由がわかりました。 0.0
とはビット単位で異なります。
この 15桁の障壁はハードコードされており、CLIを再コンパイルするか、Monoを使用するか、Microsoftを呼び出して、完全な" precision" (これは実際には正確ではありませんが、より良い単語がないためです)。おそらく自分で52ビットの精度を計算するか、前述のライブラリを使用する方が簡単でしょう。
編集:IEE-754浮動小数点を試してみたい場合、考慮このオンラインツールは、浮動小数点のすべての関連部分を表示します。
使用
Console.WriteLine(String.Format(" {0:G17}", i));
これにより、所有している17桁すべてが表示されます。デフォルトでは、Double値には10桁の15桁の精度が含まれますが、内部では最大17桁が維持されます。 {0:R}は常に17桁を与えるとは限らず、その精度で数値を表現できる場合は15を与えます。
数値をその精度で表現できる場合は15桁、最大精度でしか表現できない場合は17桁を返します。 doubleがそれを実装する方法であるより多くの数字を返すようにするためにできることは何もありません。気に入らない場合は、新しい二重クラスを自分で実行してください...
.NETのダブルカントは17を超える桁を格納できないため、デバッガで6.89999999999999946709が表示されず、6.8999999999999995が表示されます。間違っていることを証明するために画像を提供してください。
これに対する答えは簡単で、 MSDN
浮動小数点数は10進数にしか近似できず、浮動小数点数の精度によってその数値が10進数に近似する精度が決まることに注意してください。デフォルトでは、Double値には 15桁の精度が含まれますが、内部では最大17桁が維持されます。
この例では、iの値は6.89999999999999946709で、3桁目から16桁目までのすべての位置に数字9があります(数字の整数部分をカウントすることを忘れないでください)。文字列に変換するとき、フレームワークは数値を15桁に丸めます。
i = 6.89999999999999 946709
digit = 111111 111122
1 23456789012345 678901
iは結果を再現しようとしましたが、デバッガで「i」を見ると、質問で書いたように「6.8999999999999994670909」ではなく「6.8999999999999995」と表示されていました。見たものを再現する手順を提供できますか?
デバッガーが表示する内容を確認するには、次のコード行のようにDoubleConverterを使用できます。
Console.WriteLine(TypeDescriptor.GetConverter(i).ConvertTo(i, typeof(string)));
これがお役に立てば幸いです!
編集:思ったよりも疲れていると思います。もちろん、これはラウンドトリップ値への書式設定と同じです(前述)。
答えは「はい」です。.NETでは二重印刷が壊れています。末尾のゴミ数字を印刷しています。
正しく実装する方法を読むことができますこちら。
IronSchemeでも同じことをしなければなりませんでした。
> (* 10.0 0.69)
6.8999999999999995
> 6.89999999999999946709
6.8999999999999995
> (- 6.9 (* 10.0 0.69))
8.881784197001252e-16
> 6.9
6.9
> (- 6.9 8.881784197001252e-16)
6.8999999999999995
注:CとC#の両方に正しい値があり、印刷が壊れています。
更新:この発見につながったメーリングリストの会話を探しています。
この簡単な修正が見つかりました。
double i = 10 * 0.69;
System.Diagnostics.Debug.WriteLine(i);
String s = String.Format("{0:F20}", i).Substring(0,20);
System.Diagnostics.Debug.WriteLine(s + " " +s.Length );
Console.WriteLine(string.Format(" Course Fees is {0:0.00}&quot ;, + cfees));
internal void DisplaycourseDetails()
{
Console.WriteLine("Course Code : " + cid);
Console.WriteLine("Couse Name : " + cname);
//Console.WriteLine("Couse Name : " + string.Format("{0:0.00}"+ cfees));
// string s = string.Format("Course Fees is {0:0.00}", +cfees);
// Console.WriteLine(s);
Console.WriteLine( string.Format("Course Fees is {0:0.00}", +cfees));
}
static void Main(string[] args)
{
Course obj1 = new Course(101, "C# .net", 1100.00);
obj1.DisplaycourseDetails();
Course obj2 = new Course(102, "Angular", 7000.00);
obj2.DisplaycourseDetails();
Course obj3 = new Course(103, "MVC", 1100.00);
obj3.DisplaycourseDetails();
Console.ReadLine();
}
}