go/test/convert5.go
David Chase 0e466a8d1d cmd/compile: modify float-to-[u]int so that amd64 and arm64 match
Eventual goal is that all the architectures agree, and are
sensible.  The test will be build-tagged to exclude
not-yet-handled platforms.

This change also bisects the conversion change in case of bugs.
(`bisect -compile=convert ...`)

Change-Id: I98528666b0a3fde17cbe8d69b612d01da18dce85
Reviewed-on: https://go-review.googlesource.com/c/go/+/691135
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
2025-10-09 08:23:21 -07:00

268 lines
6.7 KiB
Go

// run
// 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)
}
}