Skip to content

Commit d6248dc

Browse files
fixed min/max
1 parent e18304a commit d6248dc

3 files changed

Lines changed: 117 additions & 41 deletions

File tree

compiler/expressions.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,18 +1064,17 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args
10641064
return fc.formatExpr("$recover()")
10651065
case "close":
10661066
return fc.formatExpr(`$close(%e)`, args[0])
1067-
case "min":
1067+
case "min", "max":
10681068
if basic, isBasic := fc.typeOf(args[0]).Underlying().(*types.Basic); isBasic && isOrdered(basic) {
1069-
cond := fc.translateExpr(&ast.BinaryExpr{X: args[0], Y: args[1], Op: token.LSS})
1070-
return fc.formatExpr("(%s ? %e : %e)", cond, args[0], args[1])
1071-
}
1072-
panic(fmt.Sprintf("Unhandled type for min: %T\n", args[0]))
1073-
case "max":
1074-
if basic, isBasic := fc.typeOf(args[0]).Underlying().(*types.Basic); isBasic && isOrdered(basic) {
1075-
cond := fc.translateExpr(&ast.BinaryExpr{X: args[0], Y: args[1], Op: token.GTR})
1076-
return fc.formatExpr("(%s ? %e : %e)", cond, args[0], args[1])
1069+
fnName := `$` + name
1070+
if is64Bit(basic) {
1071+
fnName += `64`
1072+
} else if isString(basic) {
1073+
fnName += `Str`
1074+
}
1075+
return fc.formatExpr("%s(%e, %s)", fnName, args[0], strings.Join(fc.translateExprSlice(args[1:], basic), `, `))
10771076
}
1078-
panic(fmt.Sprintf("Unhandled type for max: %T\n", args[0]))
1077+
panic(fmt.Sprintf("Unhandled type for %s: %T\n", name, args[0]))
10791078
case "clear":
10801079
switch argType := fc.typeOf(args[0]).Underlying().(type) {
10811080
case *types.Slice:

compiler/prelude/numeric.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
var $min = Math.min;
2+
var $max = Math.max;
3+
4+
var $less64 = (x, y) => x.$high < y.$high || (x.$high === y.$high && x.$low < y.$low);
5+
var $min64 = (first, ...rest) => rest.reduce((m, x) => $less64(x, m) ? x : m, first);
6+
var $max64 = (first, ...rest) => rest.reduce((m, x) => $less64(m, x) ? x : m, first);
7+
var $minStr = (first, ...rest) => rest.reduce((m, x) => x < m ? x : m, first);
8+
var $maxStr = (first, ...rest) => rest.reduce((m, x) => m < x ? x : m, first);
9+
210
var $mod = (x, y) => { return x % y; };
311
var $parseInt = parseInt;
412
var $parseFloat = f => {

tests/numeric_test.go

Lines changed: 100 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tests
33
import (
44
"cmp"
55
"fmt"
6+
"math"
67
"math/bits"
78
"math/rand"
89
"runtime"
@@ -203,43 +204,111 @@ func Test_32BitEnvironment(t *testing.T) {
203204
}
204205
}
205206

206-
// checkMinMax is a helper for Test_MinMax that checks the builtin min and max methods.
207-
// The x value must be less than y.
208-
func checkMinMax[T cmp.Ordered](t *testing.T, x, y T) {
207+
// checkMinMax2 is a helper for Test_MinMax that checks the builtin min
208+
// and max methods. The x value must be less than y.
209+
func checkMinMax2[T cmp.Ordered](t *testing.T, x, y T) {
209210
t.Helper()
210-
if got, want := min(x, y), x; got != want {
211-
t.Errorf("min[%T](x, y): got: %v, want: %v", x, got, want)
211+
check := func(a, b T) {
212+
if got, want := min(a, b), x; got != want {
213+
t.Errorf("min[%T](%v, %v): got: %v, want: %v", want, a, b, got, want)
214+
}
215+
if got, want := max(a, b), y; got != want {
216+
t.Errorf("max[%T](%v, %v): got: %v, want: %v", want, a, b, got, want)
217+
}
212218
}
213-
if got, want := min(y, x), x; got != want {
214-
t.Errorf("min[%T](y, x): got: %v, want: %v", x, got, want)
219+
check(x, y)
220+
check(y, x)
221+
}
222+
223+
// checkMinMax4 is a helper for Test_MinMax that checks the builtin min
224+
// and max methods. The builtin min and max are not actually veriadic,
225+
// so cannot be tested via `min(first, rest...)`, but they do allow 1 or more
226+
// arguments, so this one checks 4 arguments. v1 must be the actual min,
227+
// and v4 must be the actual max.
228+
func checkMinMax4[T cmp.Ordered](t *testing.T, v1, v2, v3, v4 T) {
229+
t.Helper()
230+
check := func(a, b, c, d T) {
231+
if got, want := min(a, b, c, d), v1; got != want {
232+
t.Errorf("min[%T](%v, %v, %v, %v): got: %v, want: %v", want, a, b, c, d, got, want)
233+
}
234+
if got, want := max(a, b, c, d), v4; got != want {
235+
t.Errorf("max[%T](%v, %v, %v, %v): got: %v, want: %v", want, a, b, c, d, got, want)
236+
}
215237
}
216-
if got, want := max(x, y), y; got != want {
217-
t.Errorf("max[%T](x, y): got: %v, want: %v", x, got, want)
238+
check(v1, v2, v3, v4)
239+
check(v1, v2, v4, v3)
240+
check(v1, v4, v2, v3)
241+
check(v1, v4, v3, v2)
242+
check(v2, v1, v3, v4)
243+
check(v2, v1, v4, v3)
244+
check(v2, v4, v1, v3)
245+
check(v2, v4, v3, v1)
246+
check(v3, v1, v2, v4)
247+
check(v3, v1, v4, v2)
248+
check(v3, v4, v1, v2)
249+
check(v3, v4, v2, v1)
250+
check(v4, v1, v2, v3)
251+
check(v4, v1, v3, v2)
252+
check(v4, v3, v1, v2)
253+
check(v4, v3, v2, v1)
254+
}
255+
256+
// checkMinMax1 is a helper for Test_MinMax that checks the builtin min
257+
// and max methods. This checks the edge case with 1 argument.
258+
func checkMinMax1[T cmp.Ordered](t *testing.T, x T) {
259+
t.Helper()
260+
if got := min(x); got != x {
261+
t.Errorf("min[%T](%v): got: %v, want: %v", x, x, got, x)
218262
}
219-
if got, want := max(y, x), y; got != want {
220-
t.Errorf("max[%T](y, x): got: %v, want: %v", x, got, want)
263+
if got := max(x); got != x {
264+
t.Errorf("max[%T](%v): got: %v, want: %v", x, x, got, x)
221265
}
222266
}
223267

224268
func Test_MinMax(t *testing.T) {
225-
checkMinMax(t, 0, 1) // int
226-
checkMinMax(t, -1, 0) // int
227-
checkMinMax(t, 12, 42) // int
228-
checkMinMax(t, -42, -12) // int
229-
checkMinMax[int8](t, -9, 13)
230-
checkMinMax[int16](t, 0, 23)
231-
checkMinMax[int32](t, -87, 1234)
232-
checkMinMax[int64](t, -0xDEAD_BEEF, 0x7FFF_FFFF_FFFF_FFFF)
233-
checkMinMax[uint8](t, 9, 13)
234-
checkMinMax[uint16](t, 0, 23)
235-
checkMinMax[uint32](t, 87, 1234)
236-
checkMinMax[uint64](t, 0xDEAD_BEEF, 0x7FFF_FFFF_FFFF_FFFF)
237-
checkMinMax[uintptr](t, 12345, 54321)
238-
checkMinMax[float32](t, 1.41421356237, 3.14159265359)
239-
checkMinMax(t, -3.14159265359, 1.41421356237) // float64
240-
checkMinMax(t, ``, `a`) // string
241-
checkMinMax(t, `a`, `z`) // string
242-
checkMinMax(t, `a`, `aa`) // string
243-
checkMinMax(t, `banana`, `cat`) // string
244-
checkMinMax(t, `Dog`, `dog`) // string
269+
checkMinMax2(t, 0, 1) // int
270+
checkMinMax2(t, -1, 0) // int
271+
checkMinMax2(t, 12, 42) // int
272+
checkMinMax2(t, -42, -12) // int
273+
checkMinMax2[int8](t, -9, 13)
274+
checkMinMax2[int16](t, 0, 23)
275+
checkMinMax2[int32](t, -87, 1234)
276+
checkMinMax2[int64](t, -0xDEAD_BEEF, 0x7FFF_FFFF_FFFF_FFFF)
277+
checkMinMax2[uint8](t, 9, 13)
278+
checkMinMax2[uint16](t, 0, 23)
279+
checkMinMax2[uint32](t, 87, 1234)
280+
checkMinMax2[uint64](t, 0xDEAD_BEEF, 0x7FFF_FFFF_FFFF_FFFF)
281+
checkMinMax2[uintptr](t, 12345, 54321)
282+
checkMinMax2[float32](t, 1.41421356237, 3.14159265359)
283+
checkMinMax2(t, -3.14159265359, 1.41421356237) // float64
284+
checkMinMax2(t, ``, `a`) // string
285+
checkMinMax2(t, `a`, `z`) // string
286+
checkMinMax2(t, `a`, `aa`) // string
287+
checkMinMax2(t, `banana`, `cat`) // string
288+
checkMinMax2(t, `Dog`, `dog`) // string
289+
290+
checkMinMax4(t, -4, -3, -2, -1) // int
291+
checkMinMax4(t, 1, 2, 3, 4) // int
292+
checkMinMax4[int64](t, -4, -3, -2, -1)
293+
checkMinMax4[int64](t, 1, 2, 3, 4)
294+
checkMinMax4[uint64](t, 1, 2, 3, 4)
295+
checkMinMax4(t, `apple`, `banana`, `carrot`, `durian`) // string
296+
297+
checkMinMax1(t, 1) // int
298+
checkMinMax1[int64](t, -19)
299+
checkMinMax1[uint64](t, 244)
300+
checkMinMax1(t, 2.3) // float64
301+
checkMinMax1(t, `Ludo`) // string
302+
303+
// Note that math.Min and math.Max act differently for NaN than max and min,
304+
// see [https://github.com/golang/go/issues/60616]
305+
// Fortunelty the builtin max and min act like JS's Math.max and Math.min,
306+
// see [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min]
307+
// If any argument is NaN, then NaN will be returned.
308+
if got := min(42, math.NaN(), -81); !math.IsNaN(got) {
309+
t.Errorf("min(..NaN..): got: %v, want: %v", got, math.NaN())
310+
}
311+
if got := max(42, math.NaN(), -81); !math.IsNaN(got) {
312+
t.Errorf("max(..NaN..): got: %v, want: %v", got, math.NaN())
313+
}
245314
}

0 commit comments

Comments
 (0)