| // run -gcflags=-d=converthash=qy |
| |
| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build !wasm && !386 && !arm && !mips |
| |
| // TODO fix this to work for wasm and 32-bit architectures. |
| // Doing more than this, however, expands the change. |
| |
| package main |
| |
| import ( |
| "fmt" |
| "runtime" |
| ) |
| |
| // This test checks that conversion from floats to (unsigned) 32 and 64-bit |
| // integers has the same sensible behavior for corner cases, and that the |
| // conversions to smaller integers agree. Because outliers are platform- |
| // independent, the "golden test" for smaller integers is more like of |
| // a "gold-ish test" and subject to change. |
| |
| //go:noinline |
| func id[T any](x T) T { |
| return x |
| } |
| |
| //go:noinline |
| func want[T comparable](name string, x, y T) { |
| if x != y { |
| _, _, line, _ := runtime.Caller(1) |
| fmt.Println("FAIL at line", line, "var =", name, "got =", x, "want =", y) |
| } |
| } |
| |
| //go:noinline |
| func log[T comparable](name string, x T) { |
| fmt.Println(name, x) |
| } |
| |
| const ( |
| // pX = max positive signed X bit |
| // nX = min negative signed X bit |
| // uX = max unsigned X bit |
| // tX = two to the X |
| p32 = 2147483647 |
| n32 = -2147483648 |
| u32 = 4294967295 |
| p64 = 9223372036854775807 |
| n64 = -9223372036854775808 |
| u64 = 18446744073709551615 |
| t44 = 1 << 44 |
| ) |
| |
| func main() { |
| one := 1.0 |
| minus1_32 := id(float32(-1.0)) |
| minus1_64 := id(float64(-1.0)) |
| p32_plus4k_plus1 := id(float32(p32 + 4096 + 1)) // want this to be precise and fit in 24 bits mantissa |
| p64_plus4k_plus1 := id(float64(p64 + 4096 + 1)) // want this to be precise and fit in 53 bits mantissa |
| n32_minus4k := id(float32(n32 - 4096)) |
| n64_minus4k := id(float64(n64 - 4096)) |
| inf_32 := id(float32(one / 0)) |
| inf_64 := id(float64(one / 0)) |
| ninf_32 := id(float32(-one / 0)) |
| ninf_64 := id(float64(-one / 0)) |
| |
| // int32 conversions |
| int32Tests := []struct { |
| name string |
| input any // Use any to handle both float32 and float64 |
| expected int32 |
| }{ |
| {"minus1_32", minus1_32, -1}, |
| {"minus1_64", minus1_64, -1}, |
| {"p32_plus4k_plus1", p32_plus4k_plus1, p32}, |
| {"p64_plus4k_plus1", p64_plus4k_plus1, p32}, |
| {"n32_minus4k", n32_minus4k, n32}, |
| {"n64_minus4k", n64_minus4k, n32}, |
| {"inf_32", inf_32, p32}, |
| {"inf_64", inf_64, p32}, |
| {"ninf_32", ninf_32, n32}, |
| {"ninf_64", ninf_64, n32}, |
| } |
| |
| for _, test := range int32Tests { |
| var converted int32 |
| switch v := test.input.(type) { |
| case float32: |
| converted = int32(v) |
| case float64: |
| converted = int32(v) |
| } |
| want(test.name, converted, test.expected) |
| } |
| |
| // int64 conversions |
| int64Tests := []struct { |
| name string |
| input any |
| expected int64 |
| }{ |
| {"minus1_32", minus1_32, -1}, |
| {"minus1_64", minus1_64, -1}, |
| {"p32_plus4k_plus1", p32_plus4k_plus1, p32 + 4096 + 1}, |
| {"p64_plus4k_plus1", p64_plus4k_plus1, p64}, |
| {"n32_minus4k", n32_minus4k, n32 - 4096}, |
| {"n64_minus4k", n64_minus4k, n64}, |
| {"inf_32", inf_32, p64}, |
| {"inf_64", inf_64, p64}, |
| {"ninf_32", ninf_32, n64}, |
| {"ninf_64", ninf_64, n64}, |
| } |
| |
| for _, test := range int64Tests { |
| var converted int64 |
| switch v := test.input.(type) { |
| case float32: |
| converted = int64(v) |
| case float64: |
| converted = int64(v) |
| } |
| want(test.name, converted, test.expected) |
| } |
| |
| // uint32 conversions |
| uint32Tests := []struct { |
| name string |
| input any |
| expected uint32 |
| }{ |
| {"minus1_32", minus1_32, 0}, |
| {"minus1_64", minus1_64, 0}, |
| {"p32_plus4k_plus1", p32_plus4k_plus1, p32 + 4096 + 1}, |
| {"p64_plus4k_plus1", p64_plus4k_plus1, u32}, |
| {"n32_minus4k", n32_minus4k, 0}, |
| {"n64_minus4k", n64_minus4k, 0}, |
| {"inf_32", inf_32, u32}, |
| {"inf_64", inf_64, u32}, |
| {"ninf_32", ninf_32, 0}, |
| {"ninf_64", ninf_64, 0}, |
| } |
| |
| for _, test := range uint32Tests { |
| var converted uint32 |
| switch v := test.input.(type) { |
| case float32: |
| converted = uint32(v) |
| case float64: |
| converted = uint32(v) |
| } |
| want(test.name, converted, test.expected) |
| } |
| |
| u64_plus4k_plus1_64 := id(float64(u64 + 4096 + 1)) |
| u64_plust44_plus1_32 := id(float32(u64 + t44 + 1)) |
| |
| // uint64 conversions |
| uint64Tests := []struct { |
| name string |
| input any |
| expected uint64 |
| }{ |
| {"minus1_32", minus1_32, 0}, |
| {"minus1_64", minus1_64, 0}, |
| {"p32_plus4k_plus1", p32_plus4k_plus1, p32 + 4096 + 1}, |
| {"p64_plus4k_plus1", p64_plus4k_plus1, p64 + 4096 + 1}, |
| {"n32_minus4k", n32_minus4k, 0}, |
| {"n64_minus4k", n64_minus4k, 0}, |
| {"inf_32", inf_32, u64}, |
| {"inf_64", inf_64, u64}, |
| {"ninf_32", ninf_32, 0}, |
| {"ninf_64", ninf_64, 0}, |
| {"u64_plus4k_plus1_64", u64_plus4k_plus1_64, u64}, |
| {"u64_plust44_plus1_32", u64_plust44_plus1_32, u64}, |
| } |
| |
| for _, test := range uint64Tests { |
| var converted uint64 |
| switch v := test.input.(type) { |
| case float32: |
| converted = uint64(v) |
| case float64: |
| converted = uint64(v) |
| } |
| want(test.name, converted, test.expected) |
| } |
| |
| // for smaller integer types |
| // TODO the overflow behavior is dubious, maybe we should fix it to be more sensible, e.g. saturating. |
| fmt.Println("Below this are 'golden' results to check for consistency across platforms. Overflow behavior is not necessarily what we want") |
| |
| u8plus2 := id(float64(257)) |
| p8minus1 := id(float32(126)) |
| n8plus2 := id(float64(-126)) |
| n8minusone := id(float32(-129)) |
| |
| fmt.Println("\nuint8 conversions") |
| uint8Tests := []struct { |
| name string |
| input any |
| }{ |
| {"minus1_32", minus1_32}, |
| {"minus1_64", minus1_64}, |
| {"p32_plus4k_plus1", p32_plus4k_plus1}, |
| {"p64_plus4k_plus1", p64_plus4k_plus1}, |
| {"n32_minus4k", n32_minus4k}, |
| {"n64_minus4k", n64_minus4k}, |
| {"inf_32", inf_32}, |
| {"inf_64", inf_64}, |
| {"ninf_32", ninf_32}, |
| {"ninf_64", ninf_64}, |
| {"u64_plus4k_plus1_64", u64_plus4k_plus1_64}, |
| {"u64_plust44_plus1_32", u64_plust44_plus1_32}, |
| {"u8plus2", u8plus2}, |
| {"p8minus1", p8minus1}, |
| {"n8plus2", n8plus2}, |
| {"n8minusone", n8minusone}, |
| } |
| |
| for _, test := range uint8Tests { |
| var converted uint8 |
| switch v := test.input.(type) { |
| case float32: |
| converted = uint8(v) |
| case float64: |
| converted = uint8(v) |
| } |
| log(test.name, converted) |
| } |
| |
| fmt.Println("\nint8 conversions") |
| int8Tests := []struct { |
| name string |
| input any |
| }{ |
| {"minus1_32", minus1_32}, |
| {"minus1_64", minus1_64}, |
| {"p32_plus4k_plus1", p32_plus4k_plus1}, |
| {"p64_plus4k_plus1", p64_plus4k_plus1}, |
| {"n32_minus4k", n32_minus4k}, |
| {"n64_minus4k", n64_minus4k}, |
| {"inf_32", inf_32}, |
| {"inf_64", inf_64}, |
| {"ninf_32", ninf_32}, |
| {"ninf_64", ninf_64}, |
| {"u64_plus4k_plus1_64", u64_plus4k_plus1_64}, |
| {"u64_plust44_plus1_32", u64_plust44_plus1_32}, |
| {"u8plus2", u8plus2}, |
| {"p8minus1", p8minus1}, |
| {"n8plus2", n8plus2}, |
| {"n8minusone", n8minusone}, |
| } |
| |
| for _, test := range int8Tests { |
| var converted int8 |
| switch v := test.input.(type) { |
| case float32: |
| converted = int8(v) |
| case float64: |
| converted = int8(v) |
| } |
| log(test.name, converted) |
| } |
| |
| } |