Custom method to return decimals shows strange behavior

I am writing a simple method that will calculate the number of decimal places in a decimal value. The method is as follows:

public int GetDecimalPlaces(decimal decimalNumber) { try { int decimalPlaces = 1; double powers = 10.0; if (decimalNumber > 0.0m) { while (((double)decimalNumber * powers) % 1 != 0.0) { powers *= 10.0; ++decimalPlaces; } } return decimalPlaces; 

I ran it against some test values ​​to make sure everything was working fine, but I return to the last one really weird:

 int test = GetDecimalPlaces(0.1m); int test2 = GetDecimalPlaces(0.01m); int test3 = GetDecimalPlaces(0.001m); int test4 = GetDecimalPlaces(0.0000000001m); int test5 = GetDecimalPlaces(0.00000000010000000001m); int test6 = GetDecimalPlaces(0.0000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001m); 

Tests 1-5 work fine, but test6 returns 23. I know that the value passed exceeds the maximum decimal precision, but why 23? Another thing that I found odd is when I set a breakpoint inside the GetDecimalPlaces method after my call from test6, the decimalNumber inside the method goes through the same value that would be obtained from test5 (20 decimal places), although the value passed , has 20 decimal places 23. Returns.

Perhaps this is simply because I pass in a number that has too many decimal places and everything goes wrong, but I want to make sure that I have not missed something fundamentally wrong here, which may discard the calculations for other values ​​later along the way .

+6
source share
3 answers

The number you are actually checking is as follows:

 0.0000000001000000000100000000 

This is the closest exact decimal value to 0.0000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001.

So the correct answer is actually 20. However, your code gives you 23 because you are using binary floating point arithmetic, for no obvious reason. This will introduce errors into your calculations completely unnecessarily. If you change the use of the decimal sequence, this will be fine:

 public static int GetDecimalPlaces(decimal decimalNumber) { int decimalPlaces = 1; decimal powers = 10.0m; if (decimalNumber > 0.0m) { while ((decimalNumber * powers) % 1 != 0.0m) { powers *= 10.0m; ++decimalPlaces; } } return decimalPlaces; } 
+5
source

(Suggestion) You could calculate as follows:

 public static int GetDecimalPlaces(decimal decimalNumber) { var s = decimalNumber.ToString(); return s.Substring(s.IndexOf(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) + 1).Length; } 
0
source

There is another way to do this, and it probably works faster because it uses the remainder operation only if the decimal number has a "finite zero" problem.

Main idea:

In .NET, any decimal value is stored in memory in the form

m * Math.Power (10, -p)

where m is the mantissa (size 96 bits) and p is the order (value from 0 to 28).

The decimal.GetBits method extracts this representation from the decimal structure and returns it as an array from int (length 4).

Using this data, we can build another decimal number. If we use only the mantis, without the "Math.Power (10, -p)" part, the result will be an integral decimal. And if this decimal integer is divisible by 10, then our original number has one or more trailing zeros.

So here is my code

  static int GetDecimalPlaces(decimal value) { // getting raw decimal structure var raw = decimal.GetBits(value); // getting current decimal point position int decimalPoint = (raw[3] >> 16) & 0xFF; // using raw data to create integral decimal with the same mantissa // (note: it always will be absolute value because I do not analyze // the sign information of source number) decimal integral = new decimal(raw[0], raw[1], raw[2], false, 0); // disposing from trailing zeros while (integral > 0 && integral % 10 == 0) { decimalPoint--; integral /= 10; } // returning the answer return decimalPoint; } 
0
source

All Articles