65

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 return 3.

mavis
  • 3,100
  • 3
  • 24
  • 32
fasadat
  • 1,025
  • 1
  • 12
  • 27
  • 27
    Apparently no-one's worried whether the current culture's `ToString()` will include thousands separators. – Rawling Feb 04 '14 at 07:57
  • 2
    I don't want to be a great PIA but "Should return 3!" might be confusing from mathematical point of view. It's looks kinda like "40-32/2 is 4!" – luk32 Feb 04 '14 at 10:33
  • 19
    does `0.1` have one or zero digits before the decimal point? – CodesInChaos Feb 04 '14 at 13:11
  • 3
    @CodesInChaos zeore digit I think – fasadat Feb 04 '14 at 13:25
  • @fasadat edited my answer to contain less clutter and an optimized code. It became to be pretty much like Gray's answer but an incremental while loop and for loop version. – John Odom Feb 04 '14 at 21:07
  • @Rawling Is it not possible to specify that you want to use the invariant culture when turning a number to a string? – Patashu Feb 05 '14 at 03:05

25 Answers25

83

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.

astef
  • 8,575
  • 4
  • 56
  • 95
  • 4
    I think that's the best solution so far. But I would add `Math.Abs` to handle negative values as well. And maybe check whether `d`is `0`) – Stephan Bauer Feb 04 '14 at 08:11
  • 1
    Oops, forgot about zero ) – astef Feb 04 '14 at 08:38
  • 3
    For anyone who is going to call this method million times and concerned about performance: even with all this calls to `Math` methods, it's still at least 2 times faster than "truncate" solution, however I would prefer [Stephan's answer](http://stackoverflow.com/a/21546915/1177964) briefness and clarity – Fedor Feb 04 '14 at 08:49
  • 3
    @Selman22 Maybe you could please tell us how truncating could help in the code astef provided instead of acting childish?! – Stephan Bauer Feb 04 '14 at 08:49
  • 2
    @Selman22 I've measured it in comparison with Stephan's solution, it's quite precisely, I know what I'm talking about. – Fedor Feb 04 '14 at 08:58
  • 3
    I think it might be better to test if absolute value is less than 1, rather than just for zero. It's an edge case, but one might not want 0.0001 to return as -4 digits before the decimal point. – Taemyr Feb 04 '14 at 09:53
  • @astef You should also include Maths.Abs() in the test: `return Math.Abs(d) < 10 ? 1 : ...` – Dubu Feb 04 '14 at 10:11
  • 3
    How can you be so sure that this solution will work for *all* possible values? Even ones that are huge or that are a tiny fraction above or below a power of ten? – usr Feb 04 '14 at 13:03
  • 8
    "Note, that this solution is valid for all decimal values" --- wrong since the conversion to double is lossy. For example `GetNumberOfDigits(999999999999999)` should return `15` but returns `16` with your code (at least on my computer, doubles aren't consistent across computers). – CodesInChaos Feb 04 '14 at 13:05
  • 2
    @CodesInChaos Certainly correct. Since a `double` has a precision of approx. 15 to 17 significant decimal digits, something like `9999999999999999993728621.07m` will have to be rounded (up or down, depending on where the nearest multiple of the relevant power of two is, but in some (~50%) cases it will surely matter). – Jeppe Stig Nielsen Feb 04 '14 at 13:51
  • This doesn't work correctly for `0 < n < 1`. The conditional is actually unnecessary. The part after the `:` is all you need. `Math.Floor()` is also redundant. Zero can be handled as a special case. Values > 999999999999997 won't work correctly. – user207421 Feb 06 '14 at 03:55
  • @EJP `Math.Floor` and explicit cast to `int` are not the same for negative values (`Log10(x) + 1` is negative for very small `x`). – astef Feb 06 '14 at 07:10
  • 1
    @EJP About `0..1` values - you're right, answer need to be updated due to OP's new comments. And still some of the big parameters lead to a wrong results as it stated in an update text – astef Feb 06 '14 at 07:13
  • Answer should be `static int GetNumberOfDigits(decimal d) { decimal abs = Math.Abs(d); return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1); }` since GetNumberOfDigits(0.5m) should return 1 instead of 0. – Beytan Kurt Jul 01 '19 at 15:14
34

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.

Community
  • 1
  • 1
Markus
  • 20,838
  • 4
  • 31
  • 55
  • You could probably do this faster by comparing to 1 rather than doing a floor each time. – Jack Aidley Feb 04 '14 at 15:34
  • 1
    Also you can have a test value that you multiply by ten instead of doing a division. – Brian Moths Feb 04 '14 at 15:45
  • 1
    @JackAidley: thanks for your input - I did a short "benchmark" with both solutions (Floor vs. > 1m). I'm surprised that Floor is faster (10k runs, 25ms for Floor, 32ms for > 1m). – Markus Feb 04 '14 at 15:52
  • Fair enough. I wonder whether that is because of the reducing complexity of the decimal each pass? – Jack Aidley Feb 04 '14 at 16:02
  • Good solution, although TC stated that value like `0.456m` should be treated as zero-digits, while your solution returns 1 for it. – Fedor Feb 04 '14 at 16:38
  • Little performance note: this solution is about 2 times slower than the **[original Stephan's solution](http://stackoverflow.com/a/21546915/1177964)** which uses `ToString()` – Fedor Feb 04 '14 at 16:50
  • I don't know C#, but I'd guess you don't need the `decimal.Floor` in the first line of your function, to take absolute value (that is, you can just write `decimal d = dec < 0 ? decimal.Negate(dec) : dec;`, without wrapping the RHS in a `decimal.Floor`). Also, I don't think you need the repeated Floor either: try making your loop just `while (d >= 10) { cnt++; d = d / 10; }`. How would this compare against your benchmarks? – ShreevatsaR Feb 05 '14 at 05:24
  • Ah now I reread the earlier comments and see why you have the decimal.Floor in the first line... but the simpler loop is worth trying out. – ShreevatsaR Feb 05 '14 at 05:33
27

I would try this:

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

If you want to be sure not having some weird results for different cultures and with negative decimals, you better use this:

var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length
Kevin Brechbühl
  • 4,717
  • 3
  • 24
  • 47
13

I would prefer the following instead of casting to int to ensure that you can also handle big numbers (e.g. decimal.MaxValue):

Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length
Stephan Bauer
  • 9,120
  • 5
  • 36
  • 58
  • Does the 4 #s here have any significance? In the documentation, 5 appears to be used for showing all numbers and 2 #s says it will round the least significant digit (although when I tested it in LINQPad, it didn't do that). – aradil Feb 04 '14 at 16:29
  • I just wanted to avoid thousand separators. But it's a quick'n'dirty and not absolutely reliable solution. I'm sure there are way better approaches ;-) – Stephan Bauer Feb 04 '14 at 19:53
7
decimal d = 467.45M;
int i = (int)d;
Console.WriteLine(i.ToString(CultureInfo.InvariantCulture).Length); //3

As a method;

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

Note: Of course you should check first your decimals full part is bigger than Int32.MaxValue or not. Because if it is, you get an OverflowException.

Is such a case, using long instead of int can a better approach. However even a long (System.Int64) is not big enough to hold every possible decimal value.

As Rawling mentioned, your full part can hold the thousands separator and my code will be broken in such a case. Because in this way, it totally ignores my number contains NumberFormatInfo.NumberGroupSeparator or not.

That's why getting numbers only is a better approach. Like;

i.ToString().Where(c => Char.IsDigit(c)).ToArray()
Community
  • 1
  • 1
Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
  • 1
    or you could use **long** :) – Selman Genç Feb 04 '14 at 08:05
  • if the value is larger than max value of double, it again raise exception! – fasadat Feb 04 '14 at 08:12
  • @Selman22 decimal d = 99999999999999999.7m; double i = ((double)d).ToString().Length; it return wrong result! – fasadat Feb 04 '14 at 08:22
  • @fasadat check my answer then.convert it to string and don't worry about value range! – Selman Genç Feb 04 '14 at 08:24
  • 1
    Even `long` is not big enough to represent all `decimal`s. – CodesInChaos Feb 04 '14 at 13:16
  • @CodesInChaos Exactly. I think there is no healthy conversation for this case. [astef's way](http://stackoverflow.com/a/21546928/447156) is the closest right answer so far (but not exactly right).. – Soner Gönül Feb 04 '14 at 13:29
  • 2
    Astef's solution is one of the hopeless approaches. Nothing that starts with converting to `int`, `long` or `double` will work. Some of the string based answers work, but they should use `InvariantCulture`, and the division based answers work as well. – CodesInChaos Feb 04 '14 at 13:41
  • @CodesInChaos On which case my answer doesn't work? It is because only I'm using `long`? Yeah it can't hold every possible `decimal` values. I'm aware of that but is there also another reason? What do you think? – Soner Gönül Feb 04 '14 at 13:43
6

Here's a recursive example (mostly for fun).

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
}
Gray
  • 7,050
  • 2
  • 29
  • 52
  • 1
    I was going to add to my answer a recursive version but it seems like you already have one. +1. – John Odom Feb 04 '14 at 20:44
  • 1
    +1, this is exactly how I would have done it. I suppose it's not the most efficient way, though. – user505255 Feb 05 '14 at 04:12
  • @user505255 It is indeed likely to be less efficient than doing the same operation with a while loop, what with the overhead involved with recursion... It is a pretty clean example that lends itself to recursion, though. In my opinion it is probably more readable than the accepted answer, but I imagine the easiest one for developers to understand at a glance would simply be a toString()-based solution... but I suppose that has its own problems. – Gray Feb 05 '14 at 13:36
  • So, `0` has 0 digits? That's interesting. – Mr Lister Feb 05 '14 at 14:39
  • @MrLister The OP actually says he thinks that is the correct answer (see his comments on the question itself). We could modify the function to not do that pretty easily though. It let's you have a distinction between `.54` and `1.54` as far as number of digits goes. Otherwise, you'd treat them the same. – Gray Feb 05 '14 at 14:49
  • I read the OP's remark as `.1` having no digits before the decimal point, but maybe he was including `0`. Oh well. – Mr Lister Feb 05 '14 at 15:25
  • OP wasn't exactly explicit - I'll give you that. But it would be very frustrating to me if a program returned a different result for `.1` and `0.1` since they are equal. It all depends on what this would actually be used for. If it was to teach kids about decimals, you may have a different answer than if you were writing a program for a satellite that accounts for special relativity. – Gray Feb 05 '14 at 15:45
5

Math.Floor(Math.Log10((double)n) + 1); is the way to go.

Converting to int is BAD because decimal may be bigger than 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(); is bad because it may include thousands seperators.

Fedor
  • 1,548
  • 3
  • 28
  • 38
Nahum
  • 6,959
  • 12
  • 48
  • 69
  • 2
    The `Log` functions in the `Math` class only handle `double`s. – hsun324 Feb 04 '14 at 08:07
  • 1
    wrong since the conversion to double is lossy. For example `GetNumberOfDigits(999999999999999)` should return 15 but returns 16 with your code (at least on my computer, doubles aren't consistent across computers). – CodesInChaos Feb 04 '14 at 13:17
5

If you have a bias towards smaller numbers, you can use something more simple like this.

It is split up into two methods, so the first method is smaller and can be inlined.

Performance is about the same as the solution with the Log10, but without the rounding errors. The Method using Log10, is still the fastest (a bit) specially for numbers > 1 million.

    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
    }

This code is generated using the following T4 template:

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

    }
}
GvS
  • 52,015
  • 16
  • 101
  • 139
4

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.

nateirvin
  • 1,173
  • 1
  • 10
  • 28
3
var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
var count = d.ToString().TakeWhile(c => c != sep).Count();
w.b
  • 11,026
  • 5
  • 30
  • 49
3

This will do if you really don't want to use the Log method (which IMO is the best way). It's about the clearest way I can think of of doing this using ToString():

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

Or alternatively, if you don't want to count 0.123M as having one digit:

Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length
Luaan
  • 62,244
  • 7
  • 97
  • 116
3

You could use ToString function with a custom format.

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

The # specify you only want the characters before the .

The System.Globalization.CultureInfo.InvariantCulture ensure you won't get any formating from the Region Option.

Stephan
  • 591
  • 5
  • 18
3

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.

Community
  • 1
  • 1
Robert
  • 3,328
  • 2
  • 24
  • 25
2

TLDR all the other answers. I wrote this in PHP, and the math would be the same. (If I knew C# I'd have written in that language.)

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

I don't doubt there's a better way, but I wanted to throw in my $.02

EDIT:

I php-ized the code I mentioned in my comments, but did away with the int conversion.

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);
TecBrat
  • 3,643
  • 3
  • 28
  • 45
  • I think I like the answer by @Vinay Wadhwa better than my own. :-) (unless casting to int causes problems with large numbers) – TecBrat Feb 04 '14 at 14:57
2

The mathematical way of doing this (and probably the fastest) is to get a logarytm of base 10 of a absolute value of this number and round it up.

Math.Floor(Math.Log10(Math.Abs(val)) + 1);
Kubuxu
  • 295
  • 2
  • 12
  • 1
    Did you read **[this answer](http://stackoverflow.com/a/21546928/1177964)** before posting yours? – Fedor Feb 04 '14 at 16:23
  • So long as Log10() is well-behaved for your data type (that it can handle any decimal number thrown at it), @Kubuxu's formula is the correct way. No need for snide remarks or cultural dependencies on how a number gets formatted. If Log10() _can't_ handle a range of decimal numbers, that would be a problem. Other posted solutions involving repeated division by 10 are doing the same thing, in a roundabout (and slower) manner. – Phil Perry Feb 04 '14 at 17:03
  • 2
    @PhilPerry As I posted on other, equivalent answers, `Log10` only works on `double`, not `decimal`. There are values `decimal` can represent, but where this function gives the wrong result. – CodesInChaos Feb 04 '14 at 18:08
2

Use modulo, i'm not a C# programmer, but I'm pretty sure this solution work:

double i = 1;
int numberOfDecimals = 0;


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

return numberOfDecimals;
max890
  • 517
  • 2
  • 9
1

This would be the Java solution

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);
    }
}
Vinay W
  • 9,912
  • 8
  • 41
  • 47
  • 1
    `float` number is not `decimal`. `Decimal` type in .NET is significantly larger than `int`, see other similiar answers and comments to them – Fedor Feb 04 '14 at 16:21
  • thats why it is the `JAVA` solution. – Vinay W Feb 06 '14 at 10:37
  • Yeah, but OP explicitly specified `decimal` tag in question. Furthermore, as far as I know, where are some kind of `Decimal` type in `java` too. – Fedor Feb 06 '14 at 11:09
1

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;
        }
Jon Raynor
  • 3,804
  • 6
  • 29
  • 43
1

Here is my optimized version of the code inspired by Gray's answer:

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

If you don't want the Math.Abs to be called inside this function then be sure to use it outside the function on the parameter before calling GetNumOfDigits.

I decided to remove the other codes to reduce clutter in my answer, even though they helped me get to this point...

If there is any improvements needed, then let me know and I will update it :).

John Odom
  • 1,189
  • 2
  • 20
  • 35
1

In order to get an accurate and culturally agnostic answer I do the following:

  1. Use System.Numerics.BigInteger, whose constructor accepts a decimal and doesn't seem to produce any rounding errors.
  2. Use BigInteger.Abs() to remove any sign.
  3. Use BigInteger.ToString() with the "#" format to suppress any separators that might occur.

Code

decimal num = 123213123.123123M;
int length = BigInteger.Abs((BigInteger)num).ToString("#").Length;
Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70
1

You could do this by rounding the number, then getting the length of the new number. You could do it like this:

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);
Jojodmo
  • 23,357
  • 13
  • 65
  • 107
0

The other solutions will lose digits if the number is too large.

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;
}
hsun324
  • 549
  • 3
  • 9
0

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;
}
Khaled.K
  • 5,828
  • 1
  • 33
  • 51
  • What if delimeter will be not `.` but `,`? – Fedor Feb 05 '14 at 07:37
  • @Fyodor the decimal point is `.`, `,` is not a decimal point but can appear when you try `decimal.ToString("C");` which is currency representation – Khaled.K Feb 05 '14 at 09:28
  • 2
    Don't forget about other cultures :) I'm saying that decimal delimeter is depends on culture under which thread is running. It's not _always_ `.`, so just `ToString()` can return `"3,14"` instead of `"3.14"` – Fedor Feb 05 '14 at 09:40
  • is it normal to get random (-1)s without any comment ? – Khaled.K Feb 13 '14 at 08:44
0

I haven't tested this but I would keep it straightforward and do:

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.

This does not handle negative numbers. If you care about those then considering taking the absolute value. Furthermore, if you want 0 before the decimal place to not be counted then you can use a simple if statement to check it.

rex
  • 3,133
  • 6
  • 35
  • 62
  • Breaks on cultures with a different decimal separator. Either use `InvariantCulture` to convert to string (preferred), or query the current culture for the decimal separator. – CodesInChaos Feb 12 '14 at 18:38
-1

simple :

string value = "467.45";
int count =  value.split('.')[0] == "0" ? 0 : value.split('.')[0].ToString().Replace("-","").Count();
Sebastien H.
  • 6,818
  • 2
  • 28
  • 36