I am trying to figure out how to print floating point numbers without using library functions. Printing the decimal part of a floating point number turned out to be quite easy. Printing the integral part is harder:
static const int base = 2;
static const char hex[] = "0123456789abcdef";
void print_integral_part(float value)
{
assert(value >= 0);
char a[129]; // worst case is 128 digits for base 2 plus NUL
char * p = a + 128;
*p = 0;
do
{
int digit = fmod(value, base);
value /= base;
assert(p > a);
*--p = hex[digit];
} while (value >= 1);
printf("%s", p);
}
Printing the integral part of FLT_MAX
works flawlessly with base 2 and base 16:
11111111111111111111111100000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000 (base 2)
ffffff00000000000000000000000000 (base 16)
However, printing in base 10 results in errors after the first 7 digits:
340282368002860660002286082464244022240 (my own function)
340282346638528859811704183484516925440 (printf)
I assume this is a result of the division by 10. It gets better if I use double instead of float:
340282346638528986604286022844204804240 (my own function)
340282346638528859811704183484516925440 (printf)
(If you don't believe printf
, enter 2^128-2^104
into Wolfram Alpha. It is correct.)
Now, how does printf
manage to print the correct result? Does it use some bigint facilities internally? Or is there some floating point trick I am missing?
Let's explain this one more time. After the integer part has been printed (exactly) without any rounding other than chop towards 0 it's time for the decimal bits.
Start with a string of bytes (say 100 for starters) containing binary zeros. If the first bit to the right of the decimal point in the fp value is set that means that 0.5 (2^-1 or 1/(2^1)is a component of the fraction. So add 5 to the first byte. If the next bit is set 0.25 (2^-2 or 1/(2^2)) is part of the fraction add 5 to the second byte and add 2 to the first (oh, don't forget the carry, they happen - lower school math). The next bit set means 0.125 so add 5 to the third byte, 2 to the second and 1 to the first. And so on:
I did this by hand so I may have missed something but to implement this in code is trivial.
So for all those SO "can't get an exact result using float" people who don't know what they're talking about here is proof that floating point fraction values are perfectly exact. Excruciatingly exact. But binary.
For those who take the time to get their heads around how this works, better precision is well within reach. As for the others ... well I guess they'll keep on not browsing the fora for the answer to a question which has been answered numerous times previously, honestly believe they have discovered "broken floating point" (or whatever thay call it) and post a new variant of the same question every day.
"Close to magic," "dark incantation" - that's hilarious!
I believe the problem lies in value /= base; Do not forget that 10 is not a finite fraction in binary system and thus this calculation is never correct. I also assume some error will occur in fmod due to the same reason.
printf will first compute the integral part and then convert it to decimal(if I get the way you printf the integral part correctly).
This program will work for you.
It appears that the work horse for the float to string conversion is the
dtoa()
function. See dtoa.c in newlib for how they do it.I think it is close to magic. At least the source looks like some kind of dark incantation.
Yes, search for
_Bigint
in the linked source file.Likely.
Like Agent_L's answer, you're suffering from the false result caused by dividing the value by 10. Float, like any binary floating point type, cannot express correctly most rational number in decimal. After division, most of the case the result cannot be fitted into binary, so it'll be rounded. Hence the more you divide, the more error you'll realize.
If the number is not very large, a quick solution would be multiplying it with 10 or a power of 10 depending on how many digits after decimal point you need.
Another way was described here
/Edit: Read Unni's answer first. This results come from http://codepad.org/TLqQzLO3.
to see how messed up your value gets by the
value /= base
operation:When in doubt, throw more printfs at it ;)