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

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

4

3 回答 3

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;
}
于 2012-11-20T20:51:26.520 回答
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;
    }
于 2012-11-24T21:51:48.503 回答
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 回答