Get number of digits before decimal point

前端 未结 25 1830
独厮守ぢ
独厮守ぢ 2020-12-28 11:53

I have a variable of decimal type and I want to check the number of digits before decimal point in it. What should I do? For example, 467.45 should

相关标签:
25条回答
  • 2020-12-28 12:13

    If you treat zeros or lack of zeroes as 1 number, this is OK. If you want zero to return zero or lack of zero to return zero, then there are a few edge cases to work out which shouldn't be too hard to add. Also, should Absolute value to handle negative numbers. Added that test case as well.

            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
    

    Here's a somewhat final solution, if you find a test case that doesn't work, let me know. It should return 3,0,0,0,3 for the test cases provided.

            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;
            }
    
    0 讨论(0)
  • 2020-12-28 12:13

    Algorithm:

    • Convert |decimal| to String.
    • If "." exist in the decimal, cut before it, otherwise consider the whole number.
    • Return string length.

    Example:

    3.14 --> 3.14 --> "3.14" --> "3.14".Substring(0,1) --> "3".Length --> 1
    
    -1024 --> 1024 --> "1024" --> IndexOf(".") = -1 --> "1024" --> 4
    

    Code:

    static int getNumOfDigits (decimal num)
    {
        string d = Math.Abs(num).ToString();
    
        if (d.IndexOf(".") > -1)
        {
            d = d.Substring(0, d.IndexOf("."));
        }
    
        return d.Length;
    }
    
    0 讨论(0)
  • 2020-12-28 12:16

    Solution without converting to string (which can be dangerous in case of exotic cultures):

    static int GetNumberOfDigits(decimal d)
    {
        decimal abs = Math.Abs(d);
    
        return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1);
    }
    

    Note, that this solution is valid for all decimal values

    UPDATE

    In fact this solution does not work with some big values, for example: 999999999999998, 999999999999999, 9999999999999939...

    Obviously, the mathematical operations with double are not accurate enough for this task.

    While searching wrong values I tend to use string-based alternatives proposed in this topic. As for me, that is the evidence that they are more reliable and easy-to-use (but be aware of cultures). Loop-based solutions can be faster though.

    Thanks to commentators, shame on me, lesson to you.

    0 讨论(0)
  • 2020-12-28 12:16

    Instead of converting to string, you can also divide the number by 10 until it equals 0. Interesting is, that the mathematical operations on decimals are much slower than converting the decimal to a string and returning the length (see benchmarks below).
    This solution does not use the Math-methods that take a double as input; so all operations are done on decimals and no casting is involved.

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

    Output is 29. To run this sample, visit this link.


    Side note: some benchmarks show surprising results (10k runs):

    • while ((d = decimal.Floor(d / 10m)) != 0m): 25ms
    • while ((d = d / 10m) > 1m): 32ms
    • ToString with Math-double-operations: 3ms
    • ToString with decimal-operations: 3ms
    • BigInt (see answer of @Heinzi): 2ms

    Also using random numbers instead of always the same value (to avoid possible caching of the decimal to string conversion) showed that the string-based methods are much faster.

    0 讨论(0)
  • 2020-12-28 12:18

    So, I've run into this before, and solved it with this code:

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

    SqlDecimal is part of the System.Data.SqlTypes namespace. "Precision" is the total number of significant digits, while "Scale" is the number of digits after the decimal point.

    Now, I know one objection to going this route is that SqlDecimal is part of the SQL Server-specific code. It's a valid point, but I would also point out that it's a part of the .NET framework itself, and has been since at least version 1.1, so it seems like it would be still be applicable no matter what the code around it is doing.

    I looked under the hood with a decompiler (JetBrains' dotPeek in this instance), to see if maybe the code for calculating precision and scale could be easily extracted and just used, without pulling in SqlDecimal. The code to calculate scale is very simple, but the method to calculate precision is non-trivial, so if it were me, I'd just go through SqlDecimal.

    0 讨论(0)
  • 2020-12-28 12:18

    This answer is pretty much lifted from Calculate System.Decimal Precision and Scale but with a minor change to fit the question asked.

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

    Also if you want to do it without using the SqlDecimal class check out Jon Skeet's answer for the same question.

    0 讨论(0)
提交回复
热议问题