-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
math: add Compare and Compare32 #56491
Comments
Change https://go.dev/cl/446155 mentions this issue: |
This is related to #33440. @smasher164 mentions the existence of a total-ordering predicate that could be used to sort floats with NaNs. |
I don't think they will, using I think the only advantage of |
Since Go1.18 is released, many users use the The main concern is that users don't notice this special case, and the replacement may break their codes. |
I think it's worth adding documentation to slices.Sort that sorting a float won't work. Maybe also there could be a comparator function in sort. |
The documentation for
Is this not sufficient documentation, @carlmjohnson ? |
@zhangyunhao116 I'm not a big fan of this proposal at this time. The problem with sorting floats is clearly documented and a workaround is provided. It's easy to sort using |
That seems good to me. In #50340 I think there was talk about adding sort.Compare which would take a comparable and return -1, 0, 1. If something like that is ever added there should also be CompareFloat, but it's not necessary on its own. |
Agree that the special case is clearly documented and has a workaround, but it's nice to support all the special cases as I did a simple poll about
51 people joined it, 82.4% (42) selected A, and 17.6% (9) selected B. Looks like many users of this API doesn't notice the special case. |
The proposal still need more discussions, it's a rare case, and It's merely not as users intuitively expected. The |
Special casing the generic type being float64 would solve sorting []float64, but it wouldn't solve sorting []struct {x float64}. I don't see how we can special case this reasonably. Callers should not sort slices with NaNs in them. |
What if instead, we provided a function in the package math
// FloatCompare compares x to y according to the
// total-ordering predicate in IEEE-754, section 5.10.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b.
func FloatCompare[F constraints.Float](a, b F) int
// FloatLess reports whether a is less than b according to the
// total-ordering predicate in IEEE-754, section 5.10.
func FloatLess[F constraints.Float](a, b F) bool {
return FloatCompare(a, b) < 0
} Thus, usage with the var fs []MyFloat32
slices.SortFunc(fs, math.FloatLess[MyFloat32]) This would solve both the NaN issue from this issue and the -0 vs +0 issue from #33440. More complicated composite cases like #56491 (comment) can be handled by passing a user-defined less function to |
This proposal has been added to the active column of the proposals project |
Should FloatCompare go in math or sort? I can see the case for either. ISTM, if we have that we should have |
My argument for it being in "math" is:
|
That makes sense to me. My only other thought is that |
This total-ordering predicate would be pretty simple to define as well, since it's just: func sign(a, b int64) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}
func FloatCompare[F constraints.Float](a, b F) int {
x := int64(Float64bits(float64(a)))
y := int64(Float64bits(float64(b)))
x ^= int64(uint64(x>>63) >> 1)
y ^= int64(uint64(y>>63) >> 1)
return sign(x, y)
} The only thing I'd point out is this ordering of NaNs is different from the way |
The special case in sort seems like a clear decline since it can't be done generally. |
I feel slightly better about this because it is defined by IEEE-754 and we are not making it up. I don't think we should make this function the first generic one in math. |
My reason for proposing a generic version was because you can't roundtrip convert a float32->float64->float32 and preserve the exact bits for NaNs. A single bit is mangled in the float32->float64 conversion. See this example: https://go.dev/play/p/sPn-yfpd-nK, where one of the bits gets mangled on my Ryzen 5900x. I don't know if this a Go-specific issue or a x86 issue. This means that: var x, y float32 = ...
Compare(float64(x), float64(y)) wouldn't be quite correct for NaNs. Arguably this case is esoteric, but Options:
I don't have a strong preference. |
My feeling is to just provide the float64 API under the name math.Compare, and wait until math/v2 to provide a generic version along with generic everything else. |
This is squashing a signaling NaN to a quiet NaN. I believe all platforms do this on a float32->float64->float32 conversion. (Joe and I have been through this ugliness before, e.g., #27193 and #36400.) I'd be ok with a |
Writing this function generically without converting to Edit: I forgot that you can use unsafe and get the float value's size. func sign[I constraints.Signed](a, b I) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}
func FloatCompare[F constraints.Float](a, b F) int {
if unsafe.Sizeof(a) == 4 {
x := int32(math.Float32bits(float32(a)))
y := int32(math.Float32bits(float32(b)))
x ^= int32(uint32(x>>31) >> 1)
y ^= int32(uint32(y>>31) >> 1)
return sign(x, y)
} else {
x := int64(math.Float64bits(float64(a)))
y := int64(math.Float64bits(float64(b)))
x ^= int64(uint64(x>>63) >> 1)
y ^= int64(uint64(y>>63) >> 1)
return sign(x, y)
}
} |
The problem is there's currently no trivial/cheap way to derive the Plus the "fix" in your approach would probably more resemble this: if !NAN2008 {
if IsNaN(a) {
flip signalling bit of a
}
if IsNaN(b) {
flip signalling bit of b
}
}
compare as if on nan2008 |
Right, as you posted that comment, I noticed that |
I suggest that we write special case code if |
Reopening because the CL was rolled back. |
If we fix Maybe |
Looking a bit more I'm not even sure that there is any problem with |
I was trying to replicate the issue by manually toggling the quiet bit, and I was still getting the correct answer. (Sidenote: there are two quiet nan values from https://github.com/golang/go/blob/master/src/cmd/compile/internal/test/float_test.go
Doesn't |
Hmmm, you're right. Both the constants and the The error messages in the test log are
That is interesting because the test actually never compares positive NaN with negative infinity. The outputs of According Richard Sandiford at https://gcc.gnu.org/pipermail/gcc-patches/2006-September/199974.html the MIPS So I think that this could be described as a bug in the MIPS support in the Go compiler. |
I filed #58466 for the compiler bug. If that gets resolved as I expect, then we can return to CL 459435. |
As a workaround, in case we decide not to change |
Change https://go.dev/cl/467515 mentions this issue: |
Sent a second CL with the same change, but updated the tests. @xen0n are you able to verify that these tests pass on MIPS? |
This change introduces the Compare and Compare32 functions based on the total-ordering predicate in IEEE-754, section 5.10. In particular, * -NaN is ordered before any other value * +NaN is ordered after any other value * -0 is ordered before +0 * All other values are ordered the usual way name time/op Compare-8 0.24ns ± 1% Compare32-8 0.24ns ± 0% Fixes golang#56491. Change-Id: I9444fbfefe26741794c4436a26d403b8da97bdaf Reviewed-on: https://go-review.googlesource.com/c/go/+/459435 Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: David Chase <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
This change introduces the Compare and Compare32 functions based on the total-ordering predicate in IEEE-754, section 5.10. In particular, * -NaN is ordered before any other value * +NaN is ordered after any other value * -0 is ordered before +0 * All other values are ordered the usual way Compare-8 0.4537n ± 1% Compare32-8 0.3752n ± 1% geomean 0.4126n Fixes golang#56491. Change-Id: I5c9c77430a2872f380688c1b0a66f2105b77d5ac Reviewed-on: https://go-review.googlesource.com/c/go/+/467515 Reviewed-by: WANG Xuerui <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Lynn Boger <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Michael Pratt <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
Change https://go.dev/cl/498776 mentions this issue: |
Issue #60519 suggests removing |
Change https://go.dev/cl/499416 mentions this issue: |
This reverts CL 467515. Now that we have cmp.Compare, we don't need math.Compare or math.Compare32 after all. For #56491 Fixes #60519 Change-Id: I8ed33464adfc6d69bd6b328edb26aa2ee3d234d9 Reviewed-on: https://go-review.googlesource.com/c/go/+/499416 Reviewed-by: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Eli Bendersky <[email protected]>
The
slices.Sort
doesn't support sorting slices of floating-point numbers containing Not-a-number (NaN) values, It's not very intuitive.Once the
slices
package is merged into stdlib, thesort.{TYPE}s
(e.g.sort.Ints
,sort.Float64s
) APIs may be semi-deprecated. But theslices
package doesn't provide a simple way to sort slices of floats containing NaNs, it recommends usingslices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))})
, this function is very complex compared to usingsort.Float64s
.Related CL: https://go-review.googlesource.com/c/exp/+/446155
The text was updated successfully, but these errors were encountered: