获取小数点前的位数

fas*_*dat 63 c# numbers decimal

我有一个decimal类型的变量,我想检查其中小数点前的位数.我该怎么办?例如,467.45应该返回3.

ast*_*tef 82

没有转换的解决方案string(在外来文化的情况下可能很危险):

static int GetNumberOfDigits(decimal d)
{
    decimal abs = Math.Abs(d);

    return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1);
}
Run Code Online (Sandbox Code Playgroud)

请注意,此解决方案适用于所有十进制值

UPDATE

事实上,这个解决方案不与一些大的值正常工作,例如:999999999999998,999999999999999,9999999999999939...

显然,数学运算double对于这项任务来说不够准确.

在搜索错误的值时,我倾向于使用string本主题中提出的基于替代的替代方法.至于我,这证明它们更可靠和易于使用(但要注意文化).但是,基于循环的解决方案可以更快.

感谢评论员,羞辱我,给你上课.

  • "请注意,此解决方案对所有十进制值都有效"---错误,因为转换为double是有损的.例如`GetNumberOfDigits(999999999999999)`应该返回`15`但是用你的代码返回`16`(至少在我的电脑上,双打在计算机之间不一致). (8认同)
  • 我认为这是迄今为止最好的解决方案.但我会添加`Math.Abs​​`来处理负值.也许检查`d`是否为'0`) (4认同)
  • 对于那些打算多次调用这种方法并关注性能的人:即使所有这些调用"数学"方法,它仍然比"截断"解决方案快至少2倍,但是我更喜欢[Stephan的答案](http ://stackoverflow.com/a/21546915/1177964)简洁明了 (3认同)
  • @ Selman22也许你可以告诉我们截断是如何帮助代码astef提供而不是表现幼稚?! (3认同)
  • 我认为测试绝对值是否小于1可能更好,而不仅仅是零.这是一个边缘情况,但可能不希望0.0001返回小数点前的-4位数. (3认同)
  • 您如何确定此解决方案适用于所有*可能的值?即使是那些巨大的,或者只是高于或低于10的幂的一小部分? (3认同)
  • @ Selman22我看不出截断是如何帮助的 (2认同)
  • @ Selman22我与Stephan的解决方案相比,我已经测量过了,很确切地说,我知道我在说什么. (2认同)
  • @CodesInChaos当然正确.因为`double`具有大约的精度.15到17位有效十进制数字,例如"9999999999999999993728621.07m"必须舍入(向上或向下,取决于两个相关幂的最接近倍数,但在某些(约50%)情况下,它肯定会重要). (2认同)

Mar*_*kus 32

您也可以将数字除以10,直到它等于0.有趣的是,小数的数学运算比将小数转换为字符串并返回长度慢得多(参见下面的基准).
此解决方案不使用以double作为输入的Math方法; 所以所有操作都是在小数上完成的,不涉及任何转换.

using System;

public class Test
{
    public static void Main()
    {
        decimal dec = -12345678912345678912345678912.456m;
        int digits = GetDigits(dec);
        Console.WriteLine(digits.ToString());
    }

    static int GetDigits(decimal dec)
    {
        decimal d = decimal.Floor(dec < 0 ? decimal.Negate(dec) : dec);
        // As stated in the comments of the question, 
        // 0.xyz should return 0, therefore a special case
        if (d == 0m)
            return 0;
        int cnt = 1;
        while ((d = decimal.Floor(d / 10m)) != 0m)
            cnt++;
        return cnt;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是29.要运行此示例,请访问此链接.


旁注:一些基准测试显示出令人惊讶的结果(10k运行):

  • while ((d = decimal.Floor(d / 10m)) != 0m):25ms
  • while ((d = d / 10m) > 1m):32毫秒
  • 使用Math-double操作的ToString:3ms
  • 带小数运算的ToString:3ms
  • BigInt(见@Heinzi的回答):2ms

同样使用随机数而不是总是相同的值(以避免可能的十进制缓存到字符串转换)表明基于字符串的方法要快得多.


Kev*_*ühl 26

我会试试这个:

Math.Truncate(467.45).ToString().Length
Run Code Online (Sandbox Code Playgroud)

如果你想确保不会有不同文化和负小数的一些奇怪的结果,你最好使用这个:

var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length
Run Code Online (Sandbox Code Playgroud)

  • 你也应该使用`ToString(CultureInfo.InvariantCulture)`,否则你会得到一些非常疯狂的结果. (6认同)
  • 你是对的,谢谢.最好使用Math.Truncate.我已经更新了我的答案. (2认同)
  • 此解决方案将计算负数的减号.这就是为什么@ stephan-bauer的回答更好. (2认同)

Ste*_*uer 13

我更喜欢以下而不是强制转换,int以确保您也可以处理大数字(例如decimal.MaxValue):

Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length
Run Code Online (Sandbox Code Playgroud)


Son*_*nül 7

decimal d = 467.45M;
int i = (int)d;
Console.WriteLine(i.ToString(CultureInfo.InvariantCulture).Length); //3
Run Code Online (Sandbox Code Playgroud)

作为一种方法;

public static int GetDigitsLength(decimal d)
{
  int i = int(d);
  return i.ToString(CultureInfo.InvariantCulture).Length;
}
Run Code Online (Sandbox Code Playgroud)

注意:当然你应该首先检查你的小数位数是否大于Int32.MaxValue或小于.因为如果是,你会得到一个OverflowException.

是这样的情况,使用long而不是int可以更好的方法.然而,即使a long(System.Int64)也不足以容纳所有可能的decimal价值.

正如Rawling所提到的,你的完整部分可以容纳千位分隔符,在这种情况下我的代码将被破坏.因为这样,它完全忽略了我的号码是否包含NumberFormatInfo.NumberGroupSeparator.

这就是为什么只获取数字是一种更好的方法.喜欢;

i.ToString().Where(c => Char.IsDigit(c)).ToArray()
Run Code Online (Sandbox Code Playgroud)

  • Astef的解决方案是无望的方法之一.转换为`int`,`long`或`double`开始的任何事都无效.一些基于字符串的答案有效,但它们应该使用`InvariantCulture`,并且基于分部的答案也可以. (2认同)

Gra*_*ray 6

这是一个递归的例子(主要是为了好玩).

void Main()
{
    digitCount(0.123M); //0
    digitCount(493854289.543354345M); //10
    digitCount(4937854345454545435549.543354345M); //22
    digitCount(-4937854345454545435549.543354345M); //22
    digitCount(1.0M); //1
    //approximately the biggest number you can pass to the function that works.
    digitCount(Decimal.MaxValue + 0.4999999M); //29
}

int digitCount(decimal num, int count = 0)
{
    //divided down to last digit, return how many times that happened
    if(Math.Abs(num) < 1)
        return count;
    return digitCount(num/10, ++count); //increment the count and divide by 10 to 'remove' a digit
}
Run Code Online (Sandbox Code Playgroud)


Nah*_*hum 5

Math.Floor(Math.Log10((double)n) + 1); 是要走的路.

转换int为BAD因为decimal可能大于int:

Decimal.MaxValue = 79,228,162,514,264,337,593,543,950,335;
Int32.MaxValue = 2,147,483,647; //that is, hexadecimal 0x7FFFFFFF;
Run Code Online (Sandbox Code Playgroud)

Math.Floor(n).ToString().Count(); 很糟糕,因为它可能包括数千个分隔符.

  • `Math`类中的`Log`函数只处理`double`s. (2认同)

GvS*_*GvS 5

如果你偏向于较小的数字,你可以使用更简单的东西.

它分为两种方法,因此第一种方法较小,可以内联.

性能与Log10的解决方案大致相同,但没有舍入错误.使用Log10的方法仍然是最快的(有点)特别适用于数量> 100万的数字.

    public static int CountNrOfDigitsIfs(decimal d) {

        var absD = Math.Abs(d);
        // 1
        if (absD < 10M) return 1;
        // 2
        if (absD < 100M) return 2;
        // 3
        if (absD < 1000M) return 3;
        // 4
        if (absD < 10000M) return 4;

        return CountNrOfDigitsIfsLarge(d);
    }

    private static int CountNrOfDigitsIfsLarge(decimal d) {

        // 5
        if (d < 100000M) return 5;
        // 6
        if (d < 1000000M) return 6;
        // 7
        if (d < 10000000M) return 7;
        // 8
        if (d < 100000000M) return 8;
        // 9
        if (d < 1000000000M) return 9;
        // 10
        if (d < 10000000000M) return 10;
        // 11
        if (d < 100000000000M) return 11;
        // 12
        if (d < 1000000000000M) return 12;
        // 13
        if (d < 10000000000000M) return 13;
        // 14
        if (d < 100000000000000M) return 14;
        // 15
        if (d < 1000000000000000M) return 15;
        // 16
        if (d < 10000000000000000M) return 16;
        // 17
        if (d < 100000000000000000M) return 17;
        // 18
        if (d < 1000000000000000000M) return 18;
        // 19
        if (d < 10000000000000000000M) return 19;
        // 20
        if (d < 100000000000000000000M) return 20;
        // 21
        if (d < 1000000000000000000000M) return 21;
        // 22
        if (d < 10000000000000000000000M) return 22;
        // 23
        if (d < 100000000000000000000000M) return 23;
        // 24
        if (d < 1000000000000000000000000M) return 24;
        // 25
        if (d < 10000000000000000000000000M) return 25;
        // 26
        if (d < 100000000000000000000000000M) return 26;
        // 27
        if (d < 1000000000000000000000000000M) return 27;
        // 28
        if (d < 10000000000000000000000000000M) return 28;

        return 29; // Max nr of digits in decimal
    }
Run Code Online (Sandbox Code Playgroud)

此代码使用以下T4​​模板生成:

<#
   const int SIGNIFICANT_DECIMALS = 29;
   const int SPLIT = 5;
#>

namespace Study.NrOfDigits {
    static partial class DigitCounter {

        public static int CountNrOfDigitsIfs(decimal d) {

            var absD = Math.Abs(d);
<#          
            for (int i = 1; i < SPLIT; i++) { // Only 29 significant digits
               var zeroes = new String('0', i);
#>
            // <#= i #>
            if (absD < 1<#= zeroes #>M) return <#= i #>;
<# 
            }
#>

            return CountNrOfDigitsIfsLarge(d);
        }

        private static int CountNrOfDigitsIfsLarge(decimal d) {

<#          
            for (int i = SPLIT; i < SIGNIFICANT_DECIMALS; i++) { // Only 29 significant digits
               var zeroes = new String('0', i);
#>
            // <#= i #>
            if (d < 1<#= zeroes #>M) return <#= i #>;
<# 
            }
#>

            return <#= SIGNIFICANT_DECIMALS #>; // Max nr of digits in decimal
        }

    }
}
Run Code Online (Sandbox Code Playgroud)