Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/317.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 返回小数位数的自定义方法显示奇怪的行为_C#_Methods_Decimal - Fatal编程技术网

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

C# 返回小数位数的自定义方法显示奇怪的行为,c#,methods,decimal,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) {

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

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工作正常,但测试6返回23。我知道传入的值超过了最大十进制精度,但为什么是23?另一件我觉得奇怪的事情是,当我在test6调用之后在GetDecimalPlaces方法中放置断点时,该方法中的decimalNumber值与test5中的值相同(20位小数),尽管传入的值有20位小数,但返回的是23位小数

也许这只是因为我传递的数字有太多的小数位,事情变得不稳定,但我想确保我没有遗漏一些根本性的错误,这些错误可能会在以后的过程中影响其他值的计算。

您实际测试的数字是:

0.0000000001000000000100000000
这是最接近0.00000000010000000100000000000001000000000000010000000000000100000000000001000000000000010000000000000100000000000001000000000000010000000000000100000000000001的精确十进制值

所以正确答案实际上是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;
}
(建议)你可以这样计算:

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

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

基本思想:

在.NET中,任何十进制数都以以下形式存储在内存中

m*数学幂(10,-p)

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

方法从decimal结构中检索此表示形式,并将其作为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;
    }

只是一个猜测,但我知道,如果运行代码的硬件支持它,或者在将其存储到变量之前可以对编译时文本执行计算,那么该语言保留使用比它所说的更高精度的权利;给定的精度是最小值,而不是精确值。出于好奇,将其转换为字符串并计算小数点后的字符不是更快更容易吗?我知道像23.23e3这样的数字会有一些问题,但适应它应该不难。@TimSchmelter哈哈哈谢谢你让我度过了美好的一天hilarious@TyCobb我最初是使用string.Split('.')实现的计算数字,但问题出现在不使用“.”作为小数分隔符的其他CultureInfo中,然后必须开始对字符串进行各种杂乱的处理,以确保值仍然正确。这要精确得多,确保我们只处理数值而不是字符串。请注意为什么20是正确答案;这是因为后面的零被忽略了。该函数返回的结果永远不会超过28(29?),这是C#十进制中有效位数的最大值。要想得到更多,我想你需要使用字符串表示法。非常感谢你澄清了这一点。我可以看到问题的根源,双打肯定干扰了我的价值观。不,请阅读我在原始帖子上的评论,看看为什么使用字符串是不可接受的。我明白了。但是,如果您在应用程序级别设置区域性,也许您可以使用我发布的编辑版本。