zlacker

[parent] [thread] 16 comments
1. fl0ki+(OP)[view] [source] 2024-01-16 19:56:09
> The results of your program will vary depending on the exact make of your compiler and other random attributes of your compile environment, which can wreak havoc if you have code that absolutely wants bit-identical results. This doesn't matter for everybody, but there are some domains where this can be a non-starter (e.g., multiplayer game code).

This already shouldn't be assumed, because even the same code, compiler, and flags can produce different floating point results on different CPU targets. With the world increasingly split over x86_64 and aarch64, with more to come, it would be unwise to assume they produce the same exact numbers.

Often this comes down to acceptable implementation defined behavior, e.g. temporarily using an 80-bit floating register despite the result being coerced to 64 bits, or using an FMA instruction that loses less precision than separate multiply and add instructions.

Portable results should come from integers (even if used to simulate rationals and fixed point), not floats. I understand that's not easy with multiplayer games, but doing so with floats is simply impossible because of what is left as implementation-defined in our language standards.

replies(2): >>Ashame+H5 >>jcranm+Q6
2. Ashame+H5[view] [source] 2024-01-16 20:22:05
>>fl0ki+(OP)
> Often this comes down to acceptable implementation defined behavior,

I believe this is "always" rather than often when it comes to the actual operations defined by the FP standard. gcc does play it fast and loose (as -ffast-math is not yet enabled by default, and FMA on the other hand is), but this is technically illegal and at least can be easily configured to be in standards-compliant mode.

I think the bigger problem comes from what is _not_ documented by the standard. E.g. transcendental functions. A program calling plain old sqrt(x) can find itself behaving differently _even between different stepping of the same core_, not to mention that there are well-known differences between AMD vs Intel. This is all using the same binary.

replies(1): >>mabste+EB
3. jcranm+Q6[view] [source] 2024-01-16 20:26:59
>>fl0ki+(OP)
This advice is out-of-date.

All CPU hardware nowadays conforms to IEEE 754 semantics for binary32 and binary64. (I think all the GPUs now have non-denormal-flushing modes, but my GPU knowledge is less deep). All compilers will have a floating-point mode that preserves IEEE 754 semantics assuming that FP exceptions are unobservable and rounding mode is the default, and this is usually the default (icc/icx is unusual in making fast-math the default).

Thus, you have portability of floating-point semantics, subject to caveats:

* The math library functions [1] are not the same between different implementations. If you want portability, you need to ensure that you're using the exact same math library on all platforms.

* NaN payloads are not consistent on different platforms, or necessarily within the same platform due to compiler optimizations. Note that not even IEEE 754 attempts to guarantee NaN payload stability.

* Long double is not the same type on different platforms. Don't use it. Seriously, don't.

* 32-bit x86 support for exact IEEE 754 equivalence is essentially a "known-WONTFIX" bug. (This is why the C standard implemented FLT_EVAL_METHOD). The x87 FPU evaluates everything in 80-bit precision, and while you can make this work for binary32 easily (double rounding isn't an issue), though with some performance cost (the solution involves reading/writing from memory after every operation), it's not so easy for binary64. However, the SSE registers do implement IEEE 754 exactly, and are present on every chip old enough to drink, so it's not really a problem anymore. There's a subsidiary issue that the x86-32 ABI requires floats be returned in x87 registers, which means you can't properly return an sNaN correctly, but sNaN and floating-point exceptions are firmly in the realm of nonportability anyways.

In short, if you don't need to care about 32-bit x86 support (or if you do care but can require SSE2 support), and you don't care about NaNs, and you bring your own libraries along, you can absolutely expect to have floating-point portability.

[1] It's actually not even all math library functions, just those that are like sin, pow, exp, etc., but specifically excluding things like sqrt. I'm still trying to come up with a good term to encompass these.

replies(4): >>zozbot+f8 >>fl0ki+ob >>Ashame+dc >>accoun+nO1
◧◩
4. zozbot+f8[view] [source] [discussion] 2024-01-16 20:33:37
>>jcranm+Q6
> It's actually not even all math library functions, just those that are like sin, pow, exp, etc., but specifically excluding things like sqrt. I'm still trying to come up with a good term to encompass these.

Transcendental functions. They're called that because computing an exactly rounded result might be unfeasible for some inputs. https://en.wikipedia.org/wiki/Table-maker%27s_dilemma So standards for numerical compute punt on the issue and allow for some error in the last digit.

replies(2): >>jcranm+z9 >>lifthr+Kh1
◧◩◪
5. jcranm+z9[view] [source] [discussion] 2024-01-16 20:40:14
>>zozbot+f8
Not all of the functions are transcendental--things like cbrt and rsqrt are in the list, and they're both algebraic.

(The main defining factor is if they're an IEEE 754 §5 operation or not, but IEEE 754 isn't a freely-available standard.)

◧◩
6. fl0ki+ob[view] [source] [discussion] 2024-01-16 20:50:22
>>jcranm+Q6
Not sure if this is a spooky coincidence, but I happened to be reading the Rust 1.75.0 release notes today and fell into this 50-tab rabbit hole: https://github.com/rust-lang/rust/pull/113053/
◧◩
7. Ashame+dc[view] [source] [discussion] 2024-01-16 20:54:01
>>jcranm+Q6
> this is usually the default

No, it's not. gcc itself still defaults to fp-contract=fast. Or at least does in all versions I have ever tried.

◧◩
8. mabste+EB[view] [source] [discussion] 2024-01-16 23:17:13
>>Ashame+H5
I'm surprised by this, regarding sqrt. The standard stipulates correct rounding for simple arithmetic, including sqrt ever since 754 1985.

Unless of course we are talking about the 80 bit format.

If that's not the case, would be interested to know where they differ.

Unfortunately for the transcendental function the accuracy still hasn't been pinned down, especially since that's still an ongoing research problem.

There's been some great strides in figuring out the worst cases for binary floating point up to doubles so hopefully an upcoming standard will stipulate 0.5 ULP for transcendentals. But decimal floating point still has a long way to go.

replies(1): >>Ashame+UQ
◧◩◪
9. Ashame+UQ[view] [source] [discussion] 2024-01-17 00:45:26
>>mabste+EB
Because compilers can and have implemented sqrt in terms of rsqrt which is .. fun to work with. This also on SSE.
replies(2): >>mabste+8f1 >>jcranm+oh1
◧◩◪◨
10. mabste+8f1[view] [source] [discussion] 2024-01-17 03:45:57
>>Ashame+UQ
I spent most of my career working with rsqrt haha. And had my fair share of non-754 architectures too!

Every 754 architecture (including SSE) I've worked on has an accurate sqrt().

I'm assuming you're talking about with "fast math" enabled? In which case all bets are off anyway!

replies(1): >>Ashame+wt3
◧◩◪◨
11. jcranm+oh1[view] [source] [discussion] 2024-01-17 04:06:33
>>Ashame+UQ
sqrt is a fundamental IEEE 754 operation, required to be correctly rounded, and many architectures implement a dedicated, correctly rounded sqrt instruction.

Now, there is also often an approximate rsqrt and approximate reciprocal, with varying degrees of accuracy, and that can be "fun."

◧◩◪
12. lifthr+Kh1[view] [source] [discussion] 2024-01-17 04:10:17
>>zozbot+f8
While it is a difficult problem, it is not an infeasible problem nowdays, at least for trigonometric, logarithmic and exponential functions. (All possible arguments have been mapped to prove how many additional bits are needed for correct rounding.) Two-argument pow remains an unsolved problem in my knowledge though.
replies(1): >>jcranm+6m1
◧◩◪◨
13. jcranm+6m1[view] [source] [discussion] 2024-01-17 04:50:21
>>lifthr+Kh1
My understanding is we have exhaustively enumerated the unary binary32 functions and proved the correctness of correct-rounding for them. For binary64, exhaustive enumeration is not a viable strategy, but we generally have a decent idea of what cases end up being hard-to-round, and in a few cases, we may have mechanical proofs of correctness.

There was a paper last year on binary64 pow (https://inria.hal.science/hal-04159652/document) which suggests that they have a correctly-rounded pow implementation, but I don't have enough technical knowledge to assess the validity of the claim.

replies(1): >>lifthr+yn1
◧◩◪◨⬒
14. lifthr+yn1[view] [source] [discussion] 2024-01-17 05:08:04
>>jcranm+6m1
For your information, binary64 has been indeed mapped exhaustively for several functions [1], so it is known that at most triple-double representation is enough for correct rounding.

[1] https://inria.hal.science/inria-00072594/document

> There was a paper last year on binary64 pow (https://inria.hal.science/hal-04159652/document) which suggests that they have a correctly-rounded pow implementation, but I don't have enough technical knowledge to assess the validity of the claim.

Thank you for the pointer. These were written by usual folks you'd expect from such papers (e.g. Paul Zimmermann) so I believe they did achieve significant improvement. Unfortunately it is still not complete, the paper notes that the third and final phase may still fail but is unknown whether it indeed occurs or not. So we will have to wait...

◧◩
15. accoun+nO1[view] [source] [discussion] 2024-01-17 09:01:52
>>jcranm+Q6
> All CPU hardware nowadays conforms to IEEE 754 semantics for binary32 and binary64.

Is this out of date?

https://developer.arm.com/documentation/den0018/a/NEON-Instr...

◧◩◪◨⬒
16. Ashame+wt3[view] [source] [discussion] 2024-01-17 18:36:23
>>mabste+8f1
No; compilers have done this even without fast-math. Gcc does not seem to do this anymore, but still does plenty of unsafe optimizations by default, like FMA.

Or maybe the library you use...

replies(1): >>mabste+aY7
◧◩◪◨⬒⬓
17. mabste+aY7[view] [source] [discussion] 2024-01-18 22:37:14
>>Ashame+wt3
Argh, sounds really frustrating! It's hard enough to get accuracy when you can control operations never mind when the compiler is doing magic behind the scenes!

FMAs were difficult. The Visual Studio compiler in particular didn't support purposeful FMAs for SSE instructions so you had to rely on the compiler to recognise and replace multiply-additions. Generally I want FMAs because they're more accurate but I want to control where they go.

[go to top]