Skip to content

Validate there is a problem#1

Closed
ccoVeille wants to merge 2 commits intosnopan:masterfrom
ccoveille-forks:pr-1863-snopan
Closed

Validate there is a problem#1
ccoVeille wants to merge 2 commits intosnopan:masterfrom
ccoveille-forks:pr-1863-snopan

Conversation

@ccoVeille
Copy link
Copy Markdown

This is a follow-up of

I found issue during the review of the PR

Copilot AI review requested due to automatic review settings March 17, 2026 23:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds targeted tests to validate edge-case behavior and invariants for ObjectsAreEqual / ObjectsAreEqualValues in the assert package, as a follow-up to prior float-comparison work.

Changes:

  • Extend TestObjectsAreEqual with NaN/Inf/signed-zero cases and add a symmetry assertion.
  • Refine TestObjectsAreEqualValues float32/float64 coverage (precision, overflow boundary, NaN/Inf, signed zero) and add a symmetry assertion.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{float32(10.123456), float64(10.12345678), false},

// cases for float32/float64 comparison, which should not be equal due to precision differences
{float32(1.0 / 3.0), float64(1.0 / 3.0), false},
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one won't pass now, as being

@ccoVeille
Copy link
Copy Markdown
Author

Here the PR I created to showcase the issues I think that are present in the PR you opened @snopan

Please note, I might be wrong :P

Copy link
Copy Markdown
Owner

@snopan snopan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ccoVeille,

Thanks for the PR and the work.

I've left some comments, I'm not sure if InEpsilon logic is the right approach because there seems to be some cases where this fails.

Also I think {float32(10.12345600), float64(10.123456789), true} should not be true, because float64 here is representing more decimal digits which is a different number to float32 value. So InDelta might also be the the wrong approach because it would allow this test case to pass?

I think rounding is correct because we only want to allow {float32(10.12345600), float64(10.123456), true}. But we do need to address the close to INF problem.

What are your thoughts?

Happy with most of the other changes not related to InEpsilon logic approach.

}

return newValue == toValue.Interface()
if actual != actual || expected != expected {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably use math.IsNaN() to be more clear?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this is ran in calcRelativeError(), do we need to call it again earlier?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably use math.IsNaN() to be more clear?

we couldn't as IsNan expects a float64, that's why I did this

// We need to convert the smaller type to the larger type and then compare.

smallestTypeValue, largestTypeValue := expectedValue, actualValue
if actualType.Size() < expectedType.Size() {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a comment saying
actual value is smaller than expected value, converting actual value to expected value type
I know it's kinda implicit but might be clearer to just summarize what this if clause is doing

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we could. But I think my PR should be closed, and only the unit tests I'm adding should be kept

#2

Comment on lines +213 to +227
if smallestTypeValue.Kind() == reflect.Float32 && largestTypeValue.Kind() == reflect.Float64 {
a, b := expected, actual
if af, aIsNumber := toFloat(expected); aIsNumber && af == 0 {
// avoid division by zero in calcRelativeError
a, b = b, a
}

epsilon, err := calcRelativeError(a, b)
if err != nil {
return false
}

// The threshold of 1e-6 is somewhat arbitrary, but it is a common choice for comparing floating point numbers.
return epsilon <= 1e-6
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found an edge case that fails with this relative error approach

{float32(10.123457), float64(10.123456), false}

Here's a playground link to show why

Looks like the problem is when float32(10.123457 gets converted to float64, it becomes slightly less 10.123456954956055 which now is within the epsilon requirement

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to fail also with

{float32(10.12345), float64(10.12346), false},

Copy link
Copy Markdown
Author

@ccoVeille ccoVeille Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my PR should be closed, and only the unit tests I'm adding should be kept

#2

{float32(10.1234), float64(10.1235), false},

// so anything beyond 7 decimal digits should be ignored when comparing at float32 precision, so these should still be equal
{float32(10.12345600), float64(10.123456789), true},
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, this shouldn't be true because they aren't actually the same value. We should say it equates if float64 value has the same decimal digits as float32. If float64 has more decimals past what float32 can represent, they are inherently different values.

Copy link
Copy Markdown
Author

@ccoVeille ccoVeille Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my PR should be closed, and only the unit tests I'm adding should be kept

#2

{complex128(1e+10 + 1e+10i), complex64(1e+10 + 1e+10i), true},
{complex64(1e+10 + 1e+10i), complex128(1e+10 + 1e+10i), true},

{float32(1.0 / 3.0), float64(1.0 / 3.0), true},
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same opinion on here, the float64 actually represents more decimal digits than float32, they are inherently not the same value.

Copy link
Copy Markdown
Author

@ccoVeille ccoVeille Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my PR should be closed, and only the unit tests I'm adding should be kept

#2

@snopan
Copy link
Copy Markdown
Owner

snopan commented Mar 18, 2026

I have a potential solution for values approaching INF I think.

We can split the integer and decimal part of the float, then do rounding of the decimal before combining them back again

thirtyTwo := float32(10.123456)
sixtyFour := float64(10.1234560000)
convertedThirtyTwo := float64(thirtyTwo)

integerPart := math.Floor(convertedThirtyTwo)
decimalPart := convertedThirtyTwo - integerPart

scale := math.Pow(10, 6)
roundDecimal := math.Round(decimalPart*scale) / scale

playground

@ccoVeille
Copy link
Copy Markdown
Author

Yes, I think you are for this what you said here

stretchr#1863 (comment)

In fact, your code seems OK based on your analysis of what is expected.

So my tests are good addition your PR.

But my fix is not good.

But what is unclear now to me is that I don't know if what you coded is what is expected.

Maybe there is something to talk about in the original issue or your PR

@ccoVeille
Copy link
Copy Markdown
Author

I close my PR in favor of #2

@ccoVeille ccoVeille closed this Mar 18, 2026
@ccoVeille ccoVeille deleted the pr-1863-snopan branch March 19, 2026 06:56
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.

3 participants