5

Can there be a difference in bit-representation between a direct assignment of a floating point literal float x = 3.2f; and a double implicitly converted to a float float x2 = 3.2;?

I.e. is

#define EQUAL(FLOAT_LITERAL)\
  FLOAT_LITERAL##f == static_cast<float>(FLOAT_LITERAL)
EQUAL(3.2) && EQUAL(55.6200093490) // etc ... 

true for all floating point literals?

I ask this question, because clang or gcc do not complain about narrowing conversions if numbers are in the value range of float: Warning is enabled with -Wnarrowing:

float f {3.422222222222222222222222222222246454}; // no warning/ error although it should definitely lose precision
float f2 {static_cast<double>(std::numeric_limits<float>::max()) + 1.0}; // no warning/ error
float f3 {3.5e38}; // error: narrowing conversion of '3.5e+38' from 'double' to 'float' inside { } [-Wnarrowing]

It is great that the compiler does actual range checks, but is that sufficient?

Jan15
  • 499
  • 3
  • 11
  • You type these two statements into [compiler explorer](https://godbolt.org) and check. – Mansoor Mar 28 '19 at 23:15
  • 1
    @Mansoor, thanks for that hint. I don't see how that would help me though, because I am not interested in these specific numbers but in the general rule. – Jan15 Mar 28 '19 at 23:18
  • I don't expect the instructions to change based on the numeric value of the numbers, for a simple assignment. – Mansoor Mar 28 '19 at 23:22
  • @Mansoor. I guess I am asking for more insight in what the compiler actually does, since it's no runtime code. I did check with some hand-picked values, but I don't want to type out all possible doubles to be sure. https://godbolt.org/z/MmY9-H – Jan15 Mar 28 '19 at 23:36
  • Compiler actions are in general supposed to be indistinguishable from the runtime actions, of course, but I'm not sure how much leeway FP in general has in C++. I know it is laxer than some other languages. – user207421 Mar 28 '19 at 23:47
  • If the compiler is smart enough then the assignments `float x = 3.2f;` and `float x = 3.2;` should be equal because at no point is a `double` actually needed in the final code; that's just my guess. – J.R. Mar 29 '19 at 01:25

3 Answers3

5

Assuming IEEE 754, float as 32 bit binary, double as 64 bit binary.

There are decimal fractions that round differently, under IEEE 754 round-to-nearest rules, if converted directly from decimal to float from the result of first converting from decimal to double and then to float.

For example, consider 1.0000000596046447753906250000000000000000000000000001

1.000000059604644775390625 is exactly representable as a double and is exactly half way between 1.0 and 1.00000011920928955078125, the value of the smallest float greater than 1.0. 1.0000000596046447753906250000000000000000000000000001 rounds up to 1.00000011920928955078125 if converted directly, because it is greater than the mid point. If it is first converted to 64 bit, round to nearest takes it to the mid point 1.000000059604644775390625, and then round half even rounds down to 1.0.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
  • 1
    Thanks for the explanation! Just for completeness, this is a goldbolt link which verifies your statement: https://godbolt.org/z/TOXU-V – Jan15 Mar 29 '19 at 20:19
5

The answer given by Patricia is correct. But we generally don't type such number, so maybe it's not a problem... Unless it happens with some shorter decimal literals?

I have illustrated that once in the comments following that answer Count number of digits after `.` in floating point numbers?

The decimal value 7.038531e-26 is approximately 0x1.5C87FAFFFFFFFCE4F6700...p-21 the nearest double is 0x1.5C87FB0000000p-21 and the nearest float is 0x1.5C87FAp-21.

Note that 0x1.5C87FA0000000p-21 is the nearest double to 7.038530691851209e-26

So yes, there can be a double-rounding problem (round off error twice in the same direction) with a relatively short literal...

float x = 7.038531e-26f; and float y = 7.038531e-26; should be two different numbers if the compiler rounds the literals correctly.

aka.nice
  • 9,100
  • 1
  • 28
  • 40
  • Nice example, +1. `x=0x1.5c87fap-84 y=0x1.5c87fcp-84`. – njuffa Mar 29 '19 at 17:20
  • Good work. I considered constructing a short literal, but in the interests of time I went with one I knew would show the problem. – Patricia Shanahan Mar 29 '19 at 18:26
  • 1
    Awesome, thank you! For completeness, this is a godbolt link, which verifies your statement that x and y should be different: https://godbolt.org/z/Bcp47p – Jan15 Mar 29 '19 at 20:10
  • @PatriciaShanahan This example is particularly interesting because that string can be the result of formatting a float in Java - in fact it, and its negation, are the only such numbers. I don't believe your example can; it could be handwritten in source code, but not produced by formatting. So, for example, if you are serialising some data to JSON and then parsing it, it is not entirely safe to handle fields of type float by letting the JSON parser parse it to a double and then casting it to float! – Tom Anderson Mar 09 '20 at 10:49
2

Is literal double to float conversion equal to float literal?

Usually yes, but not always equal.

Code to double then to float (vs. code to float) potentially incurs double rounding trouble.

Problem seen with any code value (even possible without a fraction) that is near the half-way case of two adjacent float values.

Occurrence: With random constants and typical float/double: about 1 in 230.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256