有没有办法获得“有效数字”?一个小数?

发布于 2024-09-19 03:26:20 字数 3536 浏览 4 评论 0原文

更新

好的,经过一番调查,这在很大程度上要感谢乔恩和汉斯提供的有用答案,这就是我能够整理的内容。到目前为止,我认为它似乎运作良好。当然,我不会用我的生命去赌它的完全正确性。

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 digit(s).
0.000          4 digit(s).
1.23           3 digit(s).
12.324         5 digit(s).
1.2300         5 digit(s).
-5             1 digit(s).
-5.01          3 digit(s).
-0.012         4 digit(s).
-0.100         4 digit(s).
0.0            2 digit(s).
10443.31       7 digit(s).
-130.340       6 digit(s).
-80.8000       6 digit(s).

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

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

我发现如果我对 a使用 decimal.Parse("1.23") ,这也有效>decimal.Parse("1.23000") for b (这意味着这个问题适用于程序接收用户输入的情况)。

很明显,十进制值在某种程度上“知道”我所说的精度。但是,除了 ToString 本身之外,我发现 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 则没有。 )

Update

OK, after some investigation, and thanks in big part to the helpful answers provided by Jon and Hans, this is what I was able to put together. So far I think it seems to work well. I wouldn't bet my life on its total correctness, of course.

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

I haven't tested it all that thoroughly. Here are a few sample inputs/outputs, though:

Value          Precision
0              1 digit(s).
0.000          4 digit(s).
1.23           3 digit(s).
12.324         5 digit(s).
1.2300         5 digit(s).
-5             1 digit(s).
-5.01          3 digit(s).
-0.012         4 digit(s).
-0.100         4 digit(s).
0.0            2 digit(s).
10443.31       7 digit(s).
-130.340       6 digit(s).
-80.8000       6 digit(s).

Using this code, I imagine I would accomplish my goal by doing something like this:

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

I haven't really finished working through this, though. Anybody who wants to share thoughts: that would be much appreciated!


Original Question

Suppose I have write this code:

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

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

The above will output:

1.23
1.23000

I find that this also works if I use decimal.Parse("1.23") for a and decimal.Parse("1.23000") for b (which means this question applies to cases where the program receives user input).

So clearly a decimal value is somehow "aware" of what I'll call its precision. However, I see no members on the decimal type that provide any way of accessing this, aside from ToString itself.

Suppose I wanted to multiply two decimal values and trim the result to the precision of the less precise argument. In other words:

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

decimal x = a / b;

Console.WriteLine(x);

The above outputs:

21.729560302171195125816619416

What I'm asking is: how could I write a method that would return 21.73 instead (since 123.4M has four significant figures)?

To be clear: I realize I could call ToString on both arguments, count the significant figures in each string, and use this number to round the result of the calculation. I'm looking for a different way, if possible.

(I also realize that in most scenarios where you're dealing with significant figures, you probably don't need to be using the decimal type. But I'm asking because, as I mentioned in the beginning, the decimal type appears to include information about precision, whereas double does not, as far as I know.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

旧情别恋 2024-09-26 03:26:20

您可以使用 Decimal.GetBits< /a> 获取原始数据,并从中计算出来。

不幸的是,我现在没有时间编写示例代码 - 您可能想使用 BigInteger 进行一些操作 - 但希望这能让您继续前进。计算出精度,然后对原始结果调用 Math.Round 可能是一个好的开始。

You can use Decimal.GetBits to get the raw data, and work it out from that.

Unfortunately I don't have time to write sample code at the moment - and you'll probably want to use BigInteger for some of the manipulation, if you're using .NET 4 - but hopefully this will get you going. Just working out the precision and then calling Math.Round on the original result may well be a good start.

纵情客 2024-09-26 03:26:20

是的,与浮点类型不同,System.Decimal 会跟踪文字中的位数。这是decimal.Parse() 的一个功能,无论是由您的代码自己执行还是由编译器在解析程序中的文字时执行。您可以恢复此信息,请查看我的答案中的代码 此帖子

在对值进行数学计算后恢复有效数字的数量在我看来是一个不太可能的事情。不知道运营商是否保留它们,请告诉我们您的发现。

Yes, unlike floating point types, System.Decimal keeps track of the number of digits in the literal. This is a feature of decimal.Parse(), whether executed by your code yourself or by the compiler when it parses a literal in your program. You can recover this information, check out the code in my answer in this thread.

Recovering the number of significant digits after you do math on the value strikes me as a long shot. No idea if the operators preserve them, please let us know what you find out.

高跟鞋的旋律 2024-09-26 03:26:20

上面的解决方案很快就失去了像 1200 这样的值,其中有效位数为 2,但函数返回 4。也许有一致的方式来处理这种情况,即检查以确保返回值不包含尾随零是整数部分,没有小数部分。

The solution above falls out of bed quickly with values like 1200 where the number of significant digits is 2 but the function returns 4. Perhaps there is a consistent manner to deal with such a situation i.e. check to make sure return value does not include trailing zeros in the whole number portion without a fractional part.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文