github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/moremath/moremath_test.go (about)

     1  package moremath
     2  
     3  import (
     4  	"math"
     5  	"testing"
     6  
     7  	"github.com/bananabytelabs/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  }