8

既存のアプリケーションは、ファイルからいくつかの浮動小数点数を読み取ります。番号は他のアプリケーション(アプリケーションBと呼びましょう)によってそこに書き込まれます。このファイルの形式はずっと前に修正されました(そして私たちはそれを変更することはできません)。このファイルでは、すべての浮動小数点数が2進表現(ファイル内で4バイト)の浮動小数点数として保存されます。

私たちのプログラムでは、データを読み取るとすぐに、floatをdoubleに変換し、すべての計算にdoubleを使用します。これは、計算が非常に広範囲であり、丸め誤差の広がりが懸念されるためです。

10進数でfloatを変換すると(以下のコードを参照)、直接変換する場合よりも正確な結果が得られることに気付きました。注:アプリケーションBも内部でdoubleを使用し、floatとしてのみファイルに書き込みます。アプリケーションBの番号0.012がfloatとしてファイルに書き込まれたとします。読み取り後に10進数に変換し、次にdoubleに変換すると、正確に0.012になります。直接変換すると、0.0120000001043081になります。

これは、ファイルから読み取ることなく、割り当てだけで再現できます。

    float readFromFile = 0.012f;
    Console.WriteLine("Read from file: " + readFromFile); 
    //prints 0.012

    double forUse = readFromFile;
    Console.WriteLine("Converted to double directly: " + forUse);
    //prints 0.0120000001043081

    double forUse1 = (double)Convert.ToDecimal(readFromFile);
    Console.WriteLine("Converted to double via decimal: " + forUse1); 
    //prints 0.012

10進数でfloatからdoubleに変換することは常に有益ですか?そうでない場合、どのような条件下で有益ですか?

編集:アプリケーションBは、2つの方法で保存した値を取得できます。

  1. 値は計算の結果である可能性があります
  2. 値は、ユーザーが小数で入力できます(したがって、上記の例では、ユーザーは編集ボックスに0.012と入力し、doubleに変換されてから、floatに保存されました)
4

3 に答える 3

13

正確に 0.012 を取得します

いいえ、ありません。3/250を正確に表すことも、表すことfloatdoubleできません。Double.ToString()取得するのは、文字列フォーマッタによってとしてレンダリングされる値です"0.012"。しかし、これはフォーマッタが正確な値を表示しないために発生します。

通過するdecimalと丸めが発生します。Math.Round必要な丸めパラメーターを使用するだけで、はるかに高速になる可能性があります (理解しやすいことは言うまでもありません) 。気になるのが有効桁数である場合は、以下を参照してください。


価値があるのは、0.012f(0.012 に最も近い 32 ビット IEEE-754 値を意味する) 正確に

0x3C449BA6

また

0.012000000104308128

これはとして正確に表現できSystem.Decimalます。しかしConvert.ToDecimal(0.012f)、その正確な値は得られません。ドキュメントによると、丸めステップがあります。

このDecimalメソッドによって返される値には、最大 7 桁の有効数字が含まれます。値パラメーターに含まれる有効数字が 7 桁を超える場合は、四捨五入を使用して丸められます。

于 2012-11-05T22:15:31.253 に答える
2

奇妙に思えるかもしれませんが、10 進法 ( を使用Convert.ToDecimal(float)) による変換は、状況によっては有益な場合があります。

元の数値がユーザーによって 10 進表現で提供され、ユーザーが入力した有効数字が 7 桁以下であることがわかっている場合は、精度が向上します。

それを証明するために、私は小さなプログラムを書きました (以下を参照)。説明は次のとおりです。

OPから思い出すと、これは一連のステップです。

  1. アプリケーション B には、次の 2 つのソースからの double があります。(a) 計算の結果。(b) ユーザーが入力した 10 進数から変換されます。
  2. アプリケーション B は double を float としてファイルに書き込みます - 52 の 2 進数 ( IEEE 754 single ) から 23 の 2 進数 ( IEEE 754 double ) へのバイナリ丸めを効果的に実行します。
  3. 私たちのアプリケーションはその float を読み取り、次の 2 つの方法のいずれかで double に変換します。

    (a) double への直接代入 - 23 ビットの数値を 2 進数のゼロ (29 ビットのゼロ) で効果的に 52 ビットの数値にパディングします。

    (b) で 10 進数に変換する(double)Convert.ToDecimal(float)

Ben Voigt が適切に気付いたようにConvert.ToDecimal(float)( 「備考」セクションの MSDN を参照)、結果を 7 桁の有効桁数に丸めます。ウィキペディアのSingleに関する IEEE 754 の記事では、次のように読むことができますprecision is 24 bits - equivalent to log10(pow(2,24)) ≈ 7.225 decimal digits.

したがって、一般的なケースでは、double に関する追加情報がない場合、ほとんどの場合、10 進数への変換によって精度がいくらか失われます。

しかし (!) もともと (float としてファイルに書き込まれる前に) double が 7 桁以下の 10 進数であったという追加の知識がある場合、10 進数の丸め(上記のステップ 3(b))で導入された丸め誤差が補正されます。 2 進丸めで発生した丸め誤差(上記のステップ 2.)。

一般的なケースのステートメントを証明するプログラムでは、double をランダムに生成し、それを float にキャストし、(a) 直接 double に変換し、(b) decimal を介して変換し、元の double とダブル (a) とダブル (b)。double(a) が double(b) よりも元の値に近い場合は、pro-direct conversion カウンターをインクリメントし、逆の場合は pro-viaDecimal カウンターをインクリメントします。100万ループでやっています。サイクル、次に pro-direct と pro-viaDecimal カウンターの比率を出力します。比率は約 3.7 であることがわかります。つまり、5 つのケースのうち約 4 つのケースで、10 進数による変換によって数値が台無しになります。

ユーザーが数字を入力する場合を証明するために、同じプログラムを使用Math.Round(originalDouble, N)し、ダブルスに適用する変更のみを行いました。originalDoubles は Random クラスから取得するため、すべて 0 から 1 の間になるため、有効桁数は小数点以下の桁数と一致します。このメソッドを、ユーザーが入力した有効数字 1 桁から有効数字 15 桁までの N によるループに配置しました。次に、それをグラフにプロットしました。ユーザーが入力した有効桁数からの依存性 (10 進数による変換よりも直接変換の方が優れている回数)。 ユーザーが入力した有効桁数からの依存性 (10 進法よりも直接変換したほうがよい回数).

ご覧のとおり、1 から 7 の型付き数字の場合、直接変換よりも Decimal による変換の方が常に優れています。正確には、100 万の乱数に対して、10 進数に変換しても改善されないのは 1 つまたは 2 つだけです。

比較に使用したコードは次のとおりです。

private static void CompareWhichIsBetter(int numTypedDigits)
{
    Console.WriteLine("Number of typed digits: " + numTypedDigits);
    Random rnd = new Random(DateTime.Now.Millisecond);
    int countDecimalIsBetter = 0;
    int countDirectIsBetter = 0;
    int countEqual = 0;

    for (int i = 0; i < 1000000; i++)
    {
        double origDouble = rnd.NextDouble();
        //Use the line below for the user-typed-in-numbers case.
        //double origDouble = Math.Round(rnd.NextDouble(), numTypedDigits); 

        float x = (float)origDouble;
        double viaFloatAndDecimal = (double)Convert.ToDecimal(x);
        double viaFloat = x;

        double diff1 = Math.Abs(origDouble - viaFloatAndDecimal);
        double diff2 = Math.Abs(origDouble - viaFloat);

        if (diff1 < diff2)
            countDecimalIsBetter++;
        else if (diff1 > diff2)
            countDirectIsBetter++;
        else
            countEqual++;
    }

    Console.WriteLine("Decimal better: " + countDecimalIsBetter);
    Console.WriteLine("Direct better: " + countDirectIsBetter);
    Console.WriteLine("Equal: " + countEqual);
    Console.WriteLine("Betterness of direct conversion: " + (double)countDirectIsBetter / countDecimalIsBetter);
    Console.WriteLine("Betterness of conv. via decimal: " + (double)countDecimalIsBetter / countDirectIsBetter );
    Console.WriteLine();
}
于 2012-11-06T22:29:27.957 に答える
1

ここに別の答えがあります-それがベンのものよりも優れているかどうかはわかりませんが(ほぼ確実にそうではありません)、正しい結果が得られるはずです:

float readFromFile = 0.012f;
decimal forUse = Convert.ToDecimal(readFromFile.ToString("0.000"));

「正しい」数値 (スポット チェックが容易なはずです) が生成される限り、.ToString("0.000")丸め誤差を心配する必要がなく、操作できるものが得られます。さらに精度が必要な場合は、 を追加してください0

もちろん、実際に0.012f最大精度で作業する必要がある場合、これは役に立ちませんが、その場合は、そもそも float から変換したくないでしょう。

于 2012-11-06T15:04:29.357 に答える