有没有一种方法可以获取一个小数的“有效数字”?

17

更新

经过一些调查和在Jon和Hans提供的有用答案的帮助下,我能够整理出如下内容。到目前为止,我认为它似乎工作得很好。当然,我不能保证它完全正确。

public static int GetSignificantDigitCount(this decimal value)
{
    /* So, the decimal type is basically represented as a fraction of two
     * integers: a numerator that can be anything, and a denominator that is 
     * some power of 10.
     * 
     * For example, the following numbers are represented by
     * the corresponding fractions:
     * 
     * VALUE    NUMERATOR   DENOMINATOR
     * 1        1           1
     * 1.0      10          10
     * 1.012    1012        1000
     * 0.04     4           100
     * 12.01    1201        100
     * 
     * So basically, if the magnitude is greater than or equal to one,
     * the number of digits is the number of digits in the numerator.
     * If it's less than one, the number of digits is the number of digits
     * in the denominator.
     */

    int[] bits = decimal.GetBits(value);

    if (value >= 1M || value <= -1M)
    {
        int highPart = bits[2];
        int middlePart = bits[1];
        int lowPart = bits[0];

        decimal num = new decimal(lowPart, middlePart, highPart, false, 0);

        int exponent = (int)Math.Ceiling(Math.Log10((double)num));

        return exponent;
    }
    else
    {
        int scalePart = bits[3];

        // Accoring to MSDN, the exponent is represented by
        // bits 16-23 (the 2nd word):
        // http://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx
        int exponent = (scalePart & 0x00FF0000) >> 16;

        return exponent + 1;
    }
}

虽然我未对其进行全面测试,但以下是一些样本输入/输出:

Value          Precision
0              1 位数字。
0.000          4 位数字。
1.23           3 位数字。
12.324         5 位数字。
1.2300         5 位数字。
-5             1 位数字。
-5.01          3 位数字。
-0.012         4 位数字。
-0.100         4 位数字。
0.0            2 位数字。
10443.31       7 位数字。
-130.340       6 位数字。
-80.8000       6 位数字。

使用这段代码,我想通过执行以下操作来实现我的目标:

public static decimal DivideUsingLesserPrecision(decimal x, decimal y)
{
    int xDigitCount = x.GetSignificantDigitCount();
    int yDigitCount = y.GetSignificantDigitCount();

    int lesserPrecision = System.Math.Min(xDigitCount, yDigitCount);

    return System.Math.Round(x / y, lesserPrecision);
}

我还没有完全解决这个问题。希望有人能分享一下自己的想法:谢谢!


原始问题

假设我编写了以下代码:

decimal a = 1.23M;
decimal b = 1.23000M;

Console.WriteLine(a);
Console.WriteLine(b);

上述代码的输出结果为:

1.23
1.23000

我发现如果我使用decimal.Parse("1.23")替换a,并使用decimal.Parse("1.23000")替换b(这意味着此问题适用于程序接收用户输入的情况)也能够起到同样的效果。

因此,显然decimal类型的值可以“感知”它所谓的精度。但是,除了ToString本身之外,我没有看到decimal类型上提供任何访问这种信息的成员。

假设我想要将两个decimal值相乘,并将结果修约到较不准确参数的精度。也就是说:

decimal a = 123.4M;
decimal b = 5.6789M;

decimal x = a / b;

Console.WriteLine(x);

以上输出:

21.729560302171195125816619416

我的问题是:我应该如何编写一个方法,返回21.73而不是原始结果(因为123.4M有四个有效数字)?

明确一下:我知道可以在两个参数上调用ToString,计算每个字符串中的有效数字,然后使用这个数字来舍入计算结果。如果可能的话,我正在寻找一个不同的方式。

(我也意识到,在大多数涉及有效数字的情况下,您可能不需要使用decimal类型。但我问这个问题是因为,正如我在开头提到的那样,decimal类型 似乎包含与精度有关的信息,而double则不包含,就我所知。)


3
直到你问了这个问题,我才意识到我想知道这个!+1 - msarchet
3
你的函数对于某些输入值无法正确工作。例如,-0.012 只有 2 个有效数字,而不是 4 个。 - James Jones
@JamesJones 显然他指的是一个不同于数学课程中所学的有效数字概念。也许“使用的数字”这个名称更有意义。 - ErikE
获取小数位数请参见此处 - orad
3个回答

3
您可以使用 Decimal.GetBits 方法来获取原始数据,从中进行计算。
不幸的是,我现在没有时间编写示例代码 - 如果您使用的是.NET 4,则可能需要使用 BigInteger 进行一些操作,但希望这会让您开始。只需计算精度,然后在原始结果上调用 Math.Round 可能是一个很好的起点。

我接受这个答案,尽管我还没有完成我的解决方案,因为它确实给了我所需的信息。(可悲的是,我希望有比“GetBits”更易于使用的东西;但似乎这是我们要使用的最好的!) - Dan Tao

3
是的,与浮点类型不同,System.Decimal跟踪字面值中的数字位数。这是decimal.Parse()的一个特性,无论是由您自己的代码执行还是由编译器在解析程序中的文字时执行。您可以恢复此信息,请查看我在此线程中的答案中的代码。
在对该值进行数学运算后恢复有效数字的数量似乎很难实现。不知道运算符是否保留它们,请告诉我们您发现了什么。

是的,我很确定那个信息没有被保留下来。这是有道理的——考虑到像1M / 4M这样的情况,显然结果应该是0.25M而不是0.3M。无论如何,我花了一点时间解决这个问题,并提出了一个类似解决方案的东西(包括你在链接答案中建议的位操作!)……不过我的代码非常丑陋。如果你稍后有空,我肯定会很感激你对我在问题顶部更新的想法。 - Dan Tao

0
上述解决方案在处理像1200这样的值时容易出问题,其中有效数字的数量为2,但函数返回了4。也许有一种一致的方法来处理这种情况,即检查返回值是否包括整数部分的尾随零而没有小数部分。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接