返回小数位的自定义方法显示奇怪的行为

时间:2012-11-20 20:39:21

标签: c# methods decimal

我正在编写一个简单的方法来计算小数值中的小数位数。该方法如下所示:

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?我发现奇怪的另一件事是当我在来自test6的调用之后在GetDecimalPlaces方法中放置一个断点时,方法中的decimalNumber的值来自test5(20个小数位)的相同值,即使该值传入时有20个小数位23返回。

也许只是因为我传递了一个数字,这个数字的位数太多而且事情变得很糟糕但是我想确保我没有遗漏一些根本错误的东西,这可能会导致其他值的计算失败后来。

3 个答案:

答案 0 :(得分:5)

实际测试的数字是:

0.0000000001000000000100000000

这是最接近的确切十进制值,为0.0000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001。

所以正确的答案实际上是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;
}

答案 1 :(得分:0)

(建议)您可以这样计算:

public static int GetDecimalPlaces(decimal decimalNumber)
{
    var s = decimalNumber.ToString();
    return s.Substring(s.IndexOf(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) + 1).Length;
}

答案 2 :(得分:0)

还有另一种方法可以做到这一点,可能它的工作速度更快,因为只有在十进制数有“尾随零”问题时才使用余数运算。

基本理念:

在.NET中,任何小数都以

的形式存储在内存中
  

m * Math.Power(10,-p)

其中m是尾数(96位大小),p是顺序(值从0到28)。

decimal.GetBits方法从十进制结构中检索此表示形式,并将其作为int(长度为4)的数组返回。

使用此数据我们可以构造另一个小数。如果我们只使用尾数,没有“Math.Power(10,-p)”部分,结果将是一个整数十进制。如果这个整数十进制数可以被10整除,那么我们的源数会有一个或多个尾随零。

所以这是我的代码

    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;
    }