8

Let's say I have two arithmetic types, an integer one, I, and a floating point one, F. I also assume that std::numeric_limits<I>::max() is smaller than std::numeric_limits<F>::max().

Now, let's say I have a positive integer value i. Because the representable range of F is larger than I, F(i) should always be defined behavior.

However, if I have a floating point value f such that f == F(i), is I(f) well defined? In other words, is I(F(i)) always defined behavior?


Relevant section from the C++14 standard:

4.9 Floating-integral conversions [conv.fpint]

  1. A prvalue of a floating point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type. [ Note: If the destination type is bool, see 4.12. — end note ]
  2. A prvalue of an integer type or of an unscoped enumeration type can be converted to a prvalue of a floating point type. The result is exact if possible. If the value being converted is in the range of values that can be represented but the value cannot be represented exactly, it is an implementation-defined choice of either the next lower or higher representable value. [ Note: Loss of precision occurs if the integral value cannot be represented exactly as a value of the floating type. — end note ] If the value being converted is outside the range of values that can be represented, the behavior is undefined. If the source type is bool, the value false is converted to zero and the value true is converted to one.
orlp
  • 112,504
  • 36
  • 218
  • 315
  • @vsoftco You'll have to rephrase that. `I <= i` makes no sense - you're comparing a type to a value. – orlp Apr 29 '15 at 00:39
  • 2
    [No](http://coliru.stacked-crooked.com/a/e58cd5864d532045). – T.C. Apr 29 '15 at 00:40
  • 2
    Imagine that `I` and `F` have the same size, say 32 bits. Then take the largest integer. It'll necessarily convert lossily to a value of type `F`. If the representable value that is chosen is larger than the next integer, then converting back results in UB. – Kerrek SB Apr 29 '15 at 00:42
  • @KerrekSB which dismissed my comment. Interesting, I didn't know this may happen. But yes, it makes perfect sense as floating points are less precise when growing. – vsoftco Apr 29 '15 at 00:43
  • [What Every Programmer Should Know About Floating-Point Arithmetic](http://floating-point-gui.de/) – Steve Apr 29 '15 at 00:46
  • @Steve Irrelevant resource. This is not asking about IEEE754 integers - it's asking about what the standard guarantees. – orlp Apr 29 '15 at 00:49
  • @orlp - Steve's resource is hardly irrelevant. The dominant computing machinery uses 32 and 64 bit two's complement arithmetic and 32 and 64 bit IEEE floating point. Suppose the C++ standards committee wrote a requirement that was impossible to implement on such machines. Those committee members would be promptly replaced with more sane representatives (and the standard would be rewritten). – David Hammen Apr 29 '15 at 00:59
  • This might not get mentioned in an answer, so I'll add: it doesn't depend on the range of the floating point type, but rather the precision. An IEEE `float` has a range that is huge compared to the range of an integer, but has a 23 bit mantissa, which means that at exponents higher than 23, it will be unable to represent all integers (even approximately). The first such value would be `2^24 + 1`, which lies between `(1 + 0/2^23) * 2^24` and `(1 + 1/2^23) * 2^24`. A `double` can round correctly to any 32 bit integer (but not to any 64 bit integer). – Wug Apr 29 '15 at 01:08
  • @orlp It is important information that is related to your question. I posted it as a *comment* for a reason. I'm sure someone may find it useful in this context. – Steve Apr 29 '15 at 01:55
  • @Steve. It's more than just informative. It's *normative*. The C standard is a normative reference to the C++ standard, and IEC 60559 is a normative reference to the C standard. By inheritance (something C++ strongly believes in), it's a normative reference to the C++ standard. – David Hammen Apr 29 '15 at 03:59

3 Answers3

4

However, if I have a floating point value f such that f == F(i), is I(f) well defined? In other words, is I(F(i)) always defined behavior?

No.

Suppose that I is a signed two's complement 32 bit integer type, F is a 32 bit single precision floating point type, and i is the maximum positive integer. This is within the range of the floating point type, but it cannot be represented exactly as a floating point number. Some of those 32 bits are used for the exponent.

Instead, the conversion from integer to floating point is implementation dependent, but typically is done by rounding to the closest representable value. That rounded value is one beyond the range of the integer type. The conversion back to integer fails (better said, it's undefined behavior).

David Hammen
  • 32,454
  • 9
  • 60
  • 108
  • Minor nitpick. _"Instead, the conversion from integer to floating point rounds to the closest representable value."_ This is not guaranteed. It's going to be an adjacent representation, not the closest one. – orlp Apr 29 '15 at 00:46
  • Technically speaking, this isn't undefined behavior, but implementation defined behavior (since it depends on the semantics of the floating point types in use, which are implementation defined). – Wug Apr 29 '15 at 00:47
  • @Wug It's implementation defined if it's going to be undefined or not :) – orlp Apr 29 '15 at 00:47
  • Another nitpick: _"That rounded value is one beyond the range of the integer type."_ This is not guaranteed either. – orlp Apr 29 '15 at 00:48
  • 1
    @orlp - Edited. The conversion from integer to floating point is implementation-dependent (and also application dependent; the application can control rounding with IEEE floating point.) If that implementation dependent conversion makes the floating point value be outside of the range of the integer type, the conversion back to an integer is undefined behavior. – David Hammen Apr 29 '15 at 00:50
  • @orlp - You are looking at it wrong. The issue isn't whether it is guaranteed that the rounded value be one beyond the range of the integer type. The issue is that this isn't forbidden. Forbidding this rounding is exactly what the standard would have to do to make your `I(F(i))` defined behavior. – David Hammen Apr 29 '15 at 01:36
  • @DavidHammen I'm perfectly aware, look at my answer. I was just nitpicking :) – orlp Apr 29 '15 at 01:37
-1

No.

It's possible that i == std::numeric_limits<I>::max(), but that i is not exactly representable in F.

If the value being converted is in the range of values that can be represented but the value cannot be represented exactly, it is an implementation-defined choice of either the next lower or higher representable value.

Since the next higher representable value may be chosen, it's possible that the result F(i) no longer fits into I, so conversion back would be undefined behavior.

orlp
  • 112,504
  • 36
  • 218
  • 315
-1

No. Regardless of the standard, you cannot expect that in general this conversion will return your original integer. It doesn't make sense mathematically. But if you read into what you quoted, the standard clearly indicates the possibility of a loss of precision upon converting from int to float.

Suppose your types I and F use the same number of bits. All of the bits of I (save possibly one that stores the sign) are used to specify the absolute value of the number. On the other hand, in F, some bits are used to specify the exponent and some are used for the significand. The range will be greater because of the possible exponent. But the significand will have less precision because there are fewer bits devoted to its specification.

Just as a test, I printed

std::numeric_limits<int>::max();
std::numeric_limits<float>::max();

I then converted the first number to float and back again. The max float had an exponent of 38, and the max int had 10 digits, so clearly float has a larger range. But upon converting the max int to float and back, I went from 2147473647 to -2147473648. So it seems the number was incremented by one unit and went around to the negative side.

I didn't check how many bits are actually used for float on my system, but it at least demonstrates the loss of precision, and it shows that gcc "rounded up".

Jim Vargo
  • 362
  • 1
  • 8
  • I never asked for the original integer value. I just asked if the conversion was defined behavior. – orlp Apr 29 '15 at 00:59