【问题标题】:custom method for returning decimal places shows odd behavior返回小数位的自定义方法显示奇怪的行为
【发布时间】:2012-11-20 20:39:21
【问题描述】:

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

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。

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

【问题讨论】:

  • 只是一个猜测,但我知道如果运行代码的硬件支持它,或者它可以在编译时执行计算,该语言保留使用比它所说的更精确的权利在将其存储在变量中之前的时间文字;给出的精度是最小值,而不是精确值。
  • 23
  • 出于好奇,只是转换为字符串并计算小数点后的字符不是更快更容易吗?我知道像 23.23e3 这样的数字存在一些问题,但应该不难适应。
  • @TimSchmelter 哈哈哈谢谢你让我的一天很有趣
  • @TyCobb 我最初是使用 string.Split('.') 和计数来实现的,但是在不使用'.'的不同 CultureInfo 中会出现问题。作为它的小数分隔符,然后您必须开始对字符串进行各种杂乱无章的工作,以确保值仍然是好的。这更加精确,并确保我们只处理数值而不是字符串

标签: c# methods decimal


【解决方案1】:

您实际测试的数字是这样的:

0.0000000001000000000100000000

这是最接近 0.0000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001 的十进制值。

所以正确答案实际上是 20。但是,您的代码给出了 23,因为您使用的是 binary 浮点运算,没有明显的原因。这将完全不必要地在您的计算中引入错误。如果您更改为始终使用小数,那很好:

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

【讨论】:

  • 只是说明为什么 20 是正确答案;这是因为后面的零被忽略了。结果,此函数永远不会返回超过 28(29?),这是 C# 十进制中有效数字的最大数量。为了获得更多,我想你需要使用字符串表示。
  • 非常感谢您解决这个问题,我可以看到问题出在哪里,双打肯定会影响我的价值
【解决方案2】:

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

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

【讨论】:

  • 不,阅读我对原帖的评论,了解为什么不接受使用字符串
  • 我明白了。但是,如果您在应用程序级别设置文化,也许您可​​以使用我发布的编辑版本。
【解决方案3】:

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

基本思路:

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

【讨论】:

    猜你喜欢
    • 2022-08-17
    • 1970-01-01
    • 2013-12-21
    • 2014-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多