I hate floating point.

See also: the 902.1 incident of 2020.

Once again, oddness from floating point values took me down a rabbit hole trying to understand why something was not working as I expected.

Earlier, I had stumbled upon one of the magic values that a 32-bit floating point value cannot represent in C. Instead of 902.1, a float will give you 902.099976… Close, but it caused me issues due to how we were doing some math conversions.

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

To work around this, I switched these values to double precision floating point values and now 902.1 shows up as 902.1:

double value = 902.1;
printf ("value = %f\n", value);

That example will indeed show 902.100000.

This extra precision ended up causing a different issue. Consider this simple code, which took a value in kilowatts and converted it to watts, then converted that to a signed integer.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(int argc, char **argv)
{
    double kw = 64.60;
    double watts = kw * 1000;

    printf ("kw   : %f\n", kw);

    printf ("watts: %f\n", watts);

    printf ("int32: %d\n", (int32_t)watts);

    return EXIT_SUCCESS;
}

That looks simple enough, but the output shows it is not:

kw   : 64.600000
watts: 64600.000000
int32: 64599

Er… what? 64.6 multiplied by 1000 displayed as 64600.00000 so that all looks good, but when converted to a signed 32-bit integer, it turned in to 64599. “Oh no, not again…”

I was amused that, by converting these values to float instead of double it worked as I expected:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(int argc, char **argv)
{
    float kw = 64.60;
    float watts = kw * 1000;

    printf ("kw   : %f\n", kw);

    printf ("watts: %f\n", watts);

    printf ("int32: %d\n", (int32_t)watts);

    return EXIT_SUCCESS;
}
kw   : 64.599998
watts: 64600.000000
int32: 64600

Apparently, whatever extra precision I was gaining from using double in this case was adding enough extra precision to throw off the conversion to integer.

I don’t know why. But at least I have a workaround.

Until next (floating point problem) time…

2 thoughts on “I hate floating point.

  1. James Jones

    OK… remember, *printf and *scanf are varargs functions, and the optional parts of varargs get the “usual argument promotions” that go back to K&R 1st edition. In particular, floats get converted to doubles before being put where they go depending on the target processor ABI. %f and %e expect doubles. %lf came with the “long double” type in some revision of ANSI/ISO C, and expects a long double, so that will try to convert part of the stack frame.

    If a number can’t be written as m/2^n for some integer m and some non-negative integer n, it can’t be represented in binary floating point. (That’s a necessary condition, not sufficient, because floating point formats have finitely many mantissa bits.) Ten is not a power of two, so you’re out of luck for accurate representation. The C standard says it rounds to the specified number of digits (6, since you didn’t specify), so that kw value will print eight digits. One decimal digit is, VERY roughly, 3.3 bits (1 / 0.30103), so that’s asking for at least 26 bits, but IEEE 754 binary single precision is just 24, even counting the implied 1 for normalized values.
    You need to decide how many decimal places you want and tell printf about it.

    Reply

Leave a Reply to James JonesCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.