strtof = strtod followed by cast?
Asked Answered
B

2

7

Suppose you have a string like "0.1" that can be only approximately represented as a binary floating point number, and you want to convert it to single precision floating point. This can be done as

strtof(s, 0);

or

(float)strtod(s, 0);

Intuitively, these should give the same result, but is intuition correct in all cases? Or are there any edge cases in which the second form, by doing the rounding twice, gives a slightly different result from the first form?

Barrera answered 1/8, 2019 at 11:11 Comment(4)
Floating point on binary computers is always going to be tricky, no matter what you do. And unless you're on a system with severe memory constraints there's really no need to use float at all and instead always use double.Formality
Or are there any edge cases in which the second form, by doing the rounding twice, gives a slightly different result from the first form? I don't know any way to prove that there aren't any such edge cases. Even if you can prove a properly-implemented system wouldn't have such edge cases, you can't prove that some future implementation has been done correctly. And what if someone compiles with gcc -funsafe-math-optimizations ...? (-ffast-math sets -funsafe-math-optimizations among others...)Eyed
@AndrewHenle The good news is that such edge cases exist now and can be exhibited, which provide a definite answer to the question, but your argument is very strange. What if the question had been “Can I be sure that int x = 1 + 1; sets x to 2? Would you have argued that there was no way to be sure a future C compiler would not botch it? No, if a compiler does not set x to 2, it simply is not a C compiler. (And yes, gcc -ffast-math is not a C compiler, as the GCC developers would be the first to tell you.)Switzer
@PascalCuoq Corner-case bugs in floating point operations aren't exactly rare (unlike botching 1 + 1....), and I assumed someone interested enough in exact floating point results to post this question should consider that.Eyed
S
6

The C standard's specification of strtod and strtof is underspecified. It leaves room for the possibility that strtof returns (float)strtod always, very often or never. (This paragraph refers to another section of the standard that contains that paragraph, which says “the result is either the nearest representable value, or the larger or smaller representable value immediately adjacent to the nearest representable value, chosen in an implementation-defined manner”).

The typical implementations of strtod and strtof return respectively the nearest double and the nearest float to the decimal representation passed to them. When these functions behave this way, then strtof(s, 0) is almost always identical to (float)strtod(s, 0). The decimal representations for which they are not identical are said to exhibit a double-rounding problem, because rounding the decimal representation first to double and then to float produces a different result than rounding directly to float. Note that when this happens, the strtof result is the more exact one. The intermediate rounding made the error slightly more than half a ULP rather than slightly less than half a ULP.

One example of decimal representation with a double-rounding problem when going through double before converting to float is 1.01161128282547 (taken from this quiz). The nearest double is exactly halfway between two floats. Rounding directly to float gets you the nearest float, and going through the nearest double produces the other float.

Switzer answered 1/8, 2019 at 11:37 Comment(2)
Note: printf("%a\n", 1.01161128282547); --> 0x1.02f8f5p+0 to help show "nearest double is exactly halfway between two floats".Stodgy
@chux Indeed, someone who is already familiar with hexadecimal floating-point notation can use this snippet to investigate the properties of 1.01161128282547: gcc.godbolt.org/z/7WmAUuSwitzer
J
-1

x86 FPU always works with 80-bit float numbers no matter with what types you operate. There even can be extra runtime cost in convertation from double to float.

I'm not sure, but strtof() can be implemented as wrapper around strtod(), so in your place i'd better use strtof(), instead calling function for parsing into double and then casting to float, to indicate your intentions. If you do not trust compiler and want optmize code, then maybe use of (float)strtod() will save you a bit performance on extra call/ret instructions.

Jase answered 1/8, 2019 at 11:25 Comment(1)
The first sentence of this answer is very outdated. It's not 1986 any more, and x86 processors have had instructions to deal with binary32 and binary64 for years. For most OSes, the move to 64-bit was the opportunity to start afresh with floating-point ABIs. gcc.godbolt.org/z/sTXsFESwitzer

© 2022 - 2024 — McMap. All rights reserved.