获取小数点前的位数

时间:2014-02-04 07:51:55

标签: c# numbers decimal

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

25 个答案:

答案 0 :(得分:81)

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

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

    return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1);
}

注意,此解决方案适用于所有小数值

<强>更新

事实上,此解决方案不适用于某些重要值,例如:9999999999999989999999999999999999999999999939 ......

显然,使用double的数学运算对于此任务来说不够准确。

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

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

答案 1 :(得分: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;
    }
}

输出为29。要运行此示例,请访问此link


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

  • while ((d = decimal.Floor(d / 10m)) != 0m):25ms
  • while ((d = d / 10m) > 1m):32ms
  • ToString with Math-double-operations:3ms
  • 带小数操作的ToString:3ms
  • BigInt(见@Heinzi的answer):2ms

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

答案 2 :(得分:26)

我会试试这个:

Math.Truncate(467.45).ToString().Length

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

var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length

答案 3 :(得分:13)

我希望以下内容而不是投放到int,以确保您也可以处理大数字(例如decimal.MaxValue):

Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length

答案 4 :(得分:7)

decimal d = 467.45M;
int i = (int)d;
Console.WriteLine(i.ToString(CultureInfo.InvariantCulture).Length); //3

作为一种方法;

public static int GetDigitsLength(decimal d)
{
  int i = int(d);
  return i.ToString(CultureInfo.InvariantCulture).Length;
}

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

在这种情况下,使用long代替int可以采用更好的方法。 然而即使longSystem.Int64)也不足以容纳每个可能的decimal值。

作为Rawling mentioned ,您的完整部分可以容纳千位分隔符,在这种情况下我的代码将被破坏。因为这样,它完全忽略了我的数字包含NumberFormatInfo.NumberGroupSeparator或不包含。

这就是为什么只获取数字是一种更好的方法。等;

i.ToString().Where(c => Char.IsDigit(c)).ToArray()

答案 5 :(得分: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
}

答案 6 :(得分:5)

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

转换为int是不好的,因为decimal可能大于int

Decimal.MaxValue = 79,228,162,514,264,337,593,543,950,335;
Int32.MaxValue = 2,147,483,647; //that is, hexadecimal 0x7FFFFFFF;

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

答案 7 :(得分:5)

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

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

性能与Log10的解决方案大致相同,但没有舍入错误。使用Log10的方法仍然是最快的(有点)专门用于数字&gt; 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
    }

此代码使用以下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
        }

    }
}

答案 8 :(得分:4)

var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
var count = d.ToString().TakeWhile(c => c != sep).Count();

答案 9 :(得分:3)

如果您真的不想使用Log方法(IMO是最好的方法),这将会这样做。这是我能想到的最简单的方法,使用ToString():

Math.Abs(val).ToString("f0", CultureInfo.InvariantCulture).Length

或者,如果您不想将0.123M计为一位数:

Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length

答案 10 :(得分:3)

您可以使用自定义格式的ToString函数。

Decimal value = 467.45m;
int count = Math.Abs(value).ToString("#", System.Globalization.CultureInfo.InvariantCulture).Length;

#指定您只想要.

之前的字符

System.Globalization.CultureInfo.InvariantCulture确保您不会从区域选项中获得任何格式。

答案 11 :(得分:3)

所以,我之前遇到过这个问题,并用这段代码解决了这个问题:

SqlDecimal d = new SqlDecimal(467.45M);
int digits = d.Precision - d.Scale;

SqlDecimalSystem.Data.SqlTypes命名空间的一部分。 &#34;精密&#34;是有效数字的总数,而&#34;比例&#34;是小数点后的位数。

现在,我知道对此路由的一个反对意见是SqlDecimal是SQL Server特定代码的一部分。这是一个有效的观点,但我也要指出它是.NET框架本身的一部分,并且至少从1.1版开始,所以看起来它仍然适用,无论什么围绕它的代码正在做。

我用一个反编译器(在这个例子中为JetBrains' dotPeek)查看引擎盖,看看是否可以轻松提取和使用计算精度和比例的代码,而不需要引入SqlDecimal。计算比例的代码非常简单,但计算精度的方法并不重要,所以如果是我,我只需要通过SqlDecimal

答案 12 :(得分:2)

这样做的数学方法(可能是最快的)是获得这个数字的绝对值的基数10的对数并围绕它 起来。

Math.Floor(Math.Log10(Math.Abs(val)) + 1);

答案 13 :(得分:2)

使用modulo,我不是C#程序员,但我很确定这个解决方案有效:

double i = 1;
int numberOfDecimals = 0;


while (varDouble % i != varDouble)
{
numberOfDecimals++;
i*=10;
}

return numberOfDecimals;

答案 14 :(得分:2)

这个答案几乎可以从Calculate System.Decimal Precision and Scale解除,但只需稍作修改即可适应所提出的问题。

class Program
{
    static void Main()
    {
        decimal dec = 467.45m;
        Console.WriteLine(dec.GetNumberOfDigitsBeforeDecimalPlace());
    }
}

public static class DecimalEx
{
    public static int GetNumberOfDigitsBeforeDecimalPlace(this decimal dec)
    {
        var x = new System.Data.SqlTypes.SqlDecimal(dec);
        return x.Precision - x.Scale;
    }
}

此外,如果您想在不使用SqlDecimal类的情况下执行此操作,请查看Jon Skeet对同一问题的回答。

答案 15 :(得分:2)

TLDR所有其他答案。我用PHP写了这个,数学也是一样的。 (如果我知道C#我会用那种语言写的。)

$input=21689584.999;

    $input=abs($input);
$exp=0;
do{
  $test=pow(10,$exp);

  if($test > $input){
    $digits=$exp;
  }
  if($test == $input){
    $digits=$exp+1;
  }
  $exp++;
}while(!$digits);
if($input < 1){$digits=0;}
echo $digits;

我不怀疑有更好的方法,但我想投入我的$ .02

修改

我在我的评论中提到了我提到的代码,但是没有使用int转换。

function digitCount($input){
  $digits=0;
  $input=abs($input);
    while ($input >= 1) {
      $digits++;
      $input=$input/10;
      //echo $input."<br>";
    }
  return $digits;   
}
$big=(float)(PHP_INT_MAX * 1.1);
echo digitCount($big);

答案 16 :(得分:1)

如果将零或零缺少视为1,则可以。如果你想要零返回零或缺少零来返回零,那么有一些边缘情况要解决,这不应该太难添加。另外,应该使用绝对值来处理负数。还添加了测试用例。

        const decimal d = 123.45m; 
        const decimal d1 = 0.123m;
        const decimal d2 = .567m;
        const decimal d3 = .333m;
        const decimal d4 = -123.45m;

        NumberFormatInfo currentProvider = NumberFormatInfo.InvariantInfo;
        var newProvider = (NumberFormatInfo) currentProvider.Clone();
        newProvider.NumberDecimalDigits = 0;
        string number = d.ToString("N", newProvider);  //returns 123 =  .Length = 3
        string number1 = d1.ToString("N", newProvider); //returns 0 = .Length = 1
        string number2 = d2.ToString("N", newProvider); //returns 1 =  .Length = 1
        string number3 = d3.ToString("N", newProvider); //returns 0 =  .Length = 1
        string number4 = Math.Abs(d4).ToString("N", newProvider); //returns 123 =  .Length = 3

这是一个有点最终的解决方案,如果你发现一个不起作用的测试用例,请告诉我。对于提供的测试用例,它应返回3,0,0,0,3。

        public static int NumbersInFrontOfDecimal(decimal input)
        {
            NumberFormatInfo currentProvider = NumberFormatInfo.InvariantInfo;
            var newProvider = (NumberFormatInfo)currentProvider.Clone();
            newProvider.NumberDecimalDigits = 0;

            var absInput = Math.Abs(input);
            var numbers =  absInput.ToString("N", newProvider);

            //Handle Zero and < 1
            if (numbers.Length == 1 && input < 1.0m)
            {
                return 0;
            }

            return numbers.Length;
        }

答案 17 :(得分:1)

这是我的优化版本的代码灵感来自Gray的答案:

    static int GetNumOfDigits(decimal dTest)
    {
        int nAnswer = 0;

        dTest = Math.Abs(dTest);

        //For loop version
        for (nAnswer = 0; nAnswer < 29 && dTest > 1; ++nAnswer)
        {
            dTest *= 0.1M;
        }

        //While loop version
        //while (dTest > 1)
        //{
        //    nAnswer++;
        //    dTest *= 0.1M;
        //}

        return (nAnswer);
    }

如果您不希望在此函数内调用Math.Abs​​,请务必使用它 在调用GetNumOfDigits之前,在参数函数之外。

我决定删除其他代码以减少我的答案中的混乱,即使他们帮助我达到这一点......

如果需要进行任何改进,请告诉我,我会更新它:)。

答案 18 :(得分:1)

这将是Java解决方案

public class test {
    public static void main(String args[]) {
        float f = 1.123f;
        int a = (int) f;
        int digits = 0;
        while (a > 0) {
            digits++;
            a=a/10;
        }
        System.out.println("No Of digits before decimal="+digits);
    }
}

答案 19 :(得分:1)

为了获得准确且文化上不可知的答案,我会执行以下操作:

  1. 使用System.Numerics.BigInteger,其构造函数接受小数并且似乎不会产生任何舍入错误。
  2. 使用BigInteger.Abs()删除任何信号。
  3. 使用带有“#”格式的BigInteger.ToString()来抑制可能出现的任何分隔符。
  4. 代码

    decimal num = 123213123.123123M;
    int length = BigInteger.Abs((BigInteger)num).ToString("#").Length;
    

答案 20 :(得分:1)

你可以通过舍入数字,然后得到新数字的长度来做到这一点。你可以这样做:

var number = 476.43;
var newNumber = Math.round(number);

//get the length of the rounded number, and subtract 1 if the
//number is negative (remove the negative sign from the count)
int digits = newNumber.ToString().Length - (number < 0 ? 1 : 0);

答案 21 :(得分:0)

<强>算法:

  • |decimal|转换为字符串。
  • 如果小数中存在".",请在其前面剪切,否则请考虑整数。
  • 返回字符串长度。

示例:

3.14 --> 3.14 --> "3.14" --> "3.14".Substring(0,1) --> "3".Length --> 1

-1024 --> 1024 --> "1024" --> IndexOf(".") = -1 --> "1024" --> 4

<强>代码:

static int getNumOfDigits (decimal num)
{
    string d = Math.Abs(num).ToString();

    if (d.IndexOf(".") > -1)
    {
        d = d.Substring(0, d.IndexOf("."));
    }

    return d.Length;
}

答案 22 :(得分:0)

我没有对此进行过测试,但我会保持简单并做到:

decimal value = 467.45;
string str = Convert.toString(value); // convert your decimal type to a string
string[] splitStr = str.split('.'); // split it into an array (use comma separator assuming you know your cultural context)
Console.WriteLine(splitStr[0].Count); // get the first element. You can also get the number of figures after the point by indexing the next value in the array.

这不处理负数。如果你关心那些,那么考虑采取绝对值。此外,如果您希望小数点前的0不被计算,那么您可以使用简单的if语句来检查它。

答案 23 :(得分:0)

如果数字太大,其他解决方案将丢失数字。

public int Digits(Decimal i)
{
    NumberFormatInfo format = CultureInfo.CurrentCulture.NumberFormat;
    var str = Math.Abs(i).ToString().Replace(format.NumberGroupSeparator, "");
    var index = str.IndexOf(format.NumberDecimalSeparator);
    var digits = index == -1 ? str.Length : index;
}

答案 24 :(得分:-1)

简单:

string value = "467.45";
int count =  value.split('.')[0] == "0" ? 0 : value.split('.')[0].ToString().Replace("-","").Count();