Skip to content

Commit 18331d9

Browse files
committed
feat: add floating number comparison function
Signed-off-by: ghosind <ghosind@gmail.com>
1 parent 4b8ad83 commit 18331d9

File tree

6 files changed

+229
-0
lines changed

6 files changed

+229
-0
lines changed

builtin.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,54 @@ func NotEqualNow(t *testing.T, actual, expect any, message ...any) error {
390390
return tryNotEqual(t, true, actual, expect, message...)
391391
}
392392

393+
// FloatEqual tests the equality between actual and expect floating numbers with epsilon. It'll
394+
// set the result to fail if they are not equal, and it doesn't stop the execution.
395+
//
396+
// FloatEqual(t, 1.0, 1.0, 0.1) // success
397+
// FloatEqual(t, 1.0, 1.01, 0.1) // success
398+
// FloatEqual(t, 1.0, 1.2, 0.1) // fail
399+
func FloatEqual(t *testing.T, actual, expect, epsilon any, message ...any) error {
400+
t.Helper()
401+
402+
return tryFloatEqual(t, false, actual, expect, epsilon, message...)
403+
}
404+
405+
// FloatEqualNow tests the equality between actual and expect floating numbers with epsilon, and
406+
// it'll stop the execution if they are not equal.
407+
//
408+
// FloatEqualNow(t, 1.0, 1.0, 0.1) // success
409+
// FloatEqualNow(t, 1.0, 1.01, 0.1) // success
410+
// FloatEqualNow(t, 1.0, 1.2, 0.1) // fail and terminate
411+
func FloatEqualNow(t *testing.T, actual, expect, epsilon any, message ...any) error {
412+
t.Helper()
413+
414+
return tryFloatEqual(t, true, actual, expect, epsilon, message...)
415+
}
416+
417+
// FloatNotEqual tests the inequality between actual and expect floating numbers with epsilon. It'll
418+
// set the result to fail if they are equal, but it doesn't stop the execution.
419+
//
420+
// FloatNotEqual(t, 1.0, 1.2, 0.1) // success
421+
// FloatNotEqual(t, 1.0, 1.1, 0.1) // success
422+
// FloatNotEqual(t, 1.0, 1.0, 0.1) // fail
423+
func FloatNotEqual(t *testing.T, actual, expect, epsilon any, message ...any) error {
424+
t.Helper()
425+
426+
return tryFloatNotEqual(t, false, actual, expect, epsilon, message...)
427+
}
428+
429+
// FloatNotEqualNow tests the inequality between actual and expect floating numbers with epsilon,
430+
// and it'll stop the execution if they are equal.
431+
//
432+
// FloatNotEqualNow(t, 1.0, 1.2, 0.1) // success
433+
// FloatNotEqualNow(t, 1.0, 1.1, 0.1) // success
434+
// FloatNotEqualNow(t, 1.0, 1.0, 0.1) // fail and terminate
435+
func FloatNotEqualNow(t *testing.T, actual, expect, epsilon any, message ...any) error {
436+
t.Helper()
437+
438+
return tryFloatNotEqual(t, true, actual, expect, epsilon, message...)
439+
}
440+
393441
// HasPrefixString tests whether the string has the prefix string or not, and it set the result to
394442
// fail if the string does not have the prefix string.
395443
//

compare.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,86 @@ func tryNotEqual(t *testing.T, failedNow bool, actual, expect any, message ...an
178178
)
179179
}
180180

181+
// FloatEqual tests the equality between actual and expect floating numbers with epsilon. It'll
182+
// set the result to fail if they are not equal, and it doesn't stop the execution.
183+
//
184+
// a := assert.New(t)
185+
// a.FloatEqual(1.0, 1.0, 0.1) // success
186+
// a.FloatEqual(1.0, 1.01, 0.1) // success
187+
// a.FloatEqual(1.0, 1.2, 0.1) // fail
188+
func (a *Assertion) FloatEqual(actual, expect, epsilon any, message ...any) error {
189+
a.Helper()
190+
191+
return tryFloatEqual(a.T, false, actual, expect, epsilon, message...)
192+
}
193+
194+
// FloatEqualNow tests the equality between actual and expect floating numbers with epsilon, and
195+
// it'll stop the execution if they are not equal.
196+
//
197+
// a := assert.New(t)
198+
// a.FloatEqualNow(1.0, 1.0, 0.1) // success
199+
// a.FloatEqualNow(1.0, 1.01, 0.1) // success
200+
// a.FloatEqualNow(1.0, 1.2, 0.1) // fail and terminate
201+
func (a *Assertion) FloatEqualNow(actual, expect, epsilon any, message ...any) error {
202+
a.Helper()
203+
204+
return tryFloatEqual(a.T, true, actual, expect, epsilon, message...)
205+
}
206+
207+
// FloatNotEqual tests the inequality between actual and expect floating numbers with epsilon. It'll
208+
// set the result to fail if they are equal, but it doesn't stop the execution.
209+
//
210+
// a := assert.New(t)
211+
// a.FloatNotEqual(1.0, 1.2, 0.1) // success
212+
// a.FloatNotEqual(1.0, 1.1, 0.1) // success
213+
// a.FloatNotEqual(1.0, 1.0, 0.1) // fail
214+
func (a *Assertion) FloatNotEqual(actual, expect, epsilon any, message ...any) error {
215+
a.Helper()
216+
217+
return tryFloatNotEqual(a.T, false, actual, expect, epsilon, message...)
218+
}
219+
220+
// FloatNotEqualNow tests the inequality between actual and expect floating numbers with epsilon,
221+
// and it'll stop the execution if they are equal.
222+
//
223+
// a := assert.New(t)
224+
// a.FloatNotEqualNow(1.0, 1.2, 0.1) // success
225+
// a.FloatNotEqualNow(1.0, 1.1, 0.1) // success
226+
// a.FloatNotEqualNow(1.0, 1.0, 0.1) // fail and terminate
227+
func (a *Assertion) FloatNotEqualNow(actual, expect, epsilon any, message ...any) error {
228+
a.Helper()
229+
230+
return tryFloatNotEqual(a.T, true, actual, expect, epsilon, message...)
231+
}
232+
233+
// tryFloatEqual try to testing the equality between actual and expect floating numbers, and it'll
234+
// fail if the values are not equal.
235+
func tryFloatEqual(t *testing.T, failedNow bool, actual, expect, epsilon any, message ...any) error {
236+
t.Helper()
237+
238+
return test(
239+
t,
240+
func() bool { return isFloatEqual(actual, expect, epsilon) },
241+
failedNow,
242+
fmt.Sprintf(defaultErrMessageEqual, actual, expect),
243+
message...,
244+
)
245+
}
246+
247+
// tryFloatNotEqual try to testing the inequality between actual and expect floating numbers, and
248+
// it'll fail if the values are equal.
249+
func tryFloatNotEqual(t *testing.T, failedNow bool, actual, expect, epsilon any, message ...any) error {
250+
t.Helper()
251+
252+
return test(
253+
t,
254+
func() bool { return !isFloatEqual(actual, expect, epsilon) },
255+
failedNow,
256+
fmt.Sprintf(defaultErrMessageEqual, actual, expect),
257+
message...,
258+
)
259+
}
260+
181261
// Nil tests whether a value is nil or not, and it'll fail when the value is not nil. It will
182262
// always return false if the value is a bool, an integer, a floating number, a complex, or a
183263
// string.
@@ -413,6 +493,24 @@ func isEqual(x, y any) bool {
413493
}
414494
}
415495

496+
// isFloatEqual checks the equality of two floating numbers with epsilon.
497+
func isFloatEqual(x, y, epsilon any) bool {
498+
xv := toFloat(x)
499+
yv := toFloat(y)
500+
ev := toFloat(epsilon)
501+
502+
if xv == yv {
503+
return true
504+
}
505+
506+
diff := xv - yv
507+
if diff < 0 {
508+
diff = -diff
509+
}
510+
511+
return diff < ev
512+
}
513+
416514
// isNil checks whether a value is nil or not. It'll always return false if the value is not a
417515
// channel, a function, a map, a point, an unsafe point, an interface, or a slice.
418516
func isNil(val any) bool {

compare_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,55 @@ func testEqualAndNotEqual(a, mockA *Assertion, v1, v2 any, isEqual bool) {
135135
}, isEqual)
136136
}
137137

138+
func TestFloatEqualAndFloatNotEqual(t *testing.T) {
139+
a := New(t)
140+
mockA := New(new(testing.T))
141+
142+
testFloatEqualAndFloatNotEqual(a, mockA, 1, 1, 1e-7, true)
143+
testFloatEqualAndFloatNotEqual(a, mockA, 1, 1.1, 1e-7, false)
144+
testFloatEqualAndFloatNotEqual(a, mockA, 1, 2, 1e-7, false)
145+
testFloatEqualAndFloatNotEqual(a, mockA, 0.999999999999, 1, 1e-7, true)
146+
testFloatEqualAndFloatNotEqual(a, mockA, 1.00000000001, 1, 1e-7, true)
147+
testFloatEqualAndFloatNotEqual(a, mockA, 1.00001, 1, 1e-7, false)
148+
testFloatEqualAndFloatNotEqual(a, mockA, 0.9999, 1, 1e-7, false)
149+
}
150+
151+
func testFloatEqualAndFloatNotEqual(a, mockA *Assertion, v1, v2, epsilon any, isEqual bool) {
152+
// a.T.Helper()
153+
154+
// FloatEqual
155+
testAssertionFunction(a, "FloatEqual", func() error {
156+
return FloatEqual(mockA.T, v1, v2, epsilon)
157+
}, isEqual)
158+
testAssertionFunction(a, "Assertion.FloatEqual", func() error {
159+
return mockA.FloatEqual(v1, v2, epsilon)
160+
}, isEqual)
161+
162+
// FloatNotEqual
163+
testAssertionFunction(a, "FloatNotEqual", func() error {
164+
return FloatNotEqual(mockA.T, v1, v2, epsilon)
165+
}, !isEqual)
166+
testAssertionFunction(a, "Assertion.FloatNotEqual", func() error {
167+
return mockA.FloatNotEqual(v1, v2, epsilon)
168+
}, !isEqual)
169+
170+
// FloatEqualNow
171+
testAssertionNowFunction(a, "FloatEqualNow", func() {
172+
FloatEqualNow(mockA.T, v1, v2, epsilon)
173+
}, !isEqual)
174+
testAssertionNowFunction(a, "Assertion.FloatEqualNow", func() {
175+
mockA.FloatEqualNow(v1, v2, epsilon)
176+
}, !isEqual)
177+
178+
// FloatNotEqualNow
179+
testAssertionNowFunction(a, "FloatNotEqualNow", func() {
180+
FloatNotEqualNow(mockA.T, v1, v2, epsilon)
181+
}, isEqual)
182+
testAssertionNowFunction(a, "Assertion.FloatNotEqualNow", func() {
183+
mockA.FloatNotEqualNow(v1, v2, epsilon)
184+
}, isEqual)
185+
}
186+
138187
func TestNilAndNotNil(t *testing.T) {
139188
a := New(t)
140189
mockA := New(new(testing.T))

error.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const (
4141
var (
4242
// ErrNotArray indicates that the value must be a slice or an array.
4343
ErrNotArray error = errors.New("the value must be a slice or an array")
44+
// ErrNotFloat indicates that the value must be a floating number.
45+
ErrNotFloat error = errors.New("the value must be a floating number")
4446
// ErrNotMap indicates that the value must be a map.
4547
ErrNotMap error = errors.New("the value must be a map")
4648
// ErrNotOrderable indicates that the value must be orderable.

util.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import (
55
"testing"
66
)
77

8+
var (
9+
floatType = reflect.TypeOf(float64(0))
10+
)
11+
812
// test tries to run the test function, and creates an assertion error if the result is fail.
913
func test(
1014
t *testing.T,
@@ -69,3 +73,17 @@ func isSameType(t1, t2 reflect.Type) bool {
6973
return t1 == t2
7074
}
7175
}
76+
77+
// toFloat converts the value to a float64, and it'll panic if the value can't be converted.
78+
func toFloat(v any) float64 {
79+
vv := reflect.ValueOf(v)
80+
if vv.CanFloat() {
81+
return vv.Float()
82+
}
83+
84+
if vv.CanConvert(floatType) {
85+
return vv.Convert(floatType).Float()
86+
}
87+
88+
panic(ErrNotFloat)
89+
}

util_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,17 @@ func TestFailedHandler(t *testing.T) {
2121
})
2222
assert.True(isTerminated)
2323
}
24+
25+
func TestToFloat(t *testing.T) {
26+
a := New(t)
27+
28+
a.NotPanicNow(func() {
29+
a.Equal(toFloat(1), 1.0)
30+
})
31+
a.NotPanicNow(func() {
32+
a.Equal(toFloat(1.0), 1.0)
33+
})
34+
a.PanicNow(func() {
35+
toFloat("1.0")
36+
})
37+
}

0 commit comments

Comments
 (0)