github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/moremath/moremath_test.go (about) 1 package moremath 2 3 import ( 4 "math" 5 "testing" 6 7 "github.com/tetratelabs/wazero/internal/testing/require" 8 ) 9 10 var ( 11 canonicalF32 = math.Float32frombits(F32CanonicalNaNBits) 12 arithmeticF32 = math.Float32frombits(F32ArithmeticNaNBits) 13 canonicalF64 = math.Float64frombits(F64CanonicalNaNBits) 14 arithmeticF64 = math.Float64frombits(F64ArithmeticNaNBits) 15 ) 16 17 func f32EqualBit(t *testing.T, f1, f2 float32) { 18 require.Equal(t, math.Float32bits(f1), math.Float32bits(f2)) 19 } 20 21 func f64EqualBit(t *testing.T, f1, f2 float64) { 22 require.Equal(t, math.Float64bits(f1), math.Float64bits(f2)) 23 } 24 25 func TestWasmCompatMin32(t *testing.T) { 26 require.Equal(t, WasmCompatMin32(-1.1, 123), float32(-1.1)) 27 require.Equal(t, WasmCompatMin32(-1.1, float32(math.Inf(1))), float32(-1.1)) 28 require.Equal(t, WasmCompatMin32(float32(math.Inf(-1)), 123), float32(math.Inf(-1))) 29 30 f32EqualBit(t, canonicalF32, WasmCompatMin32(canonicalF32, canonicalF32)) 31 f32EqualBit(t, canonicalF32, WasmCompatMin32(canonicalF32, arithmeticF32)) 32 f32EqualBit(t, canonicalF32, WasmCompatMin32(canonicalF32, 1.0)) 33 f32EqualBit(t, arithmeticF32, WasmCompatMin32(1.0, arithmeticF32)) 34 f32EqualBit(t, arithmeticF32, WasmCompatMin32(arithmeticF32, arithmeticF32)) 35 } 36 37 func TestWasmCompatMin64(t *testing.T) { 38 require.Equal(t, WasmCompatMin64(-1.1, 123), -1.1) 39 require.Equal(t, WasmCompatMin64(-1.1, math.Inf(1)), -1.1) 40 require.Equal(t, WasmCompatMin64(math.Inf(-1), 123), math.Inf(-1)) 41 42 f64EqualBit(t, canonicalF64, WasmCompatMin64(canonicalF64, canonicalF64)) 43 f64EqualBit(t, canonicalF64, WasmCompatMin64(canonicalF64, arithmeticF64)) 44 f64EqualBit(t, canonicalF64, WasmCompatMin64(canonicalF64, 1.0)) 45 f64EqualBit(t, arithmeticF64, WasmCompatMin64(1.0, arithmeticF64)) 46 f64EqualBit(t, arithmeticF64, WasmCompatMin64(arithmeticF64, arithmeticF64)) 47 } 48 49 func TestWasmCompatMax32(t *testing.T) { 50 require.Equal(t, WasmCompatMax32(-1.1, 123), float32(123)) 51 require.Equal(t, WasmCompatMax32(-1.1, float32(math.Inf(1))), float32(math.Inf(1))) 52 require.Equal(t, WasmCompatMax32(float32(math.Inf(-1)), 123), float32(123)) 53 54 f32EqualBit(t, canonicalF32, WasmCompatMax32(canonicalF32, canonicalF32)) 55 f32EqualBit(t, canonicalF32, WasmCompatMax32(canonicalF32, arithmeticF32)) 56 f32EqualBit(t, canonicalF32, WasmCompatMax32(canonicalF32, 1.0)) 57 f32EqualBit(t, arithmeticF32, WasmCompatMax32(1.0, arithmeticF32)) 58 f32EqualBit(t, arithmeticF32, WasmCompatMax32(arithmeticF32, arithmeticF32)) 59 } 60 61 func TestWasmCompatMax64(t *testing.T) { 62 require.Equal(t, WasmCompatMax64(-1.1, 123.1), 123.1) 63 require.Equal(t, WasmCompatMax64(-1.1, math.Inf(1)), math.Inf(1)) 64 require.Equal(t, WasmCompatMax64(math.Inf(-1), 123.1), 123.1) 65 66 f64EqualBit(t, canonicalF64, WasmCompatMax64(canonicalF64, canonicalF64)) 67 f64EqualBit(t, canonicalF64, WasmCompatMax64(canonicalF64, arithmeticF64)) 68 f64EqualBit(t, canonicalF64, WasmCompatMax64(canonicalF64, 1.0)) 69 f64EqualBit(t, arithmeticF64, WasmCompatMax64(1.0, arithmeticF64)) 70 f64EqualBit(t, arithmeticF64, WasmCompatMax64(arithmeticF64, arithmeticF64)) 71 } 72 73 func TestWasmCompatNearestF32(t *testing.T) { 74 require.Equal(t, WasmCompatNearestF32(-1.5), float32(-2.0)) 75 76 // This is the diff from math.Round. 77 require.Equal(t, WasmCompatNearestF32(-4.5), float32(-4.0)) 78 require.Equal(t, float32(math.Round(-4.5)), float32(-5.0)) 79 80 // Prevent constant folding by using two variables. -float32(0) is not actually negative. 81 // https://github.com/golang/go/issues/2196 82 zero := float32(0) 83 negZero := -zero 84 85 // Sign bit preserved for +/- zero 86 require.False(t, math.Signbit(float64(zero))) 87 require.False(t, math.Signbit(float64(WasmCompatNearestF32(zero)))) 88 require.True(t, math.Signbit(float64(negZero))) 89 require.True(t, math.Signbit(float64(WasmCompatNearestF32(negZero)))) 90 } 91 92 func TestWasmCompatNearestF64(t *testing.T) { 93 require.Equal(t, WasmCompatNearestF64(-1.5), -2.0) 94 95 // This is the diff from math.Round. 96 require.Equal(t, WasmCompatNearestF64(-4.5), -4.0) 97 require.Equal(t, math.Round(-4.5), -5.0) 98 99 // Prevent constant folding by using two variables. -float64(0) is not actually negative. 100 // https://github.com/golang/go/issues/2196 101 zero := float64(0) 102 negZero := -zero 103 104 // Sign bit preserved for +/- zero 105 require.False(t, math.Signbit(zero)) 106 require.False(t, math.Signbit(WasmCompatNearestF64(zero))) 107 require.True(t, math.Signbit(negZero)) 108 require.True(t, math.Signbit(WasmCompatNearestF64(negZero))) 109 } 110 111 func TestUniOp_NaNPropagation(t *testing.T) { 112 tests := []struct { 113 name string 114 f32 func(f float32) float32 115 f64 func(f float64) float64 116 }{ 117 {name: "trunc.f32", f32: WasmCompatTruncF32}, 118 {name: "trunc.f64", f64: WasmCompatTruncF64}, 119 {name: "nearest.f32", f32: WasmCompatNearestF32}, 120 {name: "nearest.f64", f64: WasmCompatNearestF64}, 121 {name: "ceil.f32", f32: WasmCompatCeilF32}, 122 {name: "ceil.f64", f64: WasmCompatCeilF64}, 123 {name: "floor.f32", f32: WasmCompatFloorF32}, 124 {name: "floor.f64", f64: WasmCompatFloorF64}, 125 } 126 127 for _, tc := range tests { 128 tc := tc 129 t.Run(tc.name, func(t *testing.T) { 130 if tc.f32 != nil { 131 f32EqualBit(t, canonicalF32, tc.f32(canonicalF32)) 132 f32EqualBit(t, arithmeticF32, tc.f32(arithmeticF32)) 133 } else { 134 f64EqualBit(t, canonicalF64, tc.f64(canonicalF64)) 135 f64EqualBit(t, arithmeticF64, tc.f64(arithmeticF64)) 136 } 137 }) 138 } 139 } 140 141 func Test_returnF32UniOp(t *testing.T) { 142 for _, tc := range []struct { 143 original, result, exp uint32 144 }{ 145 {result: math.Float32bits(1.1), exp: math.Float32bits(1.1)}, 146 {original: 1.0, result: math.Float32bits(float32(math.NaN())), exp: F32CanonicalNaNBits}, 147 {original: F32ArithmeticNaNBits, result: math.Float32bits(float32(math.NaN())), exp: F32ArithmeticNaNBits}, 148 // Even if the MSB of the payload is unset (signaling NaN), the result must it set, therefore an arithmetic NaN. 149 {original: F32ArithmeticNaNBits ^ (1 << 22), result: math.Float32bits(float32(math.NaN())), exp: F32ArithmeticNaNBits}, 150 } { 151 actual := returnF32UniOp(math.Float32frombits(tc.original), math.Float32frombits(tc.result)) 152 require.Equal(t, tc.exp, math.Float32bits(actual)) 153 } 154 } 155 156 func Test_returnF64UniOp(t *testing.T) { 157 for _, tc := range []struct { 158 original, result, exp uint64 159 }{ 160 {result: math.Float64bits(1.1), exp: math.Float64bits(1.1)}, 161 {original: 1.0, result: math.Float64bits(math.NaN()), exp: F64CanonicalNaNBits}, 162 {original: F64ArithmeticNaNBits, result: math.Float64bits(math.NaN()), exp: F64ArithmeticNaNBits}, 163 // Even if the MSB of the payload is unset (signaling NaN), the result must it set, therefore an arithmetic NaN. 164 {original: F64ArithmeticNaNBits ^ (1 << 51), result: math.Float64bits(math.NaN()), exp: F64ArithmeticNaNBits}, 165 } { 166 actual := returnF64UniOp(math.Float64frombits(tc.original), math.Float64frombits(tc.result)) 167 require.Equal(t, tc.exp, math.Float64bits(actual)) 168 } 169 } 170 171 func Test_returnF32NaNBinOp(t *testing.T) { 172 for _, tc := range []struct { 173 x, y, exp uint32 174 }{ 175 {x: F32CanonicalNaNBits, y: F32CanonicalNaNBits, exp: F32CanonicalNaNBits}, 176 {x: F32CanonicalNaNBits, y: 0, exp: F32CanonicalNaNBits}, 177 {x: 0, y: F32CanonicalNaNBits, exp: F32CanonicalNaNBits}, 178 {x: F32ArithmeticNaNBits, y: F32ArithmeticNaNBits, exp: F32ArithmeticNaNBits}, 179 {x: F32ArithmeticNaNBits, y: 0, exp: F32ArithmeticNaNBits}, 180 {x: 0, y: F32ArithmeticNaNBits, exp: F32ArithmeticNaNBits}, 181 // Even if the MSB of the payload is unset (signaling NaN), the result must it set, therefore an arithmetic NaN. 182 {x: 0, y: F32ArithmeticNaNBits ^ (1 << 22), exp: F32ArithmeticNaNBits}, 183 {x: F32ArithmeticNaNBits ^ (1 << 22), y: 0, exp: F32ArithmeticNaNBits}, 184 } { 185 actual := returnF32NaNBinOp(math.Float32frombits(tc.x), math.Float32frombits(tc.y)) 186 require.Equal(t, tc.exp, math.Float32bits(actual)) 187 } 188 } 189 190 func Test_returnF64NaNBinOp(t *testing.T) { 191 for _, tc := range []struct { 192 x, y, exp uint64 193 }{ 194 {x: F64CanonicalNaNBits, y: F64CanonicalNaNBits, exp: F64CanonicalNaNBits}, 195 {x: F64CanonicalNaNBits, y: 0, exp: F64CanonicalNaNBits}, 196 {x: 0, y: F64CanonicalNaNBits, exp: F64CanonicalNaNBits}, 197 {x: F64ArithmeticNaNBits, y: F64ArithmeticNaNBits, exp: F64ArithmeticNaNBits}, 198 {x: F64ArithmeticNaNBits, y: 0, exp: F64ArithmeticNaNBits}, 199 {x: 0, y: F64ArithmeticNaNBits, exp: F64ArithmeticNaNBits}, 200 // Even if the MSB of the payload is unset (signaling NaN), the result must it set, therefore an arithmetic NaN. 201 {x: 0, y: F64ArithmeticNaNBits ^ (1 << 51), exp: F64ArithmeticNaNBits}, 202 {x: F64ArithmeticNaNBits ^ (1 << 51), y: 0, exp: F64ArithmeticNaNBits}, 203 } { 204 actual := returnF64NaNBinOp(math.Float64frombits(tc.x), math.Float64frombits(tc.y)) 205 require.Equal(t, tc.exp, math.Float64bits(actual)) 206 } 207 }