Reputation: 13
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
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
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
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