Skip to content

Commit 6c9311e

Browse files
Additional benchmarks for Atof including hard denormals.
Change-Id: Id0ca1ac439c74b7643dd5fe556d000aefbd97ef7
1 parent fcfe73c commit 6c9311e

File tree

2 files changed

+65
-45
lines changed

2 files changed

+65
-45
lines changed

src/strconv/atof_test.go

+37-37
Original file line numberDiff line numberDiff line change
@@ -393,27 +393,45 @@ func TestRoundTrip32(t *testing.T) {
393393
t.Logf("tested %d float32's", count)
394394
}
395395

396-
func BenchmarkAtof64Decimal(b *testing.B) {
397-
for i := 0; i < b.N; i++ {
398-
ParseFloat("33909", 64)
399-
}
400-
}
401-
402-
func BenchmarkAtof64Float(b *testing.B) {
403-
for i := 0; i < b.N; i++ {
404-
ParseFloat("339.7784", 64)
405-
}
406-
}
407-
408-
func BenchmarkAtof64FloatExp(b *testing.B) {
409-
for i := 0; i < b.N; i++ {
410-
ParseFloat("-5.09e75", 64)
411-
}
396+
var atofBenches = []struct {
397+
name string
398+
arg string
399+
bitSize int
400+
}{
401+
{"64Decimal", "33909", 64},
402+
{"64Float", "339.7784", 64},
403+
{"64FloatExp", "-5.09e75", 64},
404+
{"64Big", "123456789123456789123456789", 64},
405+
{"64Denormal", "622666234635.3213e-320", 64},
406+
{"32Decimal", "33909", 32},
407+
{"32Float", "339.778", 32},
408+
{"32FloatExp", "12.3456e32", 32},
409+
// Numbers halfway (or close to halfway) between 2 floats
410+
{"64HalfwayInt", "100000000000000016777215", 64},
411+
{"64HalfwayInt", "100000000000000016777216", 64},
412+
// Almost halfway, with less than 1e-16 ulp difference
413+
// with only 16 decimal digits.
414+
{"64HalfwayHard1", "6808957268280643e116", 64}, // from ftoahard
415+
{"64HalfwayHard2", "4.334126125515466e-210", 64}, // from ftoahard
416+
// Only 3e-13*ulp larger than halfway between denormals,
417+
{"64HalfwayDenormal", "1.68514038588815e-309", 64},
418+
// Few digits, but 9.11691642378e-312 = 0x1ada385d67b.7fffffff5d9...p-1074
419+
// so naive, rounded 64-bit arithmetic is not enough to round it correctly.
420+
{"64HalfwayDenormalShort", "9.11691642378e-312", 64},
421+
// 1.62420278e-315 = 0x1398359e.7fffe022p-1074,
422+
// should parsable using 64-bit arithmetic.
423+
{"64HalfwayDenormalVeryShort", "1.62420278e-315", 64},
424+
// https://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/
425+
{"64Denormal", "2.2250738585072011e-308", 64},
412426
}
413427

414-
func BenchmarkAtof64Big(b *testing.B) {
415-
for i := 0; i < b.N; i++ {
416-
ParseFloat("123456789123456789123456789", 64)
428+
func BenchmarkAtof(b *testing.B) {
429+
for _, c := range atofBenches {
430+
b.Run(c.arg, func(b *testing.B) {
431+
for i := 0; i < b.N; i++ {
432+
ParseFloat(c.arg, c.bitSize)
433+
}
434+
})
417435
}
418436
}
419437

@@ -429,24 +447,6 @@ func BenchmarkAtof64RandomFloats(b *testing.B) {
429447
}
430448
}
431449

432-
func BenchmarkAtof32Decimal(b *testing.B) {
433-
for i := 0; i < b.N; i++ {
434-
ParseFloat("33909", 32)
435-
}
436-
}
437-
438-
func BenchmarkAtof32Float(b *testing.B) {
439-
for i := 0; i < b.N; i++ {
440-
ParseFloat("339.778", 32)
441-
}
442-
}
443-
444-
func BenchmarkAtof32FloatExp(b *testing.B) {
445-
for i := 0; i < b.N; i++ {
446-
ParseFloat("12.3456e32", 32)
447-
}
448-
}
449-
450450
var float32strings [4096]string
451451

452452
func BenchmarkAtof32Random(b *testing.B) {

src/strconv/ftoahard_test.go

+28-8
Original file line numberDiff line numberDiff line change
@@ -51,32 +51,48 @@ func GenerateHardFloat64s() []float64 {
5151
a = a.Exp(a, big.NewInt(int64(q)), nil)
5252
b := big.NewInt(1)
5353
b = b.Lsh(b, uint(-(e - 1)))
54-
x, y, prec = findFrac(a, b)
54+
x, y, prec = findFrac(a, b, 54)
5555
} else {
5656
a := big.NewInt(10)
5757
a = a.Exp(a, big.NewInt(int64(-q)), nil)
5858
b := big.NewInt(1)
5959
b = b.Lsh(b, uint(e-1))
60-
x, y, prec = findFrac(b, a)
60+
x, y, prec = findFrac(b, a, 54)
6161
}
6262

6363
if bits.Len64(y) == 54 {
6464
f := math.Ldexp(float64(y>>1), e)
65-
_ = fmt.Sprintf("f=ldexp(%d,%d)=%v, f+=(%d+%.3e)e%d\n",
65+
fmt.Printf("f=ldexp(%d,%d)=%v, f+=(%d+%.3e)e%d\n",
6666
y>>1, e, f, x, prec, -q)
6767
hards = append(hards, f)
6868
}
69+
70+
if e == -1074 {
71+
for bitlen := 30; bitlen < 54; bitlen++ {
72+
// also find hard denormals
73+
a := big.NewInt(10)
74+
a = a.Exp(a, big.NewInt(int64(q)), nil)
75+
b := big.NewInt(1)
76+
b = b.Lsh(b, uint(-(e - 1)))
77+
x, y, prec = findFrac(a, b, bitlen)
78+
79+
f := math.Ldexp(float64(y>>1), e)
80+
fmt.Printf("f=ldexp(%d,%d)=%v, f+=(%d+%.3e)e%d\n",
81+
y>>1, e, f, x, prec, -q)
82+
hards = append(hards, f)
83+
}
84+
}
6985
}
7086
return hards
7187
}
7288

7389
// findFrac returns a fraction x/y very close to u/v,
7490
// such that y*(u/v) = x+prec
75-
func findFrac(u, v *big.Int) (x, y uint64, prec float64) {
91+
func findFrac(u, v *big.Int, bitlen int) (x, y uint64, prec float64) {
7692
q := new(big.Rat).SetFrac(u, v)
7793
for seed := uint64(1); seed < 90; seed += 3 {
78-
x, y = contFrac(u, v, seed)
79-
if bits.Len64(y) == 54 && y%2 == 1 && x%10 == 0 {
94+
x, y = contFrac(u, v, seed, 1<<uint(bitlen-1))
95+
if bits.Len64(y) == bitlen && y%2 == 1 && x%10 == 0 {
8096
break
8197
}
8298
}
@@ -86,10 +102,10 @@ func findFrac(u, v *big.Int) (x, y uint64, prec float64) {
86102
return x, y, prec
87103
}
88104

89-
func contFrac(u, v *big.Int, seed uint64) (x, y uint64) {
105+
func contFrac(u, v *big.Int, seed uint64, max uint64) (x, y uint64) {
90106
var a, b uint64 = 1, 0
91107
var c, d uint64 = 0, seed
92-
for c < 1<<53 {
108+
for c < max {
93109
if v.BitLen() == 0 {
94110
break
95111
}
@@ -107,6 +123,10 @@ func contFrac(u, v *big.Int, seed uint64) (x, y uint64) {
107123
}
108124

109125
var hardFloatSamples = []float64{
126+
// Denormals
127+
math.Ldexp(328742302, -1074),
128+
math.Ldexp(1845284427387, -1074),
129+
math.Ldexp(341076211242912, -1074),
110130
// Difficulty < 1e-15
111131
math.Ldexp(6417092537094053, -748),
112132
math.Ldexp(7675932596762664, -653),

0 commit comments

Comments
 (0)