Skip to content

Commit b9f3acc

Browse files
committed
runtime: adjust softfloat corner cases to match amd64/arm64
This chooses saturating behavior for over/underflow. Change-Id: I96a33ef73feacdafe8310f893de445060bc1a536 Reviewed-on: https://go-review.googlesource.com/c/go/+/709595 Reviewed-by: Keith Randall <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 78d75b3 commit b9f3acc

File tree

4 files changed

+107
-25
lines changed

4 files changed

+107
-25
lines changed

src/runtime/export_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var F32to64 = f32to64
2525
var Fcmp64 = fcmp64
2626
var Fintto64 = fintto64
2727
var F64toint = f64toint
28+
var F64touint = f64touint64
2829

2930
var Entersyscall = entersyscall
3031
var Exitsyscall = exitsyscall

src/runtime/softfloat64.go

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ const (
2626
neg32 uint32 = 1 << (expbits32 + mantbits32)
2727
)
2828

29+
// If F is not NaN and not Inf, then f == (-1)**sign * mantissa * 2**(exp-52)
30+
// The mantissa and exp are adjusted from their stored representation so
31+
// that the mantissa includes the formerly implicit 1, the exponent bias
32+
// is removed, and denormalized floats to put a 1 in the expected
33+
// (1<<mantbits64) position.
2934
func funpack64(f uint64) (sign, mant uint64, exp int, inf, nan bool) {
3035
sign = f & (1 << (mantbits64 + expbits64))
3136
mant = f & (1<<mantbits64 - 1)
@@ -371,24 +376,25 @@ func fcmp64(f, g uint64) (cmp int32, isnan bool) {
371376
return 0, false
372377
}
373378

374-
func f64toint(f uint64) (val int64, ok bool) {
379+
// returns saturated-conversion int64 value of f
380+
// and whether the input was NaN (in which case it
381+
// may not match the "hardware" conversion).
382+
func f64toint(f uint64) (val int64, isNan bool) {
375383
fs, fm, fe, fi, fn := funpack64(f)
376384

377385
switch {
378-
case fi, fn: // NaN
379-
return 0, false
386+
387+
case fn: // NaN
388+
return -0x8000_0000_0000_0000, false
380389

381390
case fe < -1: // f < 0.5
382391
return 0, false
383392

384-
case fe > 63: // f >= 2^63
385-
if fs != 0 && fm == 0 { // f == -2^63
386-
return -1 << 63, true
387-
}
393+
case fi || fe >= 63: // |f| >= 2^63, including infinity
388394
if fs != 0 {
389-
return 0, false
395+
return -0x8000_0000_0000_0000, true
390396
}
391-
return 0, false
397+
return 0x7fff_ffff_ffff_ffff, true
392398
}
393399

394400
for fe > int(mantbits64) {
@@ -400,12 +406,51 @@ func f64toint(f uint64) (val int64, ok bool) {
400406
fm >>= 1
401407
}
402408
val = int64(fm)
409+
if val < 0 {
410+
if fs != 0 {
411+
return -0x8000_0000_0000_0000, true
412+
}
413+
return 0x7fff_ffff_ffff_ffff, true
414+
}
403415
if fs != 0 {
404416
val = -val
405417
}
406418
return val, true
407419
}
408420

421+
// returns saturated-conversion uint64 value of f
422+
// and whether the input was NaN (in which case it
423+
// may not match the "hardware" conversion).
424+
func f64touint(f uint64) (val uint64, isNan bool) {
425+
fs, fm, fe, fi, fn := funpack64(f)
426+
427+
switch {
428+
429+
case fn: // NaN
430+
return 0xffff_ffff_ffff_ffff, false
431+
432+
case fs != 0: // all negative, including -Inf, are zero
433+
return 0, true
434+
435+
case fi || fe >= 64: // positive infinity or f >= 2^64
436+
return 0xffff_ffff_ffff_ffff, true
437+
438+
case fe < -1: // f < 0.5
439+
return 0, true
440+
}
441+
442+
for fe > int(mantbits64) {
443+
fe--
444+
fm <<= 1
445+
}
446+
for fe < int(mantbits64) {
447+
fe++
448+
fm >>= 1
449+
}
450+
val = fm
451+
return val, true
452+
}
453+
409454
func fintto64(val int64) (f uint64) {
410455
fs := uint64(val) & (1 << 63)
411456
mant := uint64(val)
@@ -564,6 +609,12 @@ func fint64to64(x int64) uint64 {
564609

565610
func f32toint32(x uint32) int32 {
566611
val, _ := f64toint(f32to64(x))
612+
if val >= 0x7fffffff {
613+
return 0x7fffffff
614+
}
615+
if val < -0x80000000 {
616+
return -0x80000000
617+
}
567618
return int32(val)
568619
}
569620

@@ -574,6 +625,12 @@ func f32toint64(x uint32) int64 {
574625

575626
func f64toint32(x uint64) int32 {
576627
val, _ := f64toint(x)
628+
if val >= 0x7fffffff {
629+
return 0x7fffffff
630+
}
631+
if val < -0x80000000 {
632+
return -0x80000000
633+
}
577634
return int32(val)
578635
}
579636

@@ -583,23 +640,13 @@ func f64toint64(x uint64) int64 {
583640
}
584641

585642
func f64touint64(x uint64) uint64 {
586-
var m uint64 = 0x43e0000000000000 // float64 1<<63
587-
if fgt64(m, x) {
588-
return uint64(f64toint64(x))
589-
}
590-
y := fadd64(x, -m)
591-
z := uint64(f64toint64(y))
592-
return z | (1 << 63)
643+
val, _ := f64touint(x)
644+
return val
593645
}
594646

595647
func f32touint64(x uint32) uint64 {
596-
var m uint32 = 0x5f000000 // float32 1<<63
597-
if fgt32(m, x) {
598-
return uint64(f32toint64(x))
599-
}
600-
y := fadd32(x, -m)
601-
z := uint64(f32toint64(y))
602-
return z | (1 << 63)
648+
val, _ := f64touint(f32to64(x))
649+
return val
603650
}
604651

605652
func fuint64to64(x uint64) uint64 {

src/runtime/softfloat64_test.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,24 @@ func div(x, y float64) float64 { return x / y }
2828
func TestFloat64(t *testing.T) {
2929
base := []float64{
3030
0,
31+
1,
32+
-9223372036854775808,
33+
-9223372036854775808 + 4096,
34+
18446744073709551615,
35+
18446744073709551615 + 1,
36+
18446744073709551615 - 1,
37+
9223372036854775808 + 4096,
38+
0.5,
39+
0.75,
3140
math.Copysign(0, -1),
3241
-1,
3342
1,
3443
math.NaN(),
3544
math.Inf(+1),
3645
math.Inf(-1),
3746
0.1,
47+
0.5,
48+
0.75,
3849
1.5,
3950
1.9999999999999998, // all 1s mantissa
4051
1.3333333333333333, // 1.010101010101...
@@ -70,7 +81,7 @@ func TestFloat64(t *testing.T) {
7081
1e+307,
7182
1e+308,
7283
}
73-
all := make([]float64, 200)
84+
all := make([]float64, 250)
7485
copy(all, base)
7586
for i := len(base); i < len(all); i++ {
7687
all[i] = rand.NormFloat64()
@@ -82,6 +93,7 @@ func TestFloat64(t *testing.T) {
8293
test(t, "*", mul, fop(Fmul64), all)
8394
test(t, "/", div, fop(Fdiv64), all)
8495
}
96+
8597
}
8698

8799
// 64 -hw-> 32 -hw-> 64
@@ -104,6 +116,11 @@ func hwint64(f float64) float64 {
104116
return float64(int64(f))
105117
}
106118

119+
// float64 -hw-> uint64 -hw-> float64
120+
func hwuint64(f float64) float64 {
121+
return float64(uint64(f))
122+
}
123+
107124
// float64 -hw-> int32 -hw-> float64
108125
func hwint32(f float64) float64 {
109126
return float64(int32(f))
@@ -113,13 +130,23 @@ func hwint32(f float64) float64 {
113130
func toint64sw(f float64) float64 {
114131
i, ok := F64toint(math.Float64bits(f))
115132
if !ok {
116-
// There's no right answer for out of range.
133+
// There's no right answer for NaN.
117134
// Match the hardware to pass the test.
118135
i = int64(f)
119136
}
120137
return float64(i)
121138
}
122139

140+
func touint64sw(f float64) float64 {
141+
i := F64touint(math.Float64bits(f))
142+
if f != f {
143+
// There's no right answer for NaN.
144+
// Match the hardware to pass the test.
145+
i = uint64(f)
146+
}
147+
return float64(i)
148+
}
149+
123150
// float64 -hw-> int64 -sw-> float64
124151
func fromint64sw(f float64) float64 {
125152
return math.Float64frombits(Fintto64(int64(f)))
@@ -150,6 +177,7 @@ func test(t *testing.T, op string, hw, sw func(float64, float64) float64, all []
150177
testu(t, "to32", trunc32, to32sw, h)
151178
testu(t, "to64", trunc32, to64sw, h)
152179
testu(t, "toint64", hwint64, toint64sw, h)
180+
testu(t, "touint64", hwuint64, touint64sw, h)
153181
testu(t, "fromint64", hwint64, fromint64sw, h)
154182
testcmp(t, f, h)
155183
testcmp(t, h, f)
@@ -163,6 +191,7 @@ func testu(t *testing.T, op string, hw, sw func(float64) float64, v float64) {
163191
h := hw(v)
164192
s := sw(v)
165193
if !same(h, s) {
194+
s = sw(v) // debug me
166195
err(t, "%s %g = sw %g, hw %g\n", op, v, s, h)
167196
}
168197
}

test/convert5.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ func main() {
6262
p64_plus4k_plus1 := id(float64(p64 + 4096 + 1)) // want this to be precise and fit in 53 bits mantissa
6363
n32_minus4k := id(float32(n32 - 4096))
6464
n64_minus4k := id(float64(n64 - 4096))
65+
n32_plus4k := id(float32(n32 + 4096))
66+
n64_plus4k := id(float64(n64 + 4096))
6567
inf_32 := id(float32(one / 0))
6668
inf_64 := id(float64(one / 0))
6769
ninf_32 := id(float32(-one / 0))
@@ -79,6 +81,7 @@ func main() {
7981
{"p64_plus4k_plus1", p64_plus4k_plus1, p32},
8082
{"n32_minus4k", n32_minus4k, n32},
8183
{"n64_minus4k", n64_minus4k, n32},
84+
{"n32_plus4k", n32_plus4k, n32 + 4096},
8285
{"inf_32", inf_32, p32},
8386
{"inf_64", inf_64, p32},
8487
{"ninf_32", ninf_32, n32},
@@ -108,6 +111,8 @@ func main() {
108111
{"p64_plus4k_plus1", p64_plus4k_plus1, p64},
109112
{"n32_minus4k", n32_minus4k, n32 - 4096},
110113
{"n64_minus4k", n64_minus4k, n64},
114+
{"n32_plus4k", n32_plus4k, n32 + 4096},
115+
{"n64_plus4k", n64_plus4k, n64 + 4096},
111116
{"inf_32", inf_32, p64},
112117
{"inf_64", inf_64, p64},
113118
{"ninf_32", ninf_32, n64},

0 commit comments

Comments
 (0)