Skip to content

Conversation

@Juneezee
Copy link
Contributor

@Juneezee Juneezee commented Mar 1, 2025

We can use the built-in min and max functions since Go 1.21.

Reference: https://go.dev/ref/spec#Min_and_max

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>
@Juneezee Juneezee requested a review from a team as a code owner March 1, 2025 08:56
Copy link
Member

@chrisd8088 chrisd8088 left a 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
Comment on lines 243 to 244
humanize.FormatBytes(clamp(m.currentBytes)),
humanize.FormatByteRate(clampf(m.avgBytes), time.Second))
humanize.FormatByteRate(clamp(m.avgBytes), time.Second))
Copy link
Member

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
Comment on lines 249 to 252
func clamp[T int64 | float64](x T) uint64 {
if x < 0 {
return 0
}
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

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>
@Juneezee Juneezee requested a review from chrisd8088 March 4, 2025 05:40
Copy link
Member

@chrisd8088 chrisd8088 left a 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.

@chrisd8088 chrisd8088 merged commit 555a483 into git-lfs:main Mar 4, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants