6

小数の小数点以下の桁数を計算する簡単なメソッドを書いています。メソッドは次のようになります。

public int GetDecimalPlaces(decimal decimalNumber) { 
try {
    int decimalPlaces = 1;
    double powers = 10.0;
    if (decimalNumber > 0.0m) {
        while (((double)decimalNumber * powers) % 1 != 0.0) {
            powers *= 10.0;
            ++decimalPlaces;
        }
    }
    return decimalPlaces;

私はいくつかのテスト値に対してそれを実行して、すべてが正常に機能していることを確認しましたが、最後の値でいくつかの本当に奇妙な動作を取り戻しています:

int test = GetDecimalPlaces(0.1m);
int test2 = GetDecimalPlaces(0.01m);
int test3 = GetDecimalPlaces(0.001m);
int test4 = GetDecimalPlaces(0.0000000001m);
int test5 = GetDecimalPlaces(0.00000000010000000001m);
int test6 = GetDecimalPlaces(0.0000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001m);

テスト1〜5は正常に機能しますが、test6は23を返します。渡される値が最大小数精度を超えていることはわかっていますが、なぜ23なのですか?私が奇妙だと思ったもう1つのことは、test6からの呼び出しに続いてGetDecimalPlacesメソッド内にブレークポイントを設定した場合、メソッド内のdecimalNumberの値は、test5からの値(小数点以下20桁)と同じ値になりますが、渡された小数点以下の桁数は20で、23が返されます。

小数点以下の桁数が多すぎて不安定な数値を渡したからかもしれませんが、後で他の値の計算が失敗する可能性のある根本的に間違ったものを見逃していないことを確認したいと思います。道。

4

3 に答える 3

5

実際にテストしている数は次のとおりです。

0.0000000001000000000100000000

これは、0.0000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001に最も近い正確な10進値です。

したがって、正解は実際には20です。ただし、明確な理由がないため、バイナリ浮動小数点演算を使用しているため、コードは23を返します。それはあなたの計算に完全に不必要にエラーをもたらすでしょう。一貫して小数を使用するように変更する場合は、問題ありません。

public static int GetDecimalPlaces(decimal decimalNumber) {
    int decimalPlaces = 1;
    decimal powers = 10.0m;
    if (decimalNumber > 0.0m) {
        while ((decimalNumber * powers) % 1 != 0.0m) {
            powers *= 10.0m;
            ++decimalPlaces;
        }
    }
    return decimalPlaces;
}
于 2012-11-20T20:51:26.520 に答える
0

(提案)あなたはそれをこのように計算することができます:

public static int GetDecimalPlaces(decimal decimalNumber)
{
    var s = decimalNumber.ToString();
    return s.Substring(s.IndexOf(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) + 1).Length;
}
于 2012-11-20T21:30:36.280 に答える
0

これを行う別の方法があり、10進数に「後続ゼロ」の問題がある場合にのみ剰余演算を使用するため、おそらくより高速に動作します。

基本的な考え方:

.NETでは、小数はすべて次の形式でメモリに保存されます

m * Math.Power(10、-p)

ここで、mは仮数(96ビットサイズ)、pは次数(0から28までの値)です。

decimal.GetBitsメソッドは、decimal structからこの表現を取得し、intの配列(長さ4)として返します。

このデータを使用して、別の小数を作成できます。「Math.Power(10、-p)」の部分を除いて仮数のみを使用する場合、結果は整数の小数になります。そして、この整数の10進数が10で割り切れる場合、ソース番号には1つ以上の後続ゼロがあります。

これが私のコードです

    static int GetDecimalPlaces(decimal value)
    {
        // getting raw decimal structure
        var raw = decimal.GetBits(value);

        // getting current decimal point position
        int decimalPoint = (raw[3] >> 16) & 0xFF;

        // using raw data to create integral decimal with the same mantissa
        // (note: it always will be absolute value because I do not analyze
        // the sign information of source number)
        decimal integral = new decimal(raw[0], raw[1], raw[2], false, 0);

        // disposing from trailing zeros
        while (integral > 0 && integral % 10 == 0)
        {
            decimalPoint--;
            integral /= 10;
        }

        // returning the answer
        return decimalPoint;
    }
于 2012-11-24T21:51:48.503 に答える