Why does std::round(sin(pi/6)) not equal 1?

Viewed 156

The cppreference documentation states that std::round will specifically round away from zero in "halfway cases." While this is true with the literal 0.5, it's not true with std::sin(pi/6). I thought this might be a floating point error, so I printed the value but it's exactly 0.5. After inspecting the binary representation however, I can see that they are indeed represented differently. I've provided the code I used to make these inspections below.

#include <iostream>
#include <stdio.h>
#include <cmath>

int main(int argc, char * argv[])
{
    double const pi = std::acos(-1);
    double const a = std::sin(pi/6);
    double const b = 0.5;

    std::cout << "round(" << a << ") = " << std::round(a) << "\n";

    auto pa = reinterpret_cast<const unsigned char *>(&a);
    auto pb = reinterpret_cast<const unsigned char *>(&b);

    std::cout << "a = 0x";
    for (size_t i = 0; i != sizeof(double); ++i) {
        printf("%02x", pa[i]);
    }
    std::cout << "\nb = 0x";
    for (size_t i = 0; i != sizeof(double); ++i) {
        printf("%02x", pb[i]);
    }
    std::cout << "\n";
}
round(0.5) = 0
round(0.5) = 1
a = 0xffffffffffffdf3f
b = 0x000000000000e03f

So my question is this rounding behavior a part of the c++ specification or is this a bug? And in any case, is there some general way that I can "correct" the representation of the value returned by sin? I'm not sure what format it's in because based off what I know of IEEE-754, it looks like it should be NaN. Although from what I understand, c++ doesn't guarantee IEEE-754 floating point representation?

2 Answers

The issue is that you're not printing the value with enough significant digits. When I increase precision with std::setprecision(20), I get: round(0.49999999999999994449) = 0.

You can see this for yourself by either changing the code or entering 3fdfffffffffffff into the bottom Hexadecimal field of this online calculator: https://baseconvert.com/ieee-754-floating-point

The representation looks like NaN because you're reading it backwards. x86/x64 have little-endian floating point numbers. So you should read that it from high to low address, yielding 0x3fdfffff..., which is of course slightly less than 0.5.

Related