25

I have a Double value:

double a = 4.5565;

What is the easiest way to calculate the number of digits after the decimal point (4 in this case).

I know that I can convert to string and do a split and take the length. But is there an easier way?

blitzkriegz
  • 9,258
  • 18
  • 60
  • 71
  • what do you mean by "easier"? I doubt there is anything easier from writing/reading perspective, but there might be some way easier in terms of CPU operations for example. – Snowbear Feb 21 '12 at 23:01
  • 2
    This is not actually possible. http://en.wikipedia.org/wiki/Double-precision_floating-point_format – SLaks Feb 21 '12 at 23:02
  • 2
    The value of `a` will probably not be exactly 4.5565. If you don't understand why, I suggest you read this: http://csharpindepth.com/Articles/General/FloatingPoint.aspx – Thomas Levesque Feb 21 '12 at 23:03
  • It depends, what kind of decimals do you want to count? The ones that would appear in C#'s ToString? The ones that represent real accuracy? Or all of them that would appear in a full base 10 conversion of the value? – harold Feb 21 '12 at 23:03
  • 2
    Note that this is possible for `Decimal`. – SLaks Feb 21 '12 at 23:08
  • @blitzkriegz Take a look at my answer, it include only math. tell what do you think on it. – Dor Cohen Feb 21 '12 at 23:34
  • Maybe this might be a solution: [http://stackoverflow.com/questions/7346371/double-datatype-count-decimals-after-decimal-place/35238462#35238462](http://stackoverflow.com/questions/7346371/double-datatype-count-decimals-after-decimal-place/35238462#35238462) – Koray Feb 06 '16 at 07:23

8 Answers8

19

There's no easy way, especially since the number of digits mathematically speaking might be far more than displayed. For example, 4.5565 is actually stored as 4.556499999999999772626324556767940521240234375 (thanks to harold for calculating that). You're very unlikely to find a useful solution to this problem.

EDIT

You could come up with some algorithm that works like this: if, as you calculate the decimal representation, you find a certain number of 9s (or zeros) in succession, you round up (or down) to the last place before the series of 9s (or zeros) began. I suspect that you would find more trouble down that road than you would anticipate.

Community
  • 1
  • 1
phoog
  • 42,068
  • 6
  • 79
  • 117
  • @phoog I agree with what you say in general but what do you think on my answer? – Dor Cohen Feb 21 '12 at 23:29
  • @DorCohen I don't have time right now to look at your answer in depth. I'll try to have a look later tonight or tomorrow morning. – phoog Feb 21 '12 at 23:32
  • 2
    Previous comment accidentally was as 32bit float, it should be: 4.556499999999999772626324556767940521240234375 – harold Feb 21 '12 at 23:33
  • @phoog Where can I read about why it is stored as 4.556499999999999772626324556767940521240234375 – blitzkriegz Feb 22 '12 at 00:26
  • @blitzkriegz the links that others suggested in comments on your question are good discussions of the IEEE floating point system. I didn't think it would be necessary to add to them. The short answer is that the binary representation is in base 2, and there are many values that are finite decimals in base 10 but infinite in base 2; these have to be truncated (similar to 0.3333333 being a truncated decimal approximation of 1/3). The number that harold calculated is the exact decimal representation of the inexact binary approximation of the value 4.5565. – phoog Feb 22 '12 at 04:33
  • @blitzkriegz as an exercise, calculate the binary representation of 0.625; then calculate the binary representation of 0.2. – phoog Feb 22 '12 at 04:36
  • The binary representation of 0.625 is `0.101` (the sum of a half and an eighth). The binary representation of 0.2 is `0.001100110011...` (for example, the sum of the series `3/16^n` where n ranges from 1 to infinity). – phoog May 21 '19 at 16:41
19
var precision = 0;
var x = 1.345678901m;

while (x*(decimal)Math.Pow(10,precision) != 
         Math.Round(x*(decimal)Math.Pow(10,precision))) 
   precision++;

precision will be equal to the number of significant digits of the decimal value (setting x to 1.23456000 will result in a precision of 5 even though 8 digits were originally specified in the literal). This executes in time proportional to the number of decimal places. It counts the number of fractional digits ONLY; you can count the number of places to the left of the decimal point by taking the integer part of Math.Log10(x). It works best with decimals as they have better value precision so there is less rounding error.

NoWar
  • 36,338
  • 80
  • 323
  • 498
KeithS
  • 70,210
  • 21
  • 112
  • 164
4

Write a function

int CountDigitsAfterDecimal(double value)
        {
            bool start = false;
            int count = 0;
            foreach (var s in value.ToString())
            {
                if (s == '.')
                {
                    start = true;
                }
                else if (start)
                {
                    count++;
                }
            }

            return count;
        }
CloudyMarble
  • 36,908
  • 70
  • 97
  • 130
user1996255
  • 57
  • 1
  • 1
  • 1
    This won't work correctly for numbers expressed in standard form (e.g. 5.821E+12) because the decimal point will always come second. In the example given, the result should actually be 0 because 5.821E+12 is a very large whole number. – kiml42 Sep 03 '20 at 14:09
  • 1
    An additional consideration is that some cultures don't use '.' as the decimal separator, so your "ToString()" should be given "CultureInfo.InvariantCulture". – kiml42 Sep 03 '20 at 14:10
4

I think this might be a solution:

 private static int getDecimalCount(double val)
 {
     int i=0;
     while (Math.Round(val, i) != val)
         i++;
     return i;
 }

double val9 = 4.5565d; int count9 = getDecimalCount(val9);//result: 4

Sorry for the duplication -> https://stackoverflow.com/a/35238462/1266873

Edit: Workaround for @chri3g91 's mentioned error:

public static class DecimalExtensions
{
    public static int GetDecimalCount(this double val)
    {
        int i = 0;
        //Doubles can be rounded to 15 digits max. ref: https://stackoverflow.com/a/33714700/1266873
        while (i < 16 && Math.Round(val, i) != val)
            i++;
        return i;
    }
    public static int GetDecimalCount(this decimal val)
    {
        int i = 0;
        while (Math.Round(val, i) != val)
            i++;
        return i;
    }
}
 
Koray
  • 1,768
  • 1
  • 27
  • 37
  • 1
    Note: this will throw an ArgumentOutOfRange Exception, if val is a negative number. Ex.Message "Rounding digits must be between 0 and 15, inclusive." – chri3g91 Mar 30 '23 at 09:18
4

base on james answer bat much clearer:

int num = dValue.ToString().Length - (((int)dValue).ToString().Length + 1);

num is the exact number of digits after the decimal point. without including 0 like this(25.520000) in this case, you will get num= 2

batsheva
  • 2,175
  • 1
  • 20
  • 32
  • There is a major flaw with this answer if no decimal points are present, as that will result in -1 decimal spaces rather than 0 which would be the correct answer – RivenSkaye May 16 '22 at 07:27
2

I Think String solution is best : ((a-(int)a)+"").length-2

james
  • 1,758
  • 1
  • 16
  • 26
  • instead of splitting, you just don't count `0.` in string. Not much different than blitzkriegz's solution – L.B Feb 21 '12 at 23:10
  • @L.B true, but in my solution no need to allocate array. it is a little better. – james Feb 21 '12 at 23:22
  • 8
    Adding "" is a truly horrible alternative to calling `ToString()`. – Jon Skeet Feb 21 '12 at 23:43
  • 1
    @JonSkeet Why is that ? it will always work where toString might fail if an object is null (not in this case of course where a is primitive). – james Feb 21 '12 at 23:52
  • 2
    Personally, I wouldn't like to see this as it obfuscates the intention of the code. When you see a ToString() call, you know what the coder was trying to do, and it's not usually difficult to find the implementation of the string conversion. String concatenation strips the intention and requires some more knowledge and time to figure out what will happen. This way is too 'clever' (clever code being a bad thing for maintainability). – TheEvilPenguin Feb 22 '12 at 00:21
  • 2
    Nice idea, but you also should check if 'a' is negativ. – user1027167 Sep 01 '16 at 13:41
  • Why would you use string concatenation instead of `.ToString()`? Also, the cast to int will fail when the value is above Integer.MaxValue. – Aashishkebab Jul 20 '22 at 19:59
1

I'll perhaps use this code if I needed,

myDoubleNumber.ToString("R").Split('.')[1].Length

"R" here is Round Trip Format Specifier

We need to check for the index bounds first of course.

Nikhil Girraj
  • 1,135
  • 1
  • 15
  • 33
-1

Another solution would be to use some string functions:

private int GetSignificantDecimalPlaces(decimal number, bool trimTrailingZeros = true)
{
  var stemp = Convert.ToString(number);
  
  if (stemp.IndexOf(Application.CurrentCulture.NumberFormat.NumberDecimalSeparator) < 0)
    return 0;

  if (trimTrailingZeros)
    stemp = stemp.TrimEnd('0');

  return stemp.Length - 1 - stemp.IndexOf(Application.CurrentCulture.NumberFormat.NumberDecimalSeparator);
}

Remember to use System.Windows.Forms to get access to Application.CurrentCulture

RooiWillie
  • 2,198
  • 1
  • 30
  • 36