1

I have the following piece of code:

#include <iostream>
using namespace std;
int main() {

    double value = 0;
    double gain = 0.01;
    double offset = -500;

    value -= offset;
    unsigned long raw = value / gain;
    cout << raw;

    return 0; 
}

On my Windows machine with MinGW 5.0 and gcc 4.7.3 the console output of this programm is 49999 instead of 50000. Using some random online IDE (https://ideone.com/uDhPFM) as well as my linux machine, the result is 50000 as expected.

Why is that so?

EDIT: On both, Windows and Linux, I am using the default installation of CLion to run the program.

UPDATE:

#include <iostream>
using namespace std;

int main() {
    double value = 0;
    double gain = 0.01;
    double offset = -500;

    value = value - offset;
    double rawDouble = value / gain;
    unsigned long rawInt = value / gain;
    cout << rawDouble << endl;
    cout << rawInt << endl;
    cout << setiosflags(ios::fixed) << setprecision(24) << rawDouble << endl;
    cout << setiosflags(ios::fixed) << setprecision(24) << (value / gain) << endl;

    return 0;
}

Using this code the output is

50000
49999
50000.000000000000000000000000
50000.000000000000000000000000

Only the division in direct relation to the assignment / inplicit cast seems to fail.

mxcd
  • 1,954
  • 2
  • 25
  • 38
  • Fails to compile: `error: unknown type name 'uint64_t'` and `error: use of undeclared identifier 'cout'`. Also, GCC 4.7.3 is ancient, you should upgrade. – tambre Sep 27 '17 at 16:14
  • 50000 on my MinGW-w64 (no idea what version) w/ gcc 7.1 – Post Self Sep 27 '17 at 16:16
  • @tambre Yes, technically it isn’t a minimal, *complete*, and verifiable example. You are well aware that those are in `` and `iostream`, respectively, and this is probably not an include file error. – Daniel H Sep 27 '17 at 16:16
  • What compiler flags are you using? – Daniel H Sep 27 '17 at 16:19
  • You could try printing out the binary representation of `0.01` on each platform since this is not representable in base 2 with a finite number of digits. – Albjenow Sep 27 '17 at 16:20
  • 2
    Calculate `raw` as a double, print it out to at least 6 decimal places. – Thomas Matthews Sep 27 '17 at 16:20
  • https://stackoverflow.com/questions/2100490/floating-point-inaccuracy-examples – SHR Sep 27 '17 at 16:21
  • related/dupe: https://stackoverflow.com/questions/588004/is-floating-point-math-broken – NathanOliver Sep 27 '17 at 16:22
  • Using `double raw`, do `std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(24) << raw << std::endl` to find out more. – tadman Sep 27 '17 at 16:24
  • `0.01` is a "repeating decimal"., when you convert to binary (I believe it would be a repeating binary then). The outdated compilers usually fails due to lack of precision... – Leonardo Alves Machado Sep 27 '17 at 16:26
  • The value of `0.01` as a double is actually *very* slightly more than 0.01, because there is no `double` which precisely represents that value. However, there is also no `double` which precisely represents 500 divided by the double `0.01`, and the closest is precisely `50000`. I don’t know why any compiler would give `49999`. These details depend on using IEEE-754 doubles, but I would be shocked if gcc 4.7.3 on x86 or x64 didn’t. – Daniel H Sep 27 '17 at 16:30
  • @DanielH I didnt set any additional compiler flags in my cmake file – mxcd Sep 27 '17 at 16:37
  • The difference could be because of SSE vs. FPU. FPU calculates with an internal precision of 80-bit. You should not depend on the calculation of floating point preciseness, when a number (in this case, 0.01) cannot be represented in binary. (usually, FPU is used for floating point calculations in 32-bit mode, in 64-bit mode SSE is used) – geza Sep 27 '17 at 16:48
  • @geza But would it do a different calculation only if it were going to immediately truncate the result? To me this looks like it’s more likely a one-ulp compiler bug; I think C++ does specify that `rawInt` and `static_cast(rawDouble)` should have the same value, and given the output in the question the latter should be `50000`. – Daniel H Sep 27 '17 at 16:53
  • @maximilian009 Can you verify what `static_cast(rawDouble)` is? – Daniel H Sep 27 '17 at 16:55
  • @DanielH: in the FPU case, division result stored in a 80-bit internal register, then it is popped out to long. In the SSE case, division result stored in a 64-bit internal register, then converted to long. It is enough to make a difference. I'm not saying it is 100% sure this is the case here, but this can happen. – geza Sep 27 '17 at 16:59
  • I think if you set FPU internal precision to 64-bit, you might get 50000. – geza Sep 27 '17 at 17:01
  • @geza In 80-bit format, the division and truncation seem to result in 49999 (although I’m having trouble finding the exact intermediate values). I’m just surprised GCC is allowed to give different answers depending on when you do the cast; I thought it was forced to do intermediate rounding. Is that a GNU extension or something? – Daniel H Sep 27 '17 at 17:26
  • @DanielH: GCC cannot solve this issue at all. If division is done with 80-bit precision, you cannot "convert" it to 64-bit to simulate 64-bit division. 2 rounding vs. 1 rounding. Btw, there is "-ffloat-store" option which forces GCC to put 80-bit results to 64-bit/32-bit variables. Maybe it fixes the current issue. But as far as I know, this cannot fix all cases (because of the extra rounding). – geza Sep 27 '17 at 18:19

0 Answers0