Get number of digits before decimal point
Solution 1
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.
Solution 2
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.
Solution 3
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
Solution 4
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
Solution 5
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()
fasadat
Updated on July 05, 2022Comments
-
fasadat almost 2 years
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 return3
. -
Nahum over 10 yearsthis answer is just wrong because decimal can be WAYYY bigger than int
-
Selman Genç over 10 yearsor you could use long :)
-
Kevin Brechbühl over 10 yearsYou are right thanks. It's better to use Math.Truncate. I've updated my answer.
-
hsun324 over 10 yearsThe
Log
functions in theMath
class only handledouble
s. -
Stephan Bauer over 10 yearsI think that's the best solution so far. But I would add
Math.Abs
to handle negative values as well. And maybe check whetherd
is0
) -
fasadat over 10 yearsif the value is larger than max value of double, it again raise exception!
-
fasadat over 10 years@Selman22 decimal d = 99999999999999999.7m; double i = ((double)d).ToString().Length; it return wrong result!
-
Selman Genç over 10 years@fasadat check my answer then.convert it to string and don't worry about value range!
-
astef over 10 yearsOops, forgot about zero )
-
Fedor over 10 yearsFor 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 briefness and clarity -
Stephan Bauer over 10 years@Selman22 Maybe you could please tell us how truncating could help in the code astef provided instead of acting childish?!
-
Fedor over 10 years@Selman22 I've measured it in comparison with Stephan's solution, it's quite precisely, I know what I'm talking about.
-
Taemyr over 10 yearsI 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.
-
Dubu over 10 years@astef You should also include Maths.Abs() in the test:
return Math.Abs(d) < 10 ? 1 : ...
-
Micros over 10 yearsThis solution will count the minus sign for negative numbers. That's why @stephan-bauer 's answer is better.
-
Luaan over 10 yearsYou should also use
ToString(CultureInfo.InvariantCulture)
, or you can get some pretty crazy results. -
usr over 10 yearsHow 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?
-
CodesInChaos over 10 years"Note, that this solution is valid for all decimal values" --- wrong since the conversion to double is lossy. For example
GetNumberOfDigits(999999999999999)
should return15
but returns16
with your code (at least on my computer, doubles aren't consistent across computers). -
CodesInChaos over 10 years@ThomasW. Those don't fit into a
decimal
in the first place. -
CodesInChaos over 10 yearsEven
long
is not big enough to represent alldecimal
s. -
CodesInChaos over 10 yearswrong 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). -
Soner Gönül over 10 years@CodesInChaos Exactly. I think there is no healthy conversation for this case. astef's way is the closest right answer so far (but not exactly right)..
-
Thomas Weller over 10 years@CodesInChaos: Of course that true. I apologize for not reading the question and tags clearly.
-
CodesInChaos over 10 yearsAstef's solution is one of the hopeless approaches. Nothing that starts with converting to
int
,long
ordouble
will work. Some of the string based answers work, but they should useInvariantCulture
, and the division based answers work as well. -
Soner Gönül over 10 years@CodesInChaos On which case my answer doesn't work? It is because only I'm using
long
? Yeah it can't hold every possibledecimal
values. I'm aware of that but is there also another reason? What do you think? -
Jeppe Stig Nielsen over 10 years@CodesInChaos Certainly correct. Since a
double
has a precision of approx. 15 to 17 significant decimal digits, something like9999999999999999993728621.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). -
TecBrat over 10 yearsI think I like the answer by @Vinay Wadhwa better than my own. :-) (unless casting to int causes problems with large numbers)
-
Jack Aidley over 10 yearsYou could probably do this faster by comparing to 1 rather than doing a floor each time.
-
Brian Moths over 10 yearsAlso you can have a test value that you multiply by ten instead of doing a division.
-
Markus over 10 years@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).
-
Jack Aidley over 10 yearsFair enough. I wonder whether that is because of the reducing complexity of the decimal each pass?
-
Stephan over 10 yearsWith value like "0.1", this solution won't work with OP comment, it will return 1 when the OP want the result to be 0.
-
Fedor over 10 years
float
number is notdecimal
.Decimal
type in .NET is significantly larger thanint
, see other similiar answers and comments to them -
Fedor over 10 yearsDid you read this answer before posting yours?
-
aradil over 10 yearsDoes 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).
-
Fedor over 10 yearsGood solution, although TC stated that value like
0.456m
should be treated as zero-digits, while your solution returns 1 for it. -
Fedor over 10 yearsLittle performance note: this solution is about 2 times slower than the original Stephan's solution which uses
ToString()
-
Sebastien H. over 10 yearsthen add an if statement on "value.split('.')[0]" and check it's value if it's equals to 0..
-
Phil Perry over 10 yearsSo 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.
-
CodesInChaos over 10 years@PhilPerry As I posted on other, equivalent answers,
Log10
only works ondouble
, notdecimal
. There are valuesdecimal
can represent, but where this function gives the wrong result. -
Stephan Bauer over 10 yearsI 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 ;-)
-
John Odom over 10 yearsI was going to add to my answer a recursive version but it seems like you already have one. +1.
-
user505255 over 10 years+1, this is exactly how I would have done it. I suppose it's not the most efficient way, though.
-
sidgeon smythe over 10 yearsI 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 writedecimal d = dec < 0 ? decimal.Negate(dec) : dec;
, without wrapping the RHS in adecimal.Floor
). Also, I don't think you need the repeated Floor either: try making your loop justwhile (d >= 10) { cnt++; d = d / 10; }
. How would this compare against your benchmarks? -
sidgeon smythe over 10 yearsAh 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.
-
Fedor over 10 yearsWhat if delimeter will be not
.
but,
? -
Khaled.K over 10 years@Fyodor the decimal point is
.
,,
is not a decimal point but can appear when you trydecimal.ToString("C");
which is currency representation -
Fedor over 10 yearsDon'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 justToString()
can return"3,14"
instead of"3.14"
-
Sebastien H. over 10 yearsi know it's not the best solution, but i will spare you lines of math code, and might be even more performant
-
Gray over 10 years@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.
-
Mr Lister over 10 yearsSo,
0
has 0 digits? That's interesting. -
Gray over 10 years@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
and1.54
as far as number of digits goes. Otherwise, you'd treat them the same. -
Mr Lister over 10 yearsI read the OP's remark as
.1
having no digits before the decimal point, but maybe he was including0
. Oh well. -
Gray over 10 yearsOP 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
and0.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. -
user207421 over 10 yearsThis 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. -
astef over 10 years@EJP
Math.Floor
and explicit cast toint
are not the same for negative values (Log10(x) + 1
is negative for very smallx
). -
astef over 10 years@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 -
Vinay W over 10 yearsthats why it is the
JAVA
solution. -
Fedor over 10 yearsYeah, but OP explicitly specified
decimal
tag in question. Furthermore, as far as I know, where are some kind ofDecimal
type injava
too. -
CodesInChaos about 10 yearsBreaks 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 about 10 yearsdoesn't support negative inputs
-
Khaled.K about 10 yearsis it normal to get random (-1)s without any comment ?
-
Beytan Kurt almost 5 yearsAnswer 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. -
c-sharp-and-swiftui-devni over 4 yearsThis does not work these only gets the total length, not before the decimal point, which was the Op's original request.
-
c-sharp-and-swiftui-devni over 4 yearsPerfect this worked one of the others here didn't this was a simple and nice extension method thanks