208 lines
8.4 KiB
Go
208 lines
8.4 KiB
Go
package moremath
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
)
|
|
|
|
var (
|
|
canonicalF32 = math.Float32frombits(F32CanonicalNaNBits)
|
|
arithmeticF32 = math.Float32frombits(F32ArithmeticNaNBits)
|
|
canonicalF64 = math.Float64frombits(F64CanonicalNaNBits)
|
|
arithmeticF64 = math.Float64frombits(F64ArithmeticNaNBits)
|
|
)
|
|
|
|
func f32EqualBit(t *testing.T, f1, f2 float32) {
|
|
require.Equal(t, math.Float32bits(f1), math.Float32bits(f2))
|
|
}
|
|
|
|
func f64EqualBit(t *testing.T, f1, f2 float64) {
|
|
require.Equal(t, math.Float64bits(f1), math.Float64bits(f2))
|
|
}
|
|
|
|
func TestWasmCompatMin32(t *testing.T) {
|
|
require.Equal(t, WasmCompatMin32(-1.1, 123), float32(-1.1))
|
|
require.Equal(t, WasmCompatMin32(-1.1, float32(math.Inf(1))), float32(-1.1))
|
|
require.Equal(t, WasmCompatMin32(float32(math.Inf(-1)), 123), float32(math.Inf(-1)))
|
|
|
|
f32EqualBit(t, canonicalF32, WasmCompatMin32(canonicalF32, canonicalF32))
|
|
f32EqualBit(t, canonicalF32, WasmCompatMin32(canonicalF32, arithmeticF32))
|
|
f32EqualBit(t, canonicalF32, WasmCompatMin32(canonicalF32, 1.0))
|
|
f32EqualBit(t, arithmeticF32, WasmCompatMin32(1.0, arithmeticF32))
|
|
f32EqualBit(t, arithmeticF32, WasmCompatMin32(arithmeticF32, arithmeticF32))
|
|
}
|
|
|
|
func TestWasmCompatMin64(t *testing.T) {
|
|
require.Equal(t, WasmCompatMin64(-1.1, 123), -1.1)
|
|
require.Equal(t, WasmCompatMin64(-1.1, math.Inf(1)), -1.1)
|
|
require.Equal(t, WasmCompatMin64(math.Inf(-1), 123), math.Inf(-1))
|
|
|
|
f64EqualBit(t, canonicalF64, WasmCompatMin64(canonicalF64, canonicalF64))
|
|
f64EqualBit(t, canonicalF64, WasmCompatMin64(canonicalF64, arithmeticF64))
|
|
f64EqualBit(t, canonicalF64, WasmCompatMin64(canonicalF64, 1.0))
|
|
f64EqualBit(t, arithmeticF64, WasmCompatMin64(1.0, arithmeticF64))
|
|
f64EqualBit(t, arithmeticF64, WasmCompatMin64(arithmeticF64, arithmeticF64))
|
|
}
|
|
|
|
func TestWasmCompatMax32(t *testing.T) {
|
|
require.Equal(t, WasmCompatMax32(-1.1, 123), float32(123))
|
|
require.Equal(t, WasmCompatMax32(-1.1, float32(math.Inf(1))), float32(math.Inf(1)))
|
|
require.Equal(t, WasmCompatMax32(float32(math.Inf(-1)), 123), float32(123))
|
|
|
|
f32EqualBit(t, canonicalF32, WasmCompatMax32(canonicalF32, canonicalF32))
|
|
f32EqualBit(t, canonicalF32, WasmCompatMax32(canonicalF32, arithmeticF32))
|
|
f32EqualBit(t, canonicalF32, WasmCompatMax32(canonicalF32, 1.0))
|
|
f32EqualBit(t, arithmeticF32, WasmCompatMax32(1.0, arithmeticF32))
|
|
f32EqualBit(t, arithmeticF32, WasmCompatMax32(arithmeticF32, arithmeticF32))
|
|
}
|
|
|
|
func TestWasmCompatMax64(t *testing.T) {
|
|
require.Equal(t, WasmCompatMax64(-1.1, 123.1), 123.1)
|
|
require.Equal(t, WasmCompatMax64(-1.1, math.Inf(1)), math.Inf(1))
|
|
require.Equal(t, WasmCompatMax64(math.Inf(-1), 123.1), 123.1)
|
|
|
|
f64EqualBit(t, canonicalF64, WasmCompatMax64(canonicalF64, canonicalF64))
|
|
f64EqualBit(t, canonicalF64, WasmCompatMax64(canonicalF64, arithmeticF64))
|
|
f64EqualBit(t, canonicalF64, WasmCompatMax64(canonicalF64, 1.0))
|
|
f64EqualBit(t, arithmeticF64, WasmCompatMax64(1.0, arithmeticF64))
|
|
f64EqualBit(t, arithmeticF64, WasmCompatMax64(arithmeticF64, arithmeticF64))
|
|
}
|
|
|
|
func TestWasmCompatNearestF32(t *testing.T) {
|
|
require.Equal(t, WasmCompatNearestF32(-1.5), float32(-2.0))
|
|
|
|
// This is the diff from math.Round.
|
|
require.Equal(t, WasmCompatNearestF32(-4.5), float32(-4.0))
|
|
require.Equal(t, float32(math.Round(-4.5)), float32(-5.0))
|
|
|
|
// Prevent constant folding by using two variables. -float32(0) is not actually negative.
|
|
// https://github.com/golang/go/issues/2196
|
|
zero := float32(0)
|
|
negZero := -zero
|
|
|
|
// Sign bit preserved for +/- zero
|
|
require.False(t, math.Signbit(float64(zero)))
|
|
require.False(t, math.Signbit(float64(WasmCompatNearestF32(zero))))
|
|
require.True(t, math.Signbit(float64(negZero)))
|
|
require.True(t, math.Signbit(float64(WasmCompatNearestF32(negZero))))
|
|
}
|
|
|
|
func TestWasmCompatNearestF64(t *testing.T) {
|
|
require.Equal(t, WasmCompatNearestF64(-1.5), -2.0)
|
|
|
|
// This is the diff from math.Round.
|
|
require.Equal(t, WasmCompatNearestF64(-4.5), -4.0)
|
|
require.Equal(t, math.Round(-4.5), -5.0)
|
|
|
|
// Prevent constant folding by using two variables. -float64(0) is not actually negative.
|
|
// https://github.com/golang/go/issues/2196
|
|
zero := float64(0)
|
|
negZero := -zero
|
|
|
|
// Sign bit preserved for +/- zero
|
|
require.False(t, math.Signbit(zero))
|
|
require.False(t, math.Signbit(WasmCompatNearestF64(zero)))
|
|
require.True(t, math.Signbit(negZero))
|
|
require.True(t, math.Signbit(WasmCompatNearestF64(negZero)))
|
|
}
|
|
|
|
func TestUniOp_NaNPropagation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
f32 func(f float32) float32
|
|
f64 func(f float64) float64
|
|
}{
|
|
{name: "trunc.f32", f32: WasmCompatTruncF32},
|
|
{name: "trunc.f64", f64: WasmCompatTruncF64},
|
|
{name: "nearest.f32", f32: WasmCompatNearestF32},
|
|
{name: "nearest.f64", f64: WasmCompatNearestF64},
|
|
{name: "ceil.f32", f32: WasmCompatCeilF32},
|
|
{name: "ceil.f64", f64: WasmCompatCeilF64},
|
|
{name: "floor.f32", f32: WasmCompatFloorF32},
|
|
{name: "floor.f64", f64: WasmCompatFloorF64},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if tc.f32 != nil {
|
|
f32EqualBit(t, canonicalF32, tc.f32(canonicalF32))
|
|
f32EqualBit(t, arithmeticF32, tc.f32(arithmeticF32))
|
|
} else {
|
|
f64EqualBit(t, canonicalF64, tc.f64(canonicalF64))
|
|
f64EqualBit(t, arithmeticF64, tc.f64(arithmeticF64))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_returnF32UniOp(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
original, result, exp uint32
|
|
}{
|
|
{result: math.Float32bits(1.1), exp: math.Float32bits(1.1)},
|
|
{original: 1.0, result: math.Float32bits(float32(math.NaN())), exp: F32CanonicalNaNBits},
|
|
{original: F32ArithmeticNaNBits, result: math.Float32bits(float32(math.NaN())), exp: F32ArithmeticNaNBits},
|
|
// Even if the MSB of the payload is unset (signaling NaN), the result must it set, therefore an arithmetic NaN.
|
|
{original: F32ArithmeticNaNBits ^ (1 << 22), result: math.Float32bits(float32(math.NaN())), exp: F32ArithmeticNaNBits},
|
|
} {
|
|
actual := returnF32UniOp(math.Float32frombits(tc.original), math.Float32frombits(tc.result))
|
|
require.Equal(t, tc.exp, math.Float32bits(actual))
|
|
}
|
|
}
|
|
|
|
func Test_returnF64UniOp(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
original, result, exp uint64
|
|
}{
|
|
{result: math.Float64bits(1.1), exp: math.Float64bits(1.1)},
|
|
{original: 1.0, result: math.Float64bits(math.NaN()), exp: F64CanonicalNaNBits},
|
|
{original: F64ArithmeticNaNBits, result: math.Float64bits(math.NaN()), exp: F64ArithmeticNaNBits},
|
|
// Even if the MSB of the payload is unset (signaling NaN), the result must it set, therefore an arithmetic NaN.
|
|
{original: F64ArithmeticNaNBits ^ (1 << 51), result: math.Float64bits(math.NaN()), exp: F64ArithmeticNaNBits},
|
|
} {
|
|
actual := returnF64UniOp(math.Float64frombits(tc.original), math.Float64frombits(tc.result))
|
|
require.Equal(t, tc.exp, math.Float64bits(actual))
|
|
}
|
|
}
|
|
|
|
func Test_returnF32NaNBinOp(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
x, y, exp uint32
|
|
}{
|
|
{x: F32CanonicalNaNBits, y: F32CanonicalNaNBits, exp: F32CanonicalNaNBits},
|
|
{x: F32CanonicalNaNBits, y: 0, exp: F32CanonicalNaNBits},
|
|
{x: 0, y: F32CanonicalNaNBits, exp: F32CanonicalNaNBits},
|
|
{x: F32ArithmeticNaNBits, y: F32ArithmeticNaNBits, exp: F32ArithmeticNaNBits},
|
|
{x: F32ArithmeticNaNBits, y: 0, exp: F32ArithmeticNaNBits},
|
|
{x: 0, y: F32ArithmeticNaNBits, exp: F32ArithmeticNaNBits},
|
|
// Even if the MSB of the payload is unset (signaling NaN), the result must it set, therefore an arithmetic NaN.
|
|
{x: 0, y: F32ArithmeticNaNBits ^ (1 << 22), exp: F32ArithmeticNaNBits},
|
|
{x: F32ArithmeticNaNBits ^ (1 << 22), y: 0, exp: F32ArithmeticNaNBits},
|
|
} {
|
|
actual := returnF32NaNBinOp(math.Float32frombits(tc.x), math.Float32frombits(tc.y))
|
|
require.Equal(t, tc.exp, math.Float32bits(actual))
|
|
}
|
|
}
|
|
|
|
func Test_returnF64NaNBinOp(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
x, y, exp uint64
|
|
}{
|
|
{x: F64CanonicalNaNBits, y: F64CanonicalNaNBits, exp: F64CanonicalNaNBits},
|
|
{x: F64CanonicalNaNBits, y: 0, exp: F64CanonicalNaNBits},
|
|
{x: 0, y: F64CanonicalNaNBits, exp: F64CanonicalNaNBits},
|
|
{x: F64ArithmeticNaNBits, y: F64ArithmeticNaNBits, exp: F64ArithmeticNaNBits},
|
|
{x: F64ArithmeticNaNBits, y: 0, exp: F64ArithmeticNaNBits},
|
|
{x: 0, y: F64ArithmeticNaNBits, exp: F64ArithmeticNaNBits},
|
|
// Even if the MSB of the payload is unset (signaling NaN), the result must it set, therefore an arithmetic NaN.
|
|
{x: 0, y: F64ArithmeticNaNBits ^ (1 << 51), exp: F64ArithmeticNaNBits},
|
|
{x: F64ArithmeticNaNBits ^ (1 << 51), y: 0, exp: F64ArithmeticNaNBits},
|
|
} {
|
|
actual := returnF64NaNBinOp(math.Float64frombits(tc.x), math.Float64frombits(tc.y))
|
|
require.Equal(t, tc.exp, math.Float64bits(actual))
|
|
}
|
|
}
|