Transaction
C31EC5361140A2…141D4D568ADC
Block 409,194 · index 0 · indexed
Summary
- Hash
- C31EC5361140A214798380FFDA4A9CEDFC5F3B46DEF823DA8FDF141D4D568ADC
- Block
- 409,194
- Size
- 24721 bytes
- Gas used
- 31,752,791 / 75,356,800
- Fee
- 226070ugnot
- Status
- success
Messages
- Attached funds
- 10000000ugnot
- Package
- gno.land/p/moul/xmath/v1
Arguments · 13
- #1xmath
- #2README.md
- #3# `gno.land/p/moul/xmath/v1` `Max`, `Min`, `Clamp`, `Abs` and `Sign` for every built-in numeric type, one concrete function per type. Gno has no generics, so the alternative is writing the same three-line helper again in each realm. ```go xmath.MaxInt(3, 7) // 7 xmath.ClampInt64(x, 0, 100) // x, bounded to [0,100] xmath.AbsFloat64(-2.5) // 2.5 xmath.SignInt(-9) // -1 ``` | type | functions | | --- | --- | | `int`, `int8`, `int16`, `int32`, `int64` | `Max`, `Min`, `Clamp`, `Abs`, `Sign` | | `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `Max`, `Min`, `Clamp` | | `float32`, `float64` | `Max`, `Min`, `Clamp`, `Abs`, `Sign` | Unsigned types get no `Abs` or `Sign`: both are trivially themselves and 1, and offering them would only invite an overflow bug at the call site. The name is the type suffix: `MaxInt8`, `MinUint64`, `ClampFloat32`. `Sign` returns -1, 0 or 1. `Clamp` does not validate its bounds: calling it with `min > max` returns one bound or the other depending on the value, so check the bounds yourself if they are computed. ## Generated `xmath.gen.gno` and `xmath.gen_test.gno` are written by [`tools/xmathgen`](../../../tools/xmathgen) and carry a `DO NOT EDIT` header. Edit the generator, never the output: ```sh go -C tools run ./xmathgen # rewrite both files go -C tools run ./xmathgen -check # fail if they are stale ``` Adding a type is one row in the `types` table in `tools/xmathgen/main.go`; it expands to 5 functions plus their tests. The header used to name a `generator.go` that was never committed, so it was a rule nobody could follow. The generator above was reconstructed from the output it describes and reproduces both committed files byte for byte, which `go test ./tools/xmathgen` asserts on every run. ## `MulDiv`: the proportional share, through a 128-bit intermediate Hand-written rather than generated, in `muldiv.gno`, because it is one function and not a per-type family. ```go xmath.MulDiv(pot, stake, total) // this stake's cut, rounded toward zero xmath.MulDivUp(pot, stake, total) // the same ratio, rounded away from zero ``` `a*b/c` computes `a*b` first, and an int64 product that does not fit **wraps silently to a plausible number**. Measured with `a = 2^62`, `b = 4`, `c = 8`: the true share is `2305843009213693952` and the naive form answers **`0`**, because `2^62 * 4` is exactly `2^64` and wraps to zero. Nothing fails, nothing logs, and a payout split is simply wrong. **Ten realms deployed on gnoland-1 carry their own copy of this**, all reaching for `math/bits` the same way: `bubblerumble` (several versions) and `kourt`, whose `mulDiv128` is the same function with its own panic strings. It had no owner in `p/moul` or `p/nt` until now. `MulDiv` panics rather than returning a wrong number: on a negative operand, on a denominator at or below zero, and on a quotient that does not fit. **`MulDivUp` exists because rounding is a money question, not a style one.** Rounding a fee down and a payout up is how a pool pays out more than it holds. Use `MulDiv` for what someone receives and `MulDivUp` for what someone owes; `TestRoundingDirectionIsTheMoneyQuestion` pins that they never differ by more than one and never cross. ### Why `v1` even though adding a function is non-breaking It is non-breaking **for this repo**, and that is not the constraint that matters. `p/` packages are always public, and a live public path can never be redeployed, so functions added in place at `v0` reach `main` and never reach the chain. `v1` is the only way `MulDiv` becomes importable by anybody. The bump is free here: `xmath` has **zero importers**, in this repo and across every package deployed on `gnoland-1`. `v0` stays resolvable for nobody in particular. <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. > ⚠️ **Disclaimer:** provided as-is, without warranty; not security-audited. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
- #4gnomod.toml
- #5module = "gno.land/p/moul/xmath/v1" gno = "0.9"
- #6muldiv.gno
- #7package xmath import "math/bits" // maxInt64 is the largest int64. Unexported and spelled out because xmath // already exports MaxInt64 as the max-of-two helper, and a constant sharing // that name would shadow it. const maxInt64 = int64(9223372036854775807) // MulDiv returns a*b/c computed through a 128-bit intermediate, so the product // does not have to fit in an int64. It is the proportional-share calculation: // "this account's cut of the pot", "this stake's slice of the rewards". // // The naive a*b/c silently wraps when a*b exceeds an int64, and it wraps to a // plausible-looking number rather than to an obvious one, which is how a payout // split leaks money without anything failing. Ten realms deployed on gnoland-1 // carry their own copy of this function for exactly that reason, and every one // of them reaches for math/bits the same way. // // MulDiv panics rather than returning a wrong number: // // - a or b negative, or c at or below zero: the domain is unsigned ratios, // and a negative share is a caller bug, not a value to propagate. // - the quotient not fitting in an int64. // // Rounding is toward zero, like integer division. See [MulDivUp] when the // remainder must favour the payer instead. func MulDiv(a, b, c int64) int64 { if a < 0 || b < 0 { panic("xmath: MulDiv on a negative operand") } if c <= 0 { panic("xmath: MulDiv by a non-positive denominator") } hi, lo := bits.Mul64(uint64(a), uint64(b)) if hi >= uint64(c) { panic("xmath: MulDiv quotient overflows int64") } q, _ := bits.Div64(hi, lo, uint64(c)) if q > uint64(maxInt64) { panic("xmath: MulDiv quotient overflows int64") } return int64(q) } // MulDivUp is [MulDiv] rounding away from zero when the division is not exact. // // Which way to round is a money question, not a style one: rounding a fee down // and a payout up is how a pool pays out more than it holds. Use MulDiv for // what someone receives and MulDivUp for what someone owes. func MulDivUp(a, b, c int64) int64 { q := MulDiv(a, b, c) hi, lo := bits.Mul64(uint64(a), uint64(b)) _, rem := bits.Div64(hi, lo, uint64(c)) if rem != 0 { if q == maxInt64 { panic("xmath: MulDivUp quotient overflows int64") } q++ } return q }
- #8muldiv_test.gno
- #9package xmath import ( "testing" "gno.land/p/nt/uassert/v0" ) func TestMulDiv(t *testing.T) { cases := []struct { name string a, b, c int64 want int64 }{ {"exact", 10, 3, 5, 6}, {"rounds toward zero", 10, 1, 3, 3}, {"identity", 7, 1, 1, 7}, {"zero numerator", 0, 99, 7, 0}, {"half of a pot", 1000000, 50, 100, 500000}, {"one basis point", 1000000, 1, 10000, 100}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { uassert.Equal(t, tc.want, MulDiv(tc.a, tc.b, tc.c)) }) } } // TestMulDivSurvivesAnIntermediateThatDoesNotFit is the whole point. The naive // a*b/c computes a*b first, which wraps past the int64 maximum and wraps to a // plausible number rather than an obvious one. Through a 128-bit intermediate // the answer is exact. func TestMulDivSurvivesAnIntermediateThatDoesNotFit(t *testing.T) { // A variable, not a const: gno rejects a constant expression that // overflows at compile time, and the point here is the runtime wrap. big := int64(1) << 62 // 4611686018427387904 four := int64(4) // big*4 overflows int64; big*4/8 is big/2 and fits comfortably. uassert.Equal(t, big/2, MulDiv(big, four, 8)) // The naive form, computed here so the test shows what it would have done // rather than asserting it in prose. naive := (big * four) / 8 uassert.NotEqual(t, big/2, naive) // big*4 is exactly 2^64, so it wraps to 0 and the naive form answers 0 for // a share that is actually 2305843009213693952. Measured, not assumed: the // first version of this test asserted the wrap went negative, which is the // intuition and is wrong for this input. uassert.Equal(t, int64(0), naive) } func TestMulDivRefusesRatherThanReturningAWrongNumber(t *testing.T) { big := int64(1) << 62 cases := []struct { name string a, b, c int64 msg string }{ {"negative a", -1, 2, 3, "xmath: MulDiv on a negative operand"}, {"negative b", 1, -2, 3, "xmath: MulDiv on a negative operand"}, {"zero denominator", 1, 2, 0, "xmath: MulDiv by a non-positive denominator"}, {"negative denominator", 1, 2, -3, "xmath: MulDiv by a non-positive denominator"}, {"quotient too large", big, 8, 2, "xmath: MulDiv quotient overflows int64"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { defer func() { r := recover() if r == nil { t.Errorf("MulDiv(%d,%d,%d) did not panic", tc.a, tc.b, tc.c) return } uassert.Equal(t, tc.msg, r.(string)) }() MulDiv(tc.a, tc.b, tc.c) }) } } func TestMulDivUpRoundsAwayFromZero(t *testing.T) { cases := []struct { name string a, b, c int64 want int64 }{ {"exact is unchanged", 10, 3, 5, 6}, {"inexact rounds up", 10, 1, 3, 4}, {"one short rounds up", 7, 1, 2, 4}, {"zero stays zero", 0, 5, 3, 0}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { uassert.Equal(t, tc.want, MulDivUp(tc.a, tc.b, tc.c)) }) } } // TestRoundingDirectionIsTheMoneyQuestion states the invariant the two // functions exist to give a caller: what is received never exceeds what is // owed for the same ratio, so a pool cannot pay out more than it holds. func TestRoundingDirectionIsTheMoneyQuestion(t *testing.T) { for _, n := range []int64{1, 2, 3, 7, 99, 1000001} { down := MulDiv(n, 1, 3) up := MulDivUp(n, 1, 3) if up < down { t.Errorf("MulDivUp(%d,1,3)=%d below MulDiv=%d", n, up, down) } if up-down > 1 { t.Errorf("MulDivUp(%d,1,3)=%d more than one above MulDiv=%d", n, up, down) } } }
- #10xmath.gen.gno
- #11// Code generated by generator.go; DO NOT EDIT. package xmath // Int8 helpers func MaxInt8(a, b int8) int8 { if a > b { return a } return b } func MinInt8(a, b int8) int8 { if a < b { return a } return b } func ClampInt8(value, min, max int8) int8 { if value < min { return min } if value > max { return max } return value } func AbsInt8(x int8) int8 { if x < 0 { return -x } return x } func SignInt8(x int8) int8 { if x < 0 { return -1 } if x > 0 { return 1 } return 0 } // Int16 helpers func MaxInt16(a, b int16) int16 { if a > b { return a } return b } func MinInt16(a, b int16) int16 { if a < b { return a } return b } func ClampInt16(value, min, max int16) int16 { if value < min { return min } if value > max { return max } return value } func AbsInt16(x int16) int16 { if x < 0 { return -x } return x } func SignInt16(x int16) int16 { if x < 0 { return -1 } if x > 0 { return 1 } return 0 } // Int32 helpers func MaxInt32(a, b int32) int32 { if a > b { return a } return b } func MinInt32(a, b int32) int32 { if a < b { return a } return b } func ClampInt32(value, min, max int32) int32 { if value < min { return min } if value > max { return max } return value } func AbsInt32(x int32) int32 { if x < 0 { return -x } return x } func SignInt32(x int32) int32 { if x < 0 { return -1 } if x > 0 { return 1 } return 0 } // Int64 helpers func MaxInt64(a, b int64) int64 { if a > b { return a } return b } func MinInt64(a, b int64) int64 { if a < b { return a } return b } func ClampInt64(value, min, max int64) int64 { if value < min { return min } if value > max { return max } return value } func AbsInt64(x int64) int64 { if x < 0 { return -x } return x } func SignInt64(x int64) int64 { if x < 0 { return -1 } if x > 0 { return 1 } return 0 } // Int helpers func MaxInt(a, b int) int { if a > b { return a } return b } func MinInt(a, b int) int { if a < b { return a } return b } func ClampInt(value, min, max int) int { if value < min { return min } if value > max { return max } return value } func AbsInt(x int) int { if x < 0 { return -x } return x } func SignInt(x int) int { if x < 0 { return -1 } if x > 0 { return 1 } return 0 } // Uint8 helpers func MaxUint8(a, b uint8) uint8 { if a > b { return a } return b } func MinUint8(a, b uint8) uint8 { if a < b { return a } return b } func ClampUint8(value, min, max uint8) uint8 { if value < min { return min } if value > max { return max } return value } // Uint16 helpers func MaxUint16(a, b uint16) uint16 { if a > b { return a } return b } func MinUint16(a, b uint16) uint16 { if a < b { return a } return b } func ClampUint16(value, min, max uint16) uint16 { if value < min { return min } if value > max { return max } return value } // Uint32 helpers func MaxUint32(a, b uint32) uint32 { if a > b { return a } return b } func MinUint32(a, b uint32) uint32 { if a < b { return a } return b } func ClampUint32(value, min, max uint32) uint32 { if value < min { return min } if value > max { return max } return value } // Uint64 helpers func MaxUint64(a, b uint64) uint64 { if a > b { return a } return b } func MinUint64(a, b uint64) uint64 { if a < b { return a } return b } func ClampUint64(value, min, max uint64) uint64 { if value < min { return min } if value > max { return max } return value } // Uint helpers func MaxUint(a, b uint) uint { if a > b { return a } return b } func MinUint(a, b uint) uint { if a < b { return a } return b } func ClampUint(value, min, max uint) uint { if value < min { return min } if value > max { return max } return value } // Float32 helpers func MaxFloat32(a, b float32) float32 { if a > b { return a } return b } func MinFloat32(a, b float32) float32 { if a < b { return a } return b } func ClampFloat32(value, min, max float32) float32 { if value < min { return min } if value > max { return max } return value } func AbsFloat32(x float32) float32 { if x < 0 { return -x } return x } func SignFloat32(x float32) float32 { if x < 0 { return -1 } if x > 0 { return 1 } return 0 } // Float64 helpers func MaxFloat64(a, b float64) float64 { if a > b { return a } return b } func MinFloat64(a, b float64) float64 { if a < b { return a } return b } func ClampFloat64(value, min, max float64) float64 { if value < min { return min } if value > max { return max } return value } func AbsFloat64(x float64) float64 { if x < 0 { return -x } return x } func SignFloat64(x float64) float64 { if x < 0 { return -1 } if x > 0 { return 1 } return 0 }
- #12xmath.gen_test.gno
- #13package xmath import "testing" func TestInt8Helpers(t *testing.T) { // Test MaxInt8 if MaxInt8(1, 2) != 2 { t.Error("MaxInt8(1, 2) should be 2") } if MaxInt8(-1, -2) != -1 { t.Error("MaxInt8(-1, -2) should be -1") } // Test MinInt8 if MinInt8(1, 2) != 1 { t.Error("MinInt8(1, 2) should be 1") } if MinInt8(-1, -2) != -2 { t.Error("MinInt8(-1, -2) should be -2") } // Test ClampInt8 if ClampInt8(5, 1, 3) != 3 { t.Error("ClampInt8(5, 1, 3) should be 3") } if ClampInt8(0, 1, 3) != 1 { t.Error("ClampInt8(0, 1, 3) should be 1") } if ClampInt8(2, 1, 3) != 2 { t.Error("ClampInt8(2, 1, 3) should be 2") } // Test AbsInt8 if AbsInt8(-5) != 5 { t.Error("AbsInt8(-5) should be 5") } if AbsInt8(5) != 5 { t.Error("AbsInt8(5) should be 5") } // Test SignInt8 if SignInt8(-5) != -1 { t.Error("SignInt8(-5) should be -1") } if SignInt8(5) != 1 { t.Error("SignInt8(5) should be 1") } if SignInt8(0) != 0 { t.Error("SignInt8(0) should be 0") } } func TestInt16Helpers(t *testing.T) { // Test MaxInt16 if MaxInt16(1, 2) != 2 { t.Error("MaxInt16(1, 2) should be 2") } if MaxInt16(-1, -2) != -1 { t.Error("MaxInt16(-1, -2) should be -1") } // Test MinInt16 if MinInt16(1, 2) != 1 { t.Error("MinInt16(1, 2) should be 1") } if MinInt16(-1, -2) != -2 { t.Error("MinInt16(-1, -2) should be -2") } // Test ClampInt16 if ClampInt16(5, 1, 3) != 3 { t.Error("ClampInt16(5, 1, 3) should be 3") } if ClampInt16(0, 1, 3) != 1 { t.Error("ClampInt16(0, 1, 3) should be 1") } if ClampInt16(2, 1, 3) != 2 { t.Error("ClampInt16(2, 1, 3) should be 2") } // Test AbsInt16 if AbsInt16(-5) != 5 { t.Error("AbsInt16(-5) should be 5") } if AbsInt16(5) != 5 { t.Error("AbsInt16(5) should be 5") } // Test SignInt16 if SignInt16(-5) != -1 { t.Error("SignInt16(-5) should be -1") } if SignInt16(5) != 1 { t.Error("SignInt16(5) should be 1") } if SignInt16(0) != 0 { t.Error("SignInt16(0) should be 0") } } func TestInt32Helpers(t *testing.T) { // Test MaxInt32 if MaxInt32(1, 2) != 2 { t.Error("MaxInt32(1, 2) should be 2") } if MaxInt32(-1, -2) != -1 { t.Error("MaxInt32(-1, -2) should be -1") } // Test MinInt32 if MinInt32(1, 2) != 1 { t.Error("MinInt32(1, 2) should be 1") } if MinInt32(-1, -2) != -2 { t.Error("MinInt32(-1, -2) should be -2") } // Test ClampInt32 if ClampInt32(5, 1, 3) != 3 { t.Error("ClampInt32(5, 1, 3) should be 3") } if ClampInt32(0, 1, 3) != 1 { t.Error("ClampInt32(0, 1, 3) should be 1") } if ClampInt32(2, 1, 3) != 2 { t.Error("ClampInt32(2, 1, 3) should be 2") } // Test AbsInt32 if AbsInt32(-5) != 5 { t.Error("AbsInt32(-5) should be 5") } if AbsInt32(5) != 5 { t.Error("AbsInt32(5) should be 5") } // Test SignInt32 if SignInt32(-5) != -1 { t.Error("SignInt32(-5) should be -1") } if SignInt32(5) != 1 { t.Error("SignInt32(5) should be 1") } if SignInt32(0) != 0 { t.Error("SignInt32(0) should be 0") } } func TestInt64Helpers(t *testing.T) { // Test MaxInt64 if MaxInt64(1, 2) != 2 { t.Error("MaxInt64(1, 2) should be 2") } if MaxInt64(-1, -2) != -1 { t.Error("MaxInt64(-1, -2) should be -1") } // Test MinInt64 if MinInt64(1, 2) != 1 { t.Error("MinInt64(1, 2) should be 1") } if MinInt64(-1, -2) != -2 { t.Error("MinInt64(-1, -2) should be -2") } // Test ClampInt64 if ClampInt64(5, 1, 3) != 3 { t.Error("ClampInt64(5, 1, 3) should be 3") } if ClampInt64(0, 1, 3) != 1 { t.Error("ClampInt64(0, 1, 3) should be 1") } if ClampInt64(2, 1, 3) != 2 { t.Error("ClampInt64(2, 1, 3) should be 2") } // Test AbsInt64 if AbsInt64(-5) != 5 { t.Error("AbsInt64(-5) should be 5") } if AbsInt64(5) != 5 { t.Error("AbsInt64(5) should be 5") } // Test SignInt64 if SignInt64(-5) != -1 { t.Error("SignInt64(-5) should be -1") } if SignInt64(5) != 1 { t.Error("SignInt64(5) should be 1") } if SignInt64(0) != 0 { t.Error("SignInt64(0) should be 0") } } func TestIntHelpers(t *testing.T) { // Test MaxInt if MaxInt(1, 2) != 2 { t.Error("MaxInt(1, 2) should be 2") } if MaxInt(-1, -2) != -1 { t.Error("MaxInt(-1, -2) should be -1") } // Test MinInt if MinInt(1, 2) != 1 { t.Error("MinInt(1, 2) should be 1") } if MinInt(-1, -2) != -2 { t.Error("MinInt(-1, -2) should be -2") } // Test ClampInt if ClampInt(5, 1, 3) != 3 { t.Error("ClampInt(5, 1, 3) should be 3") } if ClampInt(0, 1, 3) != 1 { t.Error("ClampInt(0, 1, 3) should be 1") } if ClampInt(2, 1, 3) != 2 { t.Error("ClampInt(2, 1, 3) should be 2") } // Test AbsInt if AbsInt(-5) != 5 { t.Error("AbsInt(-5) should be 5") } if AbsInt(5) != 5 { t.Error("AbsInt(5) should be 5") } // Test SignInt if SignInt(-5) != -1 { t.Error("SignInt(-5) should be -1") } if SignInt(5) != 1 { t.Error("SignInt(5) should be 1") } if SignInt(0) != 0 { t.Error("SignInt(0) should be 0") } } func TestUint8Helpers(t *testing.T) { // Test MaxUint8 if MaxUint8(1, 2) != 2 { t.Error("MaxUint8(1, 2) should be 2") } // Test MinUint8 if MinUint8(1, 2) != 1 { t.Error("MinUint8(1, 2) should be 1") } // Test ClampUint8 if ClampUint8(5, 1, 3) != 3 { t.Error("ClampUint8(5, 1, 3) should be 3") } if ClampUint8(0, 1, 3) != 1 { t.Error("ClampUint8(0, 1, 3) should be 1") } if ClampUint8(2, 1, 3) != 2 { t.Error("ClampUint8(2, 1, 3) should be 2") } } func TestUint16Helpers(t *testing.T) { // Test MaxUint16 if MaxUint16(1, 2) != 2 { t.Error("MaxUint16(1, 2) should be 2") } // Test MinUint16 if MinUint16(1, 2) != 1 { t.Error("MinUint16(1, 2) should be 1") } // Test ClampUint16 if ClampUint16(5, 1, 3) != 3 { t.Error("ClampUint16(5, 1, 3) should be 3") } if ClampUint16(0, 1, 3) != 1 { t.Error("ClampUint16(0, 1, 3) should be 1") } if ClampUint16(2, 1, 3) != 2 { t.Error("ClampUint16(2, 1, 3) should be 2") } } func TestUint32Helpers(t *testing.T) { // Test MaxUint32 if MaxUint32(1, 2) != 2 { t.Error("MaxUint32(1, 2) should be 2") } // Test MinUint32 if MinUint32(1, 2) != 1 { t.Error("MinUint32(1, 2) should be 1") } // Test ClampUint32 if ClampUint32(5, 1, 3) != 3 { t.Error("ClampUint32(5, 1, 3) should be 3") } if ClampUint32(0, 1, 3) != 1 { t.Error("ClampUint32(0, 1, 3) should be 1") } if ClampUint32(2, 1, 3) != 2 { t.Error("ClampUint32(2, 1, 3) should be 2") } } func TestUint64Helpers(t *testing.T) { // Test MaxUint64 if MaxUint64(1, 2) != 2 { t.Error("MaxUint64(1, 2) should be 2") } // Test MinUint64 if MinUint64(1, 2) != 1 { t.Error("MinUint64(1, 2) should be 1") } // Test ClampUint64 if ClampUint64(5, 1, 3) != 3 { t.Error("ClampUint64(5, 1, 3) should be 3") } if ClampUint64(0, 1, 3) != 1 { t.Error("ClampUint64(0, 1, 3) should be 1") } if ClampUint64(2, 1, 3) != 2 { t.Error("ClampUint64(2, 1, 3) should be 2") } } func TestUintHelpers(t *testing.T) { // Test MaxUint if MaxUint(1, 2) != 2 { t.Error("MaxUint(1, 2) should be 2") } // Test MinUint if MinUint(1, 2) != 1 { t.Error("MinUint(1, 2) should be 1") } // Test ClampUint if ClampUint(5, 1, 3) != 3 { t.Error("ClampUint(5, 1, 3) should be 3") } if ClampUint(0, 1, 3) != 1 { t.Error("ClampUint(0, 1, 3) should be 1") } if ClampUint(2, 1, 3) != 2 { t.Error("ClampUint(2, 1, 3) should be 2") } } func TestFloat32Helpers(t *testing.T) { // Test MaxFloat32 if MaxFloat32(1, 2) != 2 { t.Error("MaxFloat32(1, 2) should be 2") } if MaxFloat32(-1, -2) != -1 { t.Error("MaxFloat32(-1, -2) should be -1") } // Test MinFloat32 if MinFloat32(1, 2) != 1 { t.Error("MinFloat32(1, 2) should be 1") } if MinFloat32(-1, -2) != -2 { t.Error("MinFloat32(-1, -2) should be -2") } // Test ClampFloat32 if ClampFloat32(5, 1, 3) != 3 { t.Error("ClampFloat32(5, 1, 3) should be 3") } if ClampFloat32(0, 1, 3) != 1 { t.Error("ClampFloat32(0, 1, 3) should be 1") } if ClampFloat32(2, 1, 3) != 2 { t.Error("ClampFloat32(2, 1, 3) should be 2") } // Test AbsFloat32 if AbsFloat32(-5) != 5 { t.Error("AbsFloat32(-5) should be 5") } if AbsFloat32(5) != 5 { t.Error("AbsFloat32(5) should be 5") } // Test SignFloat32 if SignFloat32(-5) != -1 { t.Error("SignFloat32(-5) should be -1") } if SignFloat32(5) != 1 { t.Error("SignFloat32(5) should be 1") } if SignFloat32(0.0) != 0 { t.Error("SignFloat32(0.0) should be 0") } } func TestFloat64Helpers(t *testing.T) { // Test MaxFloat64 if MaxFloat64(1, 2) != 2 { t.Error("MaxFloat64(1, 2) should be 2") } if MaxFloat64(-1, -2) != -1 { t.Error("MaxFloat64(-1, -2) should be -1") } // Test MinFloat64 if MinFloat64(1, 2) != 1 { t.Error("MinFloat64(1, 2) should be 1") } if MinFloat64(-1, -2) != -2 { t.Error("MinFloat64(-1, -2) should be -2") } // Test ClampFloat64 if ClampFloat64(5, 1, 3) != 3 { t.Error("ClampFloat64(5, 1, 3) should be 3") } if ClampFloat64(0, 1, 3) != 1 { t.Error("ClampFloat64(0, 1, 3) should be 1") } if ClampFloat64(2, 1, 3) != 2 { t.Error("ClampFloat64(2, 1, 3) should be 2") } // Test AbsFloat64 if AbsFloat64(-5) != 5 { t.Error("AbsFloat64(-5) should be 5") } if AbsFloat64(5) != 5 { t.Error("AbsFloat64(5) should be 5") } // Test SignFloat64 if SignFloat64(-5) != -1 { t.Error("SignFloat64(-5) should be -1") } if SignFloat64(5) != 1 { t.Error("SignFloat64(5) should be 1") } if SignFloat64(0.0) != 0 { t.Error("SignFloat64(0.0) should be 0") } }
Result log
msg:0,success:true,log:,events:[]