22

Lets say that input from the user is a decimal number, ex. 5.2155 (having 4 decimal digits). It can be stored freely (int,double) etc.

Is there any clever (or very simple) way to find out how many decimals the number has? (kinda like the question how do you find that a number is even or odd by masking last bit).

Milan
  • 15,389
  • 20
  • 57
  • 65

15 Answers15

21

Two ways I know of, neither very clever unfortunately but this is more a limitation of the environment rather than me :-)

The first is to sprintf the number to a big buffer with a "%.50f" format string, strip off the trailing zeros then count the characters after the decimal point. This will be limited by the printf family itself. Or you could use the string as input by the user (rather than sprintfing a floating point value), so as to avoid floating point problems altogether.

The second is to subtract the integer portion then iteratively multiply by 10 and again subtract the integer portion until you get zero. This is limited by the limits of computer representation of floating point numbers - at each stage you may get the problem of a number that cannot be represented exactly (so .2155 may actually be .215499999998). Something like the following (untested, except in my head, which is about on par with a COMX-35):

count = 0
num = abs(num)
num = num - int(num)
while num != 0:
    num = num * 10
    count = count + 1
    num = num - int(num)

If you know the sort of numbers you'll get (e.g., they'll all be 0 to 4 digits after the decimal point), you can use standard floating point "tricks" to do it properly. For example, instead of:

while num != 0:

use

while abs(num) >= 0.0000001:
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 1
    .2155 is not going to become .21559999999999, but .2156 could – quant_dev Jul 05 '09 at 02:05
  • 1
    No-one likes a smart-alec :-) Thanks, @quant_dev, I fixed it. – paxdiablo Jul 05 '09 at 02:06
  • 2
    I would classify as clever only because of how thoroughly you answered the question. – ojblass Jul 05 '09 at 07:49
  • 3
    Shouldn't `while abs(num) <= 0.0000001:` be `while abs(num) >= 0.0000001:` ? – Guillaume Algis Jul 12 '13 at 11:21
  • 2
    @Guillaume, yes, congrats on being the first person to pick up that error in four _years._ Fixed as per your suggestion. – paxdiablo Jul 12 '13 at 11:49
  • Certainly code should be `while fabs(num) >= 0.0000001` (fabs vs. abs). Code posted may be pseudo code, yet important to use the proper absolute value function in real code. – chux - Reinstate Monica Dec 29 '14 at 22:45
  • Within the while loop, the ending num is the fractional part. If this is 0.99999999999, then the loop will go on forever. What works for me is the following: while abs(num - roundtonearestinteger(num)) >= epsilon – Tryer Jul 31 '18 at 04:57
  • I posted an answer below which is similar. `while val - std::numeric_limits::epsilon() > std::numeric_limits::epsilon()` is I think the "correct" comparison rather than hard coding an arbitrary 0.00000001 value. – Russell Trahan Feb 11 '21 at 22:35
8

Once the number is converted from the user representation (string, OCR-ed gif file, whatever) into a floating point number, you are not dealing with the same number necessarily. So the strict, not very useful answer is "No".

If (case A) you can avoid converting the number from the string representation, the problem becomes much easier, you only need to count the digits after the decimal point and subtract the number of trailing zeros.

If you cannot do it (case B), then you need to make an assumption about the maximum number of decimals, convert the number back into string representation and round it to this maximum number using the round-to-even method. For example, if the user supplies 1.1 which gets represented as 1.09999999999999 (hypothetically), converting it back to string yields, guess what, "1.09999999999999". Rounding this number to, say, four decimal points gives you "1.1000". Now it's back to case A.

quant_dev
  • 6,181
  • 1
  • 34
  • 57
7

Off the top of my head:

start with the fractional portion: .2155

repeatedly multiply by 10 and throw away the integer portion of the number until you get zero. The number of steps will be the number of decimals. e.g:

.2155 * 10 = 2.155
.155 * 10 = 1.55
.55 * 10 = 5.5
.5 * 10 = 5.0

4 steps = 4 decimal digits

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
3

Something like this might work as well:

float i = 5.2154;
std::string s;
std::string t;
std::stringstream out;
out << i;
s = out.str();

t = s.substr(s.find(".")+1);
cout<<"number of decimal places: " << t.length();
Sev
  • 15,401
  • 9
  • 56
  • 75
  • 1
    You'd somehow need to account for numbers that don't fit neatly into a float, ie if you end up with 5.215399999999999, you probably want to report 4 decimal places. – Mike Kale Jul 05 '09 at 02:09
2

What do you mean "stored freely (int"? Once stored in an int, it has zero decimals left, clearly. A double is stored in a binary form, so no obvious or simple relation to "decimals" either. Why don't you keep the input as a string, just long enough to count those decimals, before sending it on to its final numeric-variable destination?

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
2

using the Scientific Notation format (to avoid rounding errors):

#include <stdio.h>
#include <string.h>

/* Counting the number of decimals
 *
 * 1. Use Scientific Notation format
 * 2. Convert it to a string
 * 3. Tokenize it on the exp sign, discard the base part
 * 4. convert the second token back to number
*/

int main(){

   int counts;
   char *sign;
   char str[15];
   char *base;
   char *exp10;
   float real = 0.00001;

   sprintf (str, "%E",  real);
   sign= ( strpbrk ( str, "+"))? "+" : "-";

   base = strtok (str, sign);
   exp10 = strtok (NULL, sign);

   counts=atoi(exp10);

   printf("[%d]\n", counts);

   return 0;
}

[5]

1

Years after the fight but as I have made my own solution in three lines :

string number = "543.014";    
size_t dotFound;
stoi(number, &dotFound));
string(number).substr(dotFound).size()

Of course you have to test before if it is really a float (With stof(number) == stoi(number) for example)

Holywa
  • 51
  • 1
  • 1
  • 10
1

If the decimal part of your number is stored in a separate int, you can just count the its decimal digits.

This is a improvement on andrei alexandrescu's improvement. His version was already faster than the naive way (dividing by 10 at every digit). The version below is constant time and faster at least on x86-64 and ARM for all sizes, but occupies twice as much binary code, so it is not as cache-friendly.

Benchmarks for this version vs alexandrescu's version on my PR on facebook folly.

Works on unsigned, not signed.

inline uint32_t digits10(uint64_t v) {
  return  1
        + (std::uint32_t)(v>=10)
        + (std::uint32_t)(v>=100)
        + (std::uint32_t)(v>=1000)
        + (std::uint32_t)(v>=10000)
        + (std::uint32_t)(v>=100000)
        + (std::uint32_t)(v>=1000000)
        + (std::uint32_t)(v>=10000000)
        + (std::uint32_t)(v>=100000000)
        + (std::uint32_t)(v>=1000000000)
        + (std::uint32_t)(v>=10000000000ull)
        + (std::uint32_t)(v>=100000000000ull)
        + (std::uint32_t)(v>=1000000000000ull)
        + (std::uint32_t)(v>=10000000000000ull)
        + (std::uint32_t)(v>=100000000000000ull)
        + (std::uint32_t)(v>=1000000000000000ull)
        + (std::uint32_t)(v>=10000000000000000ull)
        + (std::uint32_t)(v>=100000000000000000ull)
        + (std::uint32_t)(v>=1000000000000000000ull)
        + (std::uint32_t)(v>=10000000000000000000ull);
}
Gabriel
  • 2,841
  • 4
  • 33
  • 43
  • 3
    This counts the number of digits in an integer, not the number of decimal digits as the question asks. – keith Aug 02 '20 at 18:46
0

This is a robust C++ 11 implementation suitable for float and double types:

template <typename T>
std::enable_if_t<(std::is_floating_point<T>::value), std::size_t>
decimal_places(T v)
{
    std::size_t count = 0;

    v = std::abs(v);

    auto c = v - std::floor(v);

    T factor = 10;

    T eps = std::numeric_limits<T>::epsilon() * c;

    while ((c > eps && c < (1 - eps)) && count < std::numeric_limits<T>::max_digits10)
    {
        c = v * factor;

        c = c - std::floor(c);
            
        factor *= 10;

        eps = std::numeric_limits<T>::epsilon() * v * factor;

        count++;
    }

    return count;
}

It throws the value away each iteration and instead keeps track of a power of 10 multiplier to avoid rounding issues building up. It uses machine epsilon to correctly handle decimal numbers that cannot be represented exactly in binary such as the value of 5.2155 as stipulated in the question.

keith
  • 5,122
  • 3
  • 21
  • 50
0
int main()
{
char s[100];
fgets(s,100,stdin);
unsigned i=0,sw=0,k=0,l=0,ok=0;
unsigned length=strlen(s);
for(i=0;i<length;i++)
{
    if(isprint(s[i]))
    {
        if(sw==1)
        {
            k++;
            if(s[i]=='0')
            {
                ok=0;
            }
            if(ok==0)
            {
                if(s[i]=='0')
                    l++;
                else
                {
                    ok=1;
                    l=0;
                }
            }
        }
        if(s[i]=='.')
        {
            sw=1;
        }
    }
}
printf("%d",k-l);
return 0;
}
0

Here is yet another implementation. It's in C, but it can be freely ported to C++ (powered with templates, etc.).

#include <math.h>
#include <float.h>

int nofDecimals(const double val)
{
    int result = 0;
    double epsilon = DBL_EPSILON;
    double exponent = 1.0;

    while(fabs(val * exponent - trunc(val * exponent)) > epsilon)
    {
        ++result;

        epsilon *= 10;
        exponent *= 10;
    }

    return result;
}

It has two main tricks. First, it doesn't touch the input argument, especially, it doesn't save again and again any calculated (in iterations) values in it. That's why, there is no inaccuracy accumulation. Second, the epsilon is being multiplied by ten every loop. I.e., the difference is being compared against the "right" accuracy that corresponds to current "shift".

Serge Roussak
  • 1,731
  • 1
  • 14
  • 28
-1

I would suggest reading the value as a string, searching for the decimal point, and parsing the text before and after it as integers. No floating point or rounding errors.

Steven Sudit
  • 19,391
  • 1
  • 51
  • 53
-1
char* fractpart(double f)
 {
    int intary={1,2,3,4,5,6,7,8,9,0};
    char charary={'1','2','3','4','5','6','7','8','9','0'};
    int count=0,x,y;
    f=f-(int)f;
           while(f<=1)
           {
                     f=f*10;
                     for(y=0;y<10;y++)
                         {
                                if((int)f==intary[y])
                                 {
                                           chrstr[count]=charary[y];
                                           break;
                                 }
                         }
    f=f-(int)f;
    if(f<=0.01 || count==4)
   break;
   if(f<0)
   f=-f;
   count++;
    }
     return(chrstr);
    }
  • 3
    Welcome to Stack Overflow! Please don't answer just with source code. Try to provide a nice description about how your solution works. See: [How do I write a good answer?](https://stackoverflow.com/help/how-to-answer). Thanks – sɐunıɔןɐqɐp Jul 30 '18 at 07:21
-1

Here is the complete program

 #include <iostream.h>
 #include <conio.h>
 #include <string.h>
 #include <math.h>

     char charary[10]={'1','2','3','4','5','6','7','8','9','0'};
     int intary[10]={1,2,3,4,5,6,7,8,9,0};

     char* intpart(double);
     char* fractpart(double);

      int main()
      {
      clrscr();
      int count = 0;
      double d = 0;
      char intstr[10], fractstr[10];
     cout<<"Enter a number";
     cin>>d;
     strcpy(intstr,intpart(d));
     strcpy(fractstr,fractpart(d));
     cout<<intstr<<'.'<<fractstr;
     getche();
    return(0);
   }

    char* intpart(double f)
    {
       char retstr[10];
       int x,y,z,count1=0;
       x=(int)f;
            while(x>=1)
            {
               z=x%10;
               for(y=0;y<10;y++)
                {
                 if(z==intary[y])
                 {
                 chrstr[count1]=charary[y];
                 break;
                 }
              }
             x=x/10;
            count1++;
            }
           for(x=0,y=strlen(chrstr)-1;y>=0;y--,x++)
            retstr[x]=chrstr[y];
            retstr[x]='\0';
            return(retstr);
     }

       char* fractpart(double f)
      {
      int count=0,x,y;
      f=f-(int)f;
        while(f<=1)
        {
          f=f*10;
             for(y=0;y<10;y++)
             {
                   if((int)f==intary[y])
                   {
                        chrstr[count]=charary[y];
                        break;
                   }
            }
             f=f-(int)f;
             if(f<=0.01 || count==4)
             break;
             if(f<0)
               f=-f;
            count++;
         }
         return(chrstr); 
 }
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
-2

One way would be to read the number in as a string. Find the length of the substring after the decimal point and that's how many decimals the person entered. To convert this string into a float by using

atof(string.c_str());

On a different note; it's always a good idea when dealing with floating point operations to store them in a special object which has finite precision. For example, you could store the float points in a special type of object called "Decimal" where the whole number part and the decimal part of the number are both ints. This way you have a finite precision. The downside to this is that you have to write out methods for arithmetic operations (+, -, *, /, etc.), but you can easily overwrite operators in C++. I know this deviates from your original question, but it's always better to store your decimals in a finite form. In this way you can also answer your question of how many decimals the number has.

Jaime Garcia
  • 6,744
  • 7
  • 49
  • 61