roobigol
roobigol

Reputation: 13

Determining the number of decimal digits in a floating number

I am trying to write a program that outputs the number of the digits in the decimal portion of a given number (0.128).

I made the following program:

#include <stdio.h>
#include <math.h>
int main(){

    float result = 0;
    int count = 0;
    int exp = 0;
    
    for(exp = 0; int(1+result) % 10 != 0; exp++)
    {
        result = 0.128 * pow(10, exp);
        count++;
    }
    printf("%d \n", count);
    printf("%f \n", result);

    return 0;
}

What I had in mind was that exp keeps being incremented until int(1+result) % 10 outputs 0. So for example when result = 0.128 * pow(10,4) = 1280, result mod 10 (int(1+result) % 10) will output 0 and the loop will stop.

I know that on a bigger scale this method is still inefficient since if result was a given input like 1.1208 the program would basically stop at one digit short of the desired value; however, I am trying to first find out the reason why I'm facing the current issue.

My Issue: The loop won't just stop at 1280; it keeps looping until its value reaches 128000000.000000.

Here is the output when I run the program:

10 
128000000.000000 

Apologies if my description is vague, any given help is very much appreciated.

Upvotes: 1

Views: 1064

Answers (3)

Steve Summit
Steve Summit

Reputation: 48113

I am trying to write a program that outputs the number of the digits in the decimal portion of a given number (0.128).

This task is basically impossible, because on a conventional (binary) machine the goal is not meaningful.

If I write

float f = 0.128;
printf("%f\n", f);

I see

0.128000

and I might conclude that 0.128 has three digits. (Never mind about the three 0's.)

But if I then write

printf("%.15f\n", f);

I see

0.128000006079674

Wait a minute! What's going on? Now how many digits does it have?

It's customary to say that floating-point numbers are "not accurate" or that they suffer from "roundoff error". But in fact, floating-point numbers are, in their own way, perfectly accurate — it's just that they're accurate in base two, not the base 10 we're used to thinking about.

The surprising fact is that most decimal (base 10) fractions do not exist as finite binary fractions. This is similar to the way that the number 1/3 does not even exist as a finite decimal fraction. You can approximate 1/3 as 0.333 or 0.3333333333 or 0.33333333333333333333, but without an infinite number of 3's it's only an approximation. Similarly, you can approximate 1/10 in base 2 as 0b0.00011 or 0b0.000110011 or 0b0.000110011001100110011001100110011, but without an infinite number of 0011's it, too, is only an approximation. (That last rendition, with 33 bits past the binary point, works out to about 0.0999999999767.)

And it's the same with most decimal fractions you can think of, including 0.128. So when I wrote

float f = 0.128;

what I actually got in f was the binary number 0b0.00100000110001001001101111, which in decimal is exactly 0.12800000607967376708984375.

Once a number has been stored as a float (or a double, for that matter) it is what it is: there is no way to rediscover that it was initially initialized from a "nice, round" decimal fraction like 0.128. And if you try to "count the number of decimal digits", and if your code does a really precise job, you're liable to get an answer of 26 (that is, corresponding to the digits "12800000607967376708984375"), not 3.


P.S. If you were working with computer hardware that implemented decimal floating point, this problem's goal would be meaningful, possible, and tractable. And implementations of decimal floating point do exist. But the ordinary float and double values any of is likely to use on any of today's common, mass-market computers are invariably going to be binary (specifically, conforming to IEEE-754).


P.P.S. Above I wrote, "what I actually got in f was the binary number 0b0.00100000110001001001101111". And if you count the number of significant bits there — 100000110001001001101111 — you get 24, which is no coincidence at all. You can read at single precision floating-point format that the significand portion of a float has 24 bits (with 23 explicitly stored), and here, you're seeing that in action.

Upvotes: 4

Toby Speight
Toby Speight

Reputation: 30982

As others have said, the number of decimal digits is meaningless when using binary floating-point.

But you also have a flawed termination condition. The loop test is (int)(1+result) % 10 != 0 meaning that it will stop whenever we reach an integer whose last digit is 9.

That means that 0.9, 0.99 and 0.9999 all give a result of 2.

We also lose precision by truncating the double value we start with by storing into a float.

The most useful thing we could do is terminate when the remaining fractional part is less than the precision of the type used.


Suggested working code:

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

int main(void)
{
    double val = 0.128;
    double prec = DBL_EPSILON;
    double result;
    int count = 0;

    while (fabs(modf(val, &result)) > prec) {
        ++count;
        val *= 10;
        prec *= 10;
    }
    printf("%d digit(s): %0*.0f\n", count, count, result);
}

Results:

3 digit(s): 128

Upvotes: 1

chux
chux

Reputation: 154592

float vs. code

A binary float cannot encode 0.128 exactly as it is not a dyadic rational.
Instead, it takes on a nearby value: 0.12800000607967376708984375. 26 digits.

Rounding errors

OP's approach incurs rounding errors in result = 0.128 * pow(10, exp);.

Extended math needed

The goal is difficult. Example: FLT_TRUE_MIN takes about 149 digits.

We could use double or long double to get us somewhat there.
Simply multiply the fraction by 10.0 in each step.
d *= 10.0; still incurs rounding errors, but less so than OP's approach.

#include <stdio.h>
#include <math.h> int main(){
    int count = 0;

    float f =  0.128f;
    double d =  f - trunc(f);
    printf("%.30f\n", d);
    while (d) {
      d *= 10.0;
      double ipart = trunc(d);
      printf("%.0f", ipart);
      d -= ipart;
      count++;
    }
    printf("\n");
    printf("%d \n", count);
    return 0;
 }

Output

0.128000006079673767089843750000
12800000607967376708984375
26 

Usefulness

Typically, past FLT_DECMAL_DIG (9) or so significant decimal places, OP’s goal is usually not that useful.

Upvotes: 1

Related Questions