-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Replace min/max helpers with built-in min/max #5999
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
Conversation
We can use the built-in `min` and `max` functions since Go 1.21. Reference: https://go.dev/ref/spec#Min_and_max Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
chrisd8088
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, thanks for this PR! I've been meaning to get around to replacing the old min/max functions, and you've done it for us, which is great.
I also appreciate that you worked on simplifying the clamp() and clampf() functions in the tq/meter.go source file. Looking at those changes, though, I realized there are some latent problems with both functions, and it's not quite as simple to merge the two into a generic function as we might like.
Would you mind just restoring those two functions, for the time being, so we can approve this PR's changes to all the min/max use cases? Once that's done I've got some improvements to the clamp family of functions which should cover all the edge cases (I think!)
Again, thanks so much for the contribution and for getting this change underway!
tq/meter.go
Outdated
| humanize.FormatBytes(clamp(m.currentBytes)), | ||
| humanize.FormatByteRate(clampf(m.avgBytes), time.Second)) | ||
| humanize.FormatByteRate(clamp(m.avgBytes), time.Second)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These calls raise some interesting issues!
Tthere's a subtle difference between the clamp() and clampf() functions, with a conditional in the clamp() function which contains a bug that's never exercised because the condition is always false.
There's also some hardware-dependent behaviour in the clampf() function if it were ever to be passed a value above math.MaxUint64 (which of course is extremely unlikely).
My suggestion here is to leave these two functions are they written, for now. I'll write up a PR after this one is merged to address these other issues, since they aren't related to the use of the min() and max() helpers, which is the main focus of this PR.
tq/meter.go
Outdated
| func clamp[T int64 | float64](x T) uint64 { | ||
| if x < 0 { | ||
| return 0 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference between the two functions, aside from their input types, is in how they apply their upper limits. The clampf() performs a check for an input value greater than what can be stored in a uint64, and limits the value to that math.MaxUint64 value before casting to a uint64. This makes sense, because a float64 can represent a value much larger than math.MaxUint64.
However, the clamp() function's code, while it looks identical, is slightly different, and both incorrect and redundant. It checks if the input value is larger than math.MaxInt64, which can never be true because the input value is an int64, so the conditional never executes. That's actually good, because if the conditional did execute, it would return math.MaxUint64, a value 2× larger than math.MaxInt64! (Or more precisely, at least on some hardware it would return math.MaxUint64.)
As it is written now, this PR folds these two functions into a single clamp() function which accepts a generic input value. This replacement function uses the upper-bounds check from original clamp() function, so it checks for a value larger than math.MaxInt64, but then tries to return max.MaxUint64.
I say "tries to return" because when you cast max.MaxUint64 to a float64 you encounter hardware-dependent behaviour. The Go language specification says in the "Conversions between numeric types" subsection:
In all non-constant conversions involving floating-point or complex values, if the result type cannot represent the value the conversion succeeds but the result value is implementation-dependent.
We can test how math.MaxUint64 is converted to a float64 and then back to a uint64 with a small program:
package main
import (
"fmt"
"math"
)
func main() {
n := float64(math.MaxUint64)
fmt.Printf(" float64(math.MaxUint64) : %f\n", n)
fmt.Printf("uint64(float64(math.MaxUint64)): %20d\n", uint64(n))
}On an ARM 64-bit CPU, like an Apple Silicon CPU, we get the following, with a final value that's the same as math.MaxUint64:
float64(math.MaxUint64) : 18446744073709551616.000000
uint64(float64(math.MaxUint64)): 18446744073709551615
On an x64 CPU, we instead get a final value that's much smaller, specifically 1<<32 or math.MaxInt64 + 1:
float64(math.MaxUint64) : 18446744073709551616.000000
uint64(float64(math.MaxUint64)): 9223372036854775808
If you use n := float64(math.MaxUint64 - 1024) on the x64 CPU, though, we then get:
float64(math.MaxUint64) : 18446744073709549568.000000
uint64(float64(math.MaxUint64)): 18446744073709549568
Fixing these bugs and discrepancies isn't urgent, though, since it's improbable any user would be dealing with values in the range of these maximums, so I think it's best deferred to another PR.
I've got some code written to address them, but I'll just wait until we've adjusted this PR to deal with the min()/max() changes only and merged it, and then I'll put those fixes into a followup PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for reviewing my PR and providing such a detailed comment 😊. I have reverted the changes for tq/meter.go in commit 310b29f
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, that's much appreciated, and I'll merge this PR now.
Later I'll try put something together to address these interesting but non-urgent issues with our "clamping" functions.
Reference: #5999 (review) Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
chrisd8088
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for helping to clean up and simplify our codebase! PRs like this are always appreciated.
We can use the built-in
minandmaxfunctions since Go 1.21.Reference: https://go.dev/ref/spec#Min_and_max