21

decimalC#では、デフォルトでの丸めは。を使用していることがわかりますMidpointRounding.ToEven。これは予想されることであり、C#仕様で規定されています。ただし、次の場合:

  • Adecimal dVal
  • string sFmtに渡されるとdVal.ToString(sFmt)、の丸められたバージョンを含む文字列になる形式dVal

decimal.ToString(string)...を使用して丸められた値を返すことは明らかですMidpointRounding.AwayFromZero。これは、C#仕様と直接矛盾しているように見えます。

私の質問はこれです:これが事実である正当な理由がありますか?それとも、これは言語の単なる矛盾ですか?

decimal.ToString(string)以下に、参考のために、値の配列内のすべての値について、さまざまな丸め演算結果と演算結果をコンソールに書き込むコードをいくつか含めましたdecimal。実際の出力は埋め込まれています。その後、タイプのC#言語仕様セクションから関連する段落を含めましたdecimal

サンプルコード:

static void Main(string[] args)
{
    decimal[] dArr = new decimal[] { 12.345m, 12.355m };

    OutputBaseValues(dArr);
    // Base values:
    // d[0] = 12.345
    // d[1] = 12.355

    OutputRoundedValues(dArr);
    // Rounding with default MidpointRounding:
    // Math.Round(12.345, 2) => 12.34
    // Math.Round(12.355, 2) => 12.36
    // decimal.Round(12.345, 2) => 12.34
    // decimal.Round(12.355, 2) => 12.36

    OutputRoundedValues(dArr, MidpointRounding.ToEven);
    // Rounding with mr = MidpointRounding.ToEven:
    // Math.Round(12.345, 2, mr) => 12.34
    // Math.Round(12.355, 2, mr) => 12.36
    // decimal.Round(12.345, 2, mr) => 12.34
    // decimal.Round(12.355, 2, mr) => 12.36

    OutputRoundedValues(dArr, MidpointRounding.AwayFromZero);
    // Rounding with mr = MidpointRounding.AwayFromZero:
    // Math.Round(12.345, 2, mr) => 12.35
    // Math.Round(12.355, 2, mr) => 12.36
    // decimal.Round(12.345, 2, mr) => 12.35
    // decimal.Round(12.355, 2, mr) => 12.36

    OutputToStringFormatted(dArr, "N2");
    // decimal.ToString("N2"):
    // 12.345.ToString("N2") => 12.35
    // 12.355.ToString("N2") => 12.36

    OutputToStringFormatted(dArr, "F2");
    // decimal.ToString("F2"):
    // 12.345.ToString("F2") => 12.35
    // 12.355.ToString("F2") => 12.36

    OutputToStringFormatted(dArr, "###.##");
    // decimal.ToString("###.##"):
    // 12.345.ToString("###.##") => 12.35
    // 12.355.ToString("###.##") => 12.36

    Console.ReadKey();
}

private static void OutputBaseValues(decimal[] dArr)
{
    Console.WriteLine("Base values:");
    for (int i = 0; i < dArr.Length; i++) Console.WriteLine("d[{0}] = {1}", i, dArr[i]);
    Console.WriteLine();
}

private static void OutputRoundedValues(decimal[] dArr)
{
    Console.WriteLine("Rounding with default MidpointRounding:");
    foreach (decimal d in dArr) Console.WriteLine("Math.Round({0}, 2) => {1}", d, Math.Round(d, 2));
    foreach (decimal d in dArr) Console.WriteLine("decimal.Round({0}, 2) => {1}", d, decimal.Round(d, 2));
    Console.WriteLine();
}

private static void OutputRoundedValues(decimal[] dArr, MidpointRounding mr)
{
    Console.WriteLine("Rounding with mr = MidpointRounding.{0}:", mr);
    foreach (decimal d in dArr) Console.WriteLine("Math.Round({0}, 2, mr) => {1}", d, Math.Round(d, 2, mr));
    foreach (decimal d in dArr) Console.WriteLine("decimal.Round({0}, 2, mr) => {1}", d, decimal.Round(d, 2, mr));
    Console.WriteLine();
}

private static void OutputToStringFormatted(decimal[] dArr, string format)
{
    Console.WriteLine("decimal.ToString(\"{0}\"):", format);
    foreach (decimal d in dArr) Console.WriteLine("{0}.ToString(\"{1}\") => {2}", d, format, d.ToString(format));
    Console.WriteLine();
}


C#言語仕様のセクション4.1.7の段落(「10進型」)(完全な仕様はここ(.doc)で入手してください):

10進数型の値に対する演算の結果は、正確な結果(各演算子に対して定義されたスケールを保持)を計算し、表現に合うように丸めた結果です。結果は、最も近い表現可能な値に丸められ、結果が2つの表現可能な値に等しく近い場合は、最下位桁の位置に偶数がある値に丸められます(これは「バンカーの丸め」と呼ばれます)。ゼロの結果は常に0の符号と0のスケールを持ちます。

彼らがこの段落で考慮していなかったかもしれないことは容易に理解できますToString(string)が、私はそれがこの説明に当てはまると思う傾向があります。

4

3 に答える 3

6

仕様を注意深く読むと、ここに矛盾がないことがわかります。

重要な部分を強調して、その段落をもう一度示します。

decimal 型の値に対する演算の結果は、正確な結果を計算し (各演算子で定義されているように位取りを保持)、表現に合わせて丸めた結果になります。結果は最も近い表現可能な値に丸められ、結果が 2 つの表現可能な値に等しく近い場合は、最下位桁位置が偶数である値に丸められます (これは「バンカーの丸め」として知られています)。ゼロの結果には、常に符号 0 と位取り 0 があります。

仕様のこの部分は、 の算術演算に適用されdecimalます。文字列の書式設定はそれらの 1 つではありません。たとえそうであったとしても、例の精度が低いため問題になりません。

仕様で言及されている動作を示すには、次のコードを使用します。

Decimal d1 = 0.00000000000000000000000000090m;
Decimal d2 = 0.00000000000000000000000000110m;

// Prints: 0.0000000000000000000000000004 (rounds down)
Console.WriteLine(d1 / 2);

// Prints: 0.0000000000000000000000000006 (rounds up)
Console.WriteLine(d2 / 2);

それが仕様について話しているすべてです。一部の計算の結果が型の精度制限 (29 桁) を超える場合decimal、バンカーの丸めを使用して結果がどうなるかが決定されます。

于 2010-02-12T03:54:20.650 に答える
3

ToString()デフォルトCultureでは、仕様の計算面ではなく、に従ってフォーマットされます。どうやらCultureあなたのロケール(そしてほとんどの場合、その外観から)はゼロから四捨五入することを期待しています。

別の動作が必要な場合は、IFormatProviderに渡すことができますToString()

私は上記のことを考えましたが、あなたはそれがに関係なく常にゼロから丸められるということは正しいですCulture


この回答へのコメントにもリンクされているように、ここ(MS Docs)は動作に関する公式ドキュメントです。そのリンクされたページの上部から抜粋し、最後の2つのリスト項目に焦点を当てます。

一般的な数値タイプのフォーマットには、標準の数値フォーマット文字列が使用されます。標準の数値フォーマット文字列は、の形式を取りますAxx。ここで、

  • Aフォーマット指定子と呼ばれる単一の英字です。空白を含む複数の英字を含む数値フォーマット文字列は、カスタム数値フォーマット文字列として解釈されます。詳細については、「カスタム数値フォーマット文字列」を参照してください。

  • xx精度指定子と呼ばれるオプションの整数です。精度指定子の範囲は0〜99で、結果の桁数に影響します。精度指定子は、数値の文字列表現の桁数を制御することに注意してください。数値自体は丸められません。丸め操作を実行するには、Math.CeilingMath.Floor、またはMath.Roundメソッドを使用します。

    精度指定子が結果文字列の小数桁数を制御する場合、結果文字列は、無限に正確な結果に最も近い表現可能な結果に丸められる数値を反映します。ほぼ同じように表現できる結果が2つある場合:

    • .NETFrameworkおよび.NETCore2.0までの.NETCoreでは、ランタイムは最下位桁の結果を選択します(つまり、MidpointRounding.AwayFromZeroを使用します)。

    • .NET Core 2.1以降では、ランタイムはさらに最下位桁の結果を選択します(つまり、MidpointRounding.ToEvenを使用します)。


あなたの質問に関しては---

これが事実である正当な理由はありますか?それとも、これは言語の単なる矛盾ですか?

---FrameworkからCore2.1+への動作の変更によって暗示される答えは、おそらく「いいえ、正当な理由はなかったので、私たち(Microsoft)は先に進み、ランタイムを.NETCore2.1の言語と一致させました。後で。"

于 2010-02-12T02:24:24.927 に答える
1

おそらく、これが通貨を扱う標準的な方法だからです。10 進数を作成するきっかけとなったのは、浮動小数点では通貨の値を処理するのがうまくいかないことでした。そのため、浮動小数点の規則は、数学的な正確さよりも会計基準に沿ったものであることが期待されます。

于 2010-02-12T02:27:23.990 に答える