3

Can someone please explain why:

double d = 1.0e+300;
printf("%d\n", d == 1.0e+300);

Prints "1" as expected on a 64-bit machine, but "0" on a 32-bit machine? (I got this using GCC 6.3 on Fedora 25)

To my best knowledge, floating point literals are of type double and there is no type conversion happening.

Update: This only occurs when using the -std=c99 flag.

HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
PJK
  • 2,082
  • 3
  • 17
  • 28
  • Are you using any unusual compiler flags? – user2357112 Dec 31 '16 at 19:39
  • @user2357112 No, nothing arithmetics related. But removing `-std=c99` makes the example behave as expected, which is strange. – PJK Dec 31 '16 at 19:50
  • I'd guess that the second `1.0e+300` is being stored in x87 extended precision without a trip through double precision, making it slightly closer to the mathematically exact value of 10^300 than `d` is and causing the comparison to fail. It'd help to see the assembly. (I believe the standard permits this.) – user2357112 Dec 31 '16 at 19:56
  • Possible duplicate of http://stackoverflow.com/questions/18142348/floating-point-numbers-equality-checking/18142432#18142432 ? – Barmar Dec 31 '16 at 20:09
  • Does anything change with optimization levels (`-O0`, `-O1`, `-O2`, `-O3`)? If it is any consolation, on macOS Sierra 10.12.2 using a home-built GCC 6.3.0, then all optimization levels and both 32-bit and 64-bit compilations all produce the result 0. As far as I can tell, at the higher optimization levels at any rate, the `printf()` is modified to simply print `0` in the assembler — the equality expression is evaluated at compile-time, not run-time. With `-O0` (no optimization), there is a lot more code generated for execution at run-time. – Jonathan Leffler Dec 31 '16 at 21:27
  • You should know to never make `==` comparisons for `float` nor `double` values as the representation many/most numbers is not exact. – user3629249 Jan 01 '17 at 09:19
  • 1
    @user3629249 one does not imply the other. There are plenty of good uses for `==` between floating-point values. You might as well say “never make `+` or `*` between `float` nor `double` value as the representation of many/most numbers is not exact”. – Pascal Cuoq Jan 01 '17 at 18:33
  • @PascalCuoq, A programmer can NEVER expect a `==` comparison between float or double numbers to give the expected results – user3629249 Jan 02 '17 at 21:59
  • 1
    @user3629249 No, **you** can't expect a `==` to give the expected results. So don't use it. The program in this post, for instance, only works (on IEEE 754 platforms that rightfully define `FLT_EVAL_METHOD` as 0) because `==` gives the expected results: http://blog.frama-c.com/index.php?post/2013/05/01/A-conversionless-conversion-function2 . This is only one example, I use `==` all the time, and many programmers do. You just aren't one of them. – Pascal Cuoq Jan 03 '17 at 02:40
  • @PascalCuoq, There are lots of questions on stackoverflow.com (and related pages) where the programmer has used `==` and it has not worked. In all cases that I have read (and that is many of them) It usually results in a link to a stackoverflow document that details why `==` should not be used when comparing `float` and `double` values. I stand by those items. In any case, please look at the other answer to this question. – user3629249 Jan 05 '17 at 09:18
  • 1
    @user3629249 yes but these questions are different (e.g. the programmer expects `0.1 + 0.2 == 0.3`. This does not work in the conditions of my previous comment and can be expected not to work even if these conditions are not true). There are also plenty of questions where a programmer got a segmentation fault from using `*p`. Do you conclude that pointers should never be used? You refer to “a stackoverflow document” without saying which. so it's impossible to explain why that document is wrong or you interpret it wrongly. – Pascal Cuoq Jan 07 '17 at 00:20
  • @user3629249 There is only one answer to this question, it's not an “other answer” (your comment is not an answer, it is a bit of superstition in a comment). The answer does not contradict anything I have said. It does not justify your statement that “You should know to never make == comparisons for float nor double values” either. – Pascal Cuoq Jan 07 '17 at 00:23
  • here is a quote from: "Floating point math is not exact. Simple values like 0.1 cannot be precisely represented using binary floating point numbers, and the limited precision of floating point numbers means that slight changes in the order of operations or the precision of intermediates can change the result. That means that comparing two floats to see if they are equal is usually not what you want. GCC even has a warning for this: “warning: comparing floating point with == or != is unsafe”. – user3629249 Jan 08 '17 at 05:23
  • I suggest your read the complete link – user3629249 Jan 08 '17 at 05:25
  • @PascalCuoq, at the time I wrote the comment, there were two answers. – user3629249 Jan 09 '17 at 10:32
  • 1
    @user3629249 I assure you that there has never been a “second answer” (any user with 10000 reputation can confirm this, as deleted answers are visible to those). Have you noticed that the words “usually not” in the article you quote became “never” in your comment? – Pascal Cuoq Jan 09 '17 at 10:38

1 Answers1

6

The C standard allows to silently propagate floating-point constant to long double precision in some expressions (notice: precision, not the type). The corresponding macro is FLT_EVAL_METHOD, defined in <float.h> since C99.

As by C11 (N1570), §5.2.4.2.2, the semantic of value 2 is:

evaluate all operations and constants to the range and precision of the long double type.

From the technical viewpoint, on x86 architecture (32-bit) GCC compiles the given code into FPU instructions using x87 with 80-bit stack registers, while for x86-64 architecture (64-bit) it preffers SSE unit (as scalars within XMM registers).

The current implementation was introduced in GCC 4.5 along with -fexcess-precision=standard option. From the GCC 4.5 release notes:

GCC now supports handling floating-point excess precision arising from use of the x87 floating-point unit in a way that conforms to ISO C99. This is enabled with -fexcess-precision=standard and with standards conformance options such as -std=c99, and may be disabled using -fexcess-precision=fast.

Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
  • 3
    This is not a matter of 32 vs. 64 bit, but a gcc for x86/amd64 implementation detail. On other architectures observations might differ. Said that, the actual reason is that float types cannot represent almost any fraction exactly. – too honest for this site Jan 01 '17 at 09:55
  • Indeed the assembly confirms that this is the case. Thanks! – PJK Jan 01 '17 at 12:07
  • 1
    @Olaf When `FLT_EVAL_METHOD` is rightfully defined by the compiler as 0 and with the other usual implementation-defined details, `d == 1.0e+300` asks the question “is the `double` closest to 10^300 equal to the `double` closest to 10^300?”. The fact that most rationals cannot be represented exactly in `double` has no relevance on this question: the answer is “yes”. Indeed, the answer is “yes” also if you replace `double` by `float` and `1.0e+300` by `1.0e+300f`, although 10^300 is way outside the range of values representable as **finite** floats, because the same reasoning applies. – Pascal Cuoq Jan 01 '17 at 13:33
  • 1
    To be exhaustive, when `-fexcess-precision=standard` is set directly or through `-std=c99`, then the behavior of the generated 32-bit code is forced. When `-fexcess-precision=fast` is used for 32-bit code generation, anything can happen, as documented in https://hal.archives-ouvertes.fr/hal-00128124/document . With `-fexcess-precision=fast` a double `d` may be equal to itself, or not equal to itself, depending on the surrounding code and optimization level. `d` may differ from itself at line 10 and be equal to itself at line 12 even though line 11 in the source code does not change `d`. – Pascal Cuoq Jan 01 '17 at 13:38