Zulip Chat Archive
Stream: lean4
Topic: 0 / 0 = -nan
Mario Carneiro (Aug 09 2022 at 02:23):
I was about to write a docstring explaining that 0 / 0 = 0
over most types, but on float, 0 / 0 = -nan
, which is fun... For the record, division by zero on Float
looks like this:
a / 0 = inf
,a / -0 = -inf
ifa > 0
ora = inf
a / 0 = -inf
,a / -0 = inf
ifa < 0
ora = -inf
0 / 0 = -0 / 0 = 0 / -0 = -0 / -0 = -nan
nan / 0 = nan
,nan / -0 = nan
-nan / 0 = -nan
,-nan / -0 = -nan
it's actually impressive how many patterns this fails to have
James Gallicchio (Aug 09 2022 at 02:41):
It's also a bit weird that it doesn't match IEEE floats (which don't have a notion of sign for NaN afaik, like nan = -nan)
Mario Carneiro (Aug 09 2022 at 02:56):
IEEE floats have signed nan
Mario Carneiro (Aug 09 2022 at 02:57):
they have a lot more nan stuff too, there's qNaN and sNaN and a huge payload
Mario Carneiro (Aug 09 2022 at 02:58):
like nan = -nan
That's yet another pattern that nan fails to have, a fairly famous one: not only nan != -nan
, also nan != nan
Mario Carneiro (Aug 09 2022 at 04:40):
As far as I can tell, this is not standards conforming: 0 / 0
should be positive NaN (the sign bit should be the xor of the input signs), and it is positive in most languages I can test
Mario Carneiro (Aug 09 2022 at 04:49):
...except for C and C++ double
, which at least explains the behavior
Julian Berman (Aug 09 2022 at 06:13):
I get nan
here on macOS FWIW for 0/0
with clang++ -- some hasty googling though says this may have to do with SSE (and that disabling it may change whether you get + or - nan)
Mario Carneiro (Aug 09 2022 at 14:26):
oh, that's even better. I think that leads to yet another way to prove false using native_decide
:
theorem foo : (toString (0 / 0 : Float)).length = 4 := by native_decide
if I save this in an olean file and you load it then you could prove that the expression is equal to 3 instead
Mario Carneiro (Aug 09 2022 at 14:28):
although I suppose the same is true about System.Platform.getNumBits
:
example : (System.Platform.getNumBits ()).1 = 64 := by native_decide
Mac (Aug 09 2022 at 16:13):
Mario Carneiro said:
if I save this in an olean file and you load it then you could prove that the expression is equal to 3 instead
oleans are platform dependent, so using oleans cross-platform is expected to fail
Gabriel Ebner (Aug 09 2022 at 16:19):
The only platform-dependent part is whether it's 32-bit or 64-bit (and endianness, but that's not a practical concern nowadays when everything is little-endian). In fact, for mathlib we will certainly need to use oleans built on another platform (i.e., they're built on Linux/amd64 but used on macOS/aarch64 or Windows/amd64). It's completely infeasible to build mathlib for every OS+processor combination.
Sebastian Ullrich (Aug 09 2022 at 16:20):
In fact, we ship the macOS/Linux aarch64 cross builds with Linux x64 .oleans
Sebastian Ullrich (Aug 09 2022 at 16:31):
Related advertisement: we are still looking for volunteers for extending https://github.com/gebner/oleanparser with support for translating .oleans across architectures, in particular x64 to wasm32! At least it doesn't look like we've gotten all that closer to wasm64 https://webassembly.org/roadmap/
Mac (Aug 09 2022 at 20:02):
Gabriel Ebner said:
The only platform-dependent part is whether it's 32-bit or 64-bit (and endianness, but that's not a practical concern nowadays when everything is little-endian). In fact, for mathlib we will certainly need to use oleans built on another platform (i.e., they're built on Linux/amd64 but used on macOS/aarch64 or Windows/amd64). It's completely infeasible to build mathlib for every OS+processor combination.
I know this is the idea, but Leo was not a fan. He made it very clear to me when implementing cloud builds that Lean is not intended to support cross-platform use of built oleans (and, as such, the cloud build strategy is currently designed to prevent this). At least, that is what I got out of our conversation.
Mac (Aug 09 2022 at 20:05):
I am also not sure I agree with your experience on the cross-platform support for oleans. When I build oleans with WSL they do not work on Windows and when I build Windows oleans they do not work on WSL.
Marc Huisinga (Aug 09 2022 at 21:07):
Speaking about 32 bit platforms, is it possible that the to_offset overflow could be caused more easily on a 32 bit platform, or am I missing something?
Marc Huisinga (Aug 09 2022 at 21:10):
Ah, Gabriel's estimate may already assume 32 bit. My bad.
Sebastian Ullrich (Aug 09 2022 at 21:11):
unsigned
is 32 bit in both cases, yes
Sebastian Ullrich (Aug 09 2022 at 21:11):
I.e. it's actually better on 32 bit because even if objects are only half the size, there's not nearly enough address space for the overflow
Joseph Myers (Aug 10 2022 at 01:23):
Mario Carneiro said:
As far as I can tell, this is not standards conforming:
0 / 0
should be positive NaN (the sign bit should be the xor of the input signs), and it is positive in most languages I can test
"When either an input or result is a NaN, this standard does not interpret the sign of a NaN. However,
operations on bit strings — copy, negate, abs, copySign — specify the sign bit of a NaN result, sometimes
based upon the sign bit of a NaN operand. The logical predicates totalOrder and isSignMinus are also
affected by the sign bit of a NaN operand. For all other operations, this standard does not specify the sign
bit of a NaN result, even when there is only one input NaN, or when the NaN is produced from an invalid
operation." (IEEE 754-2019, 6.3).
pcpthm (Aug 10 2022 at 03:16):
Idea: model Float
as a quotient type where all NaN bit patterns are treated as the same.
It should be zero-cost after compilation.
WASM allows nondeterministic NaN bit pattern <https://github.com/WebAssembly/design/blob/main/Rationale.md#nan-bit-pattern-nondeterminism>.
James Gallicchio (Aug 10 2022 at 21:56):
Joseph Myers said:
" For all other operations, this standard does not specify the sign
bit of a NaN result, even when there is only one input NaN, or when the NaN is produced from an invalid
operation." (IEEE 754-2019, 6.3).
Yeah, I meant judgmental equality when I said nan = -nan
in IEEE :joy: but I think judgmental equality doesn't really matter for floats since they are opaque. Arguably one fix to this problem is to have Float.toString
print nan
regardless of the sign bit :)
Mario Carneiro (Aug 11 2022 at 06:36):
I agree that using a quotient type would make the most sense. The type itself is not exposed to lean, so changing it to be "morally" a quotient type wouldn't change anything, except that as James says this would make Float.toString
not a function anymore, so it should canonicalize NaNs first.
Mario Carneiro (Aug 11 2022 at 07:03):
While testing the other float functions to see what else "leaks" the nan sign bit or payload bits, I found this interesting behavior:
def Float.nan : Float :=
if (0 / 0 : Float).toString == "nan" then 0/0 else -(0/0)
#eval Float.nan.toUInt8 -- 0
#eval Float.nan.toUInt16 -- 0
#eval Float.nan.toUInt32 -- 0
#eval Float.nan.toUInt64 -- 9223372036854775808
#eval (-Float.nan).toUInt64 -- 9223372036854775808
It's apparently not sensitive to the sign of the nan so it's not a portability problem, but it's still weird... (FYI 9223372036854775808
is 2^63
.)
pcpthm (Aug 11 2022 at 07:08):
Reminded an old Rust soundness issue https://github.com/rust-lang/rust/issues/10184
Mario Carneiro (Aug 11 2022 at 07:08):
The C spec, unsurprisingly, declares that all float -> int conversions are UB when not representable in the target type, so this is definitely not a safe function to expose
Mario Carneiro (Aug 11 2022 at 07:08):
yep, that's exactly what I think is happening here
James Gallicchio (Aug 11 2022 at 12:40):
we could probably expose toUIntX?
instead?
Mario Carneiro (Aug 11 2022 at 13:22):
I'm inclined to make it saturate for finite numbers and infinities and return 0 on nans... but first I have to figure out how to write that in conforming C
Last updated: Dec 20 2023 at 11:08 UTC