mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
The fixed-precision ftoa algorithm is not actually
documented in the Ryū paper, and it is fairly
straightforward: multiply by a power of 10 to get
an integer that contains the digits we need.
There is also no need for separate float32 and float64
implementations.
This CL implements a new fixedFtoa, separate from Ryū.
The overall algorithm is the same, but the new code
is simpler, faster, and better documented.
Now ftoaryu.go is only about shortest-output formatting,
so if and when yet another algorithm comes along, it will
be clearer what should be replaced (all of ftoaryu.go)
and what should not (all of ftoafixed.go).
benchmark \ host linux-arm64 local linux-amd64 s7 linux-386 s7:GOARCH=386
vs base vs base vs base vs base vs base vs base
AppendFloat/Decimal -0.18% ~ ~ -0.68% +0.49% -0.79%
AppendFloat/Float +0.09% ~ +1.50% +0.84% -0.37% -0.69%
AppendFloat/Exp -0.51% ~ ~ +1.20% -1.27% -1.01%
AppendFloat/NegExp -1.01% ~ +3.43% +1.35% -2.33% ~
AppendFloat/LongExp -1.22% +0.77% ~ ~ -1.48% ~
AppendFloat/Big -2.07% ~ -2.07% -1.97% -2.89% -2.93%
AppendFloat/BinaryExp -0.28% +1.06% ~ +1.35% -0.64% -1.64%
AppendFloat/32Integer ~ ~ ~ -0.79% ~ -0.66%
AppendFloat/32ExactFraction -0.50% ~ +5.69% ~ -1.24% +0.69%
AppendFloat/32Point ~ -1.19% +2.59% +1.03% -1.37% +0.80%
AppendFloat/32Exp -3.39% -2.79% -8.36% -0.94% -5.72% -5.92%
AppendFloat/32NegExp -0.63% ~ ~ +0.98% -1.34% -0.73%
AppendFloat/32Shortest -1.00% +1.36% +2.94% ~ ~ ~
AppendFloat/32Fixed8Hard -5.91% -12.45% -6.62% ~ +18.46% +11.61%
AppendFloat/32Fixed9Hard -6.53% -11.35% -6.01% -0.97% -18.31% -9.16%
AppendFloat/64Fixed1 -13.84% -16.90% -13.13% -10.71% -24.52% -18.94%
AppendFloat/64Fixed2 -11.12% -16.97% -12.13% -9.88% -22.73% -15.48%
AppendFloat/64Fixed2.5 -21.98% -20.75% -19.08% -14.74% -28.11% -24.92%
AppendFloat/64Fixed3 -11.53% -16.21% -10.75% -7.53% -23.11% -15.78%
AppendFloat/64Fixed4 -12.89% -12.36% -11.07% -9.79% -14.51% -13.44%
AppendFloat/64Fixed5Hard -47.62% -38.59% -40.83% -37.06% -60.51% -55.29%
AppendFloat/64Fixed12 -7.40% ~ -8.56% -4.31% -13.82% -8.61%
AppendFloat/64Fixed16 -9.10% -8.95% -6.92% -3.92% -12.99% -9.03%
AppendFloat/64Fixed12Hard -9.14% -5.24% -6.23% -4.82% -13.58% -8.99%
AppendFloat/64Fixed17Hard -6.80% ~ -4.03% -2.84% -19.81% -10.27%
AppendFloat/64Fixed18Hard -0.12% ~ ~ ~ ~ ~
AppendFloat/64FixedF1 ~ ~ ~ ~ -0.40% +2.72%
AppendFloat/64FixedF2 -0.18% ~ -1.98% -0.95% ~ +1.25%
AppendFloat/64FixedF3 -0.29% ~ ~ ~ ~ +1.22%
AppendFloat/Slowpath64 -1.16% ~ ~ ~ ~ -2.16%
AppendFloat/SlowpathDenormal64 -1.09% ~ ~ -0.88% -0.83% ~
host: linux-arm64
goos: linux
goarch: arm64
pkg: internal/strconv
cpu: unknown
│ 14b7e09f493 │ f9bf7fcb8e2 │
│ sec/op │ sec/op vs base │
AppendFloat/Decimal-8 60.35n ± 0% 60.24n ± 0% -0.18% (p=0.000 n=20)
AppendFloat/Float-8 88.83n ± 0% 88.91n ± 0% +0.09% (p=0.000 n=20)
AppendFloat/Exp-8 93.55n ± 0% 93.06n ± 0% -0.51% (p=0.000 n=20)
AppendFloat/NegExp-8 94.01n ± 0% 93.06n ± 0% -1.01% (p=0.000 n=20)
AppendFloat/LongExp-8 101.00n ± 0% 99.77n ± 0% -1.22% (p=0.000 n=20)
AppendFloat/Big-8 106.1n ± 0% 103.9n ± 0% -2.07% (p=0.000 n=20)
AppendFloat/BinaryExp-8 47.48n ± 0% 47.35n ± 0% -0.28% (p=0.000 n=20)
AppendFloat/32Integer-8 60.45n ± 0% 60.43n ± 0% ~ (p=0.150 n=20)
AppendFloat/32ExactFraction-8 86.65n ± 0% 86.22n ± 0% -0.50% (p=0.000 n=20)
AppendFloat/32Point-8 83.26n ± 0% 83.21n ± 0% ~ (p=0.046 n=20)
AppendFloat/32Exp-8 92.55n ± 0% 89.42n ± 0% -3.39% (p=0.000 n=20)
AppendFloat/32NegExp-8 87.89n ± 0% 87.34n ± 0% -0.63% (p=0.000 n=20)
AppendFloat/32Shortest-8 77.05n ± 0% 76.28n ± 0% -1.00% (p=0.000 n=20)
AppendFloat/32Fixed8Hard-8 55.73n ± 0% 52.44n ± 0% -5.91% (p=0.000 n=20)
AppendFloat/32Fixed9Hard-8 64.80n ± 0% 60.57n ± 0% -6.53% (p=0.000 n=20)
AppendFloat/64Fixed1-8 53.72n ± 0% 46.29n ± 0% -13.84% (p=0.000 n=20)
AppendFloat/64Fixed2-8 52.64n ± 0% 46.79n ± 0% -11.12% (p=0.000 n=20)
AppendFloat/64Fixed2.5-8 56.01n ± 0% 43.70n ± 0% -21.98% (p=0.000 n=20)
AppendFloat/64Fixed3-8 53.38n ± 0% 47.23n ± 0% -11.53% (p=0.000 n=20)
AppendFloat/64Fixed4-8 50.62n ± 0% 44.10n ± 0% -12.89% (p=0.000 n=20)
AppendFloat/64Fixed5Hard-8 98.94n ± 0% 51.82n ± 0% -47.62% (p=0.000 n=20)
AppendFloat/64Fixed12-8 84.70n ± 0% 78.44n ± 0% -7.40% (p=0.000 n=20)
AppendFloat/64Fixed16-8 71.68n ± 0% 65.16n ± 0% -9.10% (p=0.000 n=20)
AppendFloat/64Fixed12Hard-8 68.41n ± 0% 62.16n ± 0% -9.14% (p=0.000 n=20)
AppendFloat/64Fixed17Hard-8 79.31n ± 0% 73.92n ± 0% -6.80% (p=0.000 n=20)
AppendFloat/64Fixed18Hard-8 4.290µ ± 0% 4.285µ ± 0% -0.12% (p=0.000 n=20)
AppendFloat/64FixedF1-8 216.0n ± 0% 216.1n ± 0% ~ (p=0.090 n=20)
AppendFloat/64FixedF2-8 228.2n ± 0% 227.8n ± 0% -0.18% (p=0.000 n=20)
AppendFloat/64FixedF3-8 208.8n ± 0% 208.2n ± 0% -0.29% (p=0.000 n=20)
AppendFloat/Slowpath64-8 98.56n ± 0% 97.42n ± 0% -1.16% (p=0.000 n=20)
AppendFloat/SlowpathDenormal64-8 95.81n ± 0% 94.77n ± 0% -1.09% (p=0.000 n=20)
geomean 93.81n 87.87n -6.33%
host: local
goos: darwin
cpu: Apple M3 Pro
│ 14b7e09f493 │ f9bf7fcb8e2 │
│ sec/op │ sec/op vs base │
AppendFloat/Decimal-12 21.14n ± 0% 21.15n ± 0% ~ (p=0.963 n=20)
AppendFloat/Float-12 32.48n ± 1% 32.43n ± 0% ~ (p=0.358 n=20)
AppendFloat/Exp-12 31.85n ± 0% 31.94n ± 1% ~ (p=0.634 n=20)
AppendFloat/NegExp-12 31.75n ± 0% 32.04n ± 0% ~ (p=0.004 n=20)
AppendFloat/LongExp-12 33.55n ± 0% 33.81n ± 0% +0.77% (p=0.000 n=20)
AppendFloat/Big-12 35.62n ± 1% 35.73n ± 1% ~ (p=0.888 n=20)
AppendFloat/BinaryExp-12 19.26n ± 0% 19.46n ± 1% +1.06% (p=0.000 n=20)
AppendFloat/32Integer-12 21.41n ± 0% 21.46n ± 1% ~ (p=0.733 n=20)
AppendFloat/32ExactFraction-12 31.23n ± 1% 31.30n ± 1% ~ (p=0.857 n=20)
AppendFloat/32Point-12 31.39n ± 1% 31.02n ± 0% -1.19% (p=0.000 n=20)
AppendFloat/32Exp-12 32.42n ± 1% 31.52n ± 1% -2.79% (p=0.000 n=20)
AppendFloat/32NegExp-12 30.66n ± 1% 30.66n ± 1% ~ (p=0.380 n=20)
AppendFloat/32Shortest-12 26.88n ± 1% 27.25n ± 1% +1.36% (p=0.000 n=20)
AppendFloat/32Fixed8Hard-12 19.52n ± 0% 17.09n ± 1% -12.45% (p=0.000 n=20)
AppendFloat/32Fixed9Hard-12 21.55n ± 2% 19.11n ± 1% -11.35% (p=0.000 n=20)
AppendFloat/64Fixed1-12 18.64n ± 0% 15.49n ± 0% -16.90% (p=0.000 n=20)
AppendFloat/64Fixed2-12 18.65n ± 0% 15.49n ± 0% -16.97% (p=0.000 n=20)
AppendFloat/64Fixed2.5-12 19.23n ± 1% 15.24n ± 0% -20.75% (p=0.000 n=20)
AppendFloat/64Fixed3-12 18.61n ± 0% 15.59n ± 1% -16.21% (p=0.000 n=20)
AppendFloat/64Fixed4-12 17.55n ± 1% 15.38n ± 0% -12.36% (p=0.000 n=20)
AppendFloat/64Fixed5Hard-12 29.27n ± 1% 17.97n ± 0% -38.59% (p=0.000 n=20)
AppendFloat/64Fixed12-12 28.26n ± 1% 28.17n ± 10% ~ (p=0.941 n=20)
AppendFloat/64Fixed16-12 23.56n ± 0% 21.46n ± 0% -8.95% (p=0.000 n=20)
AppendFloat/64Fixed12Hard-12 21.85n ± 2% 20.70n ± 1% -5.24% (p=0.000 n=20)
AppendFloat/64Fixed17Hard-12 26.91n ± 1% 27.10n ± 0% ~ (p=0.059 n=20)
AppendFloat/64Fixed18Hard-12 2.197µ ± 1% 2.169µ ± 1% ~ (p=0.013 n=20)
AppendFloat/64FixedF1-12 103.7n ± 1% 103.3n ± 0% ~ (p=0.035 n=20)
AppendFloat/64FixedF2-12 114.8n ± 1% 114.1n ± 1% ~ (p=0.234 n=20)
AppendFloat/64FixedF3-12 107.8n ± 1% 107.1n ± 1% ~ (p=0.180 n=20)
AppendFloat/Slowpath64-12 32.05n ± 1% 32.00n ± 0% ~ (p=0.952 n=20)
AppendFloat/SlowpathDenormal64-12 29.98n ± 1% 30.20n ± 0% ~ (p=0.004 n=20)
geomean 33.83n 31.91n -5.68%
host: linux-amd64
goos: linux
goarch: amd64
cpu: Intel(R) Xeon(R) CPU @ 2.30GHz
│ 14b7e09f493 │ f9bf7fcb8e2 │
│ sec/op │ sec/op vs base │
AppendFloat/Decimal-16 64.00n ± 1% 63.67n ± 1% ~ (p=0.784 n=20)
AppendFloat/Float-16 95.99n ± 1% 97.42n ± 1% +1.50% (p=0.000 n=20)
AppendFloat/Exp-16 97.59n ± 1% 97.72n ± 1% ~ (p=0.984 n=20)
AppendFloat/NegExp-16 97.80n ± 1% 101.15n ± 1% +3.43% (p=0.000 n=20)
AppendFloat/LongExp-16 103.1n ± 1% 104.5n ± 1% ~ (p=0.006 n=20)
AppendFloat/Big-16 110.8n ± 1% 108.5n ± 1% -2.07% (p=0.000 n=20)
AppendFloat/BinaryExp-16 47.82n ± 1% 47.33n ± 1% ~ (p=0.007 n=20)
AppendFloat/32Integer-16 63.65n ± 1% 63.51n ± 0% ~ (p=0.560 n=20)
AppendFloat/32ExactFraction-16 91.81n ± 1% 97.03n ± 1% +5.69% (p=0.000 n=20)
AppendFloat/32Point-16 89.84n ± 1% 92.16n ± 1% +2.59% (p=0.000 n=20)
AppendFloat/32Exp-16 103.80n ± 1% 95.12n ± 1% -8.36% (p=0.000 n=20)
AppendFloat/32NegExp-16 93.70n ± 1% 94.87n ± 1% ~ (p=0.003 n=20)
AppendFloat/32Shortest-16 83.98n ± 1% 86.45n ± 1% +2.94% (p=0.000 n=20)
AppendFloat/32Fixed8Hard-16 61.91n ± 1% 57.81n ± 1% -6.62% (p=0.000 n=20)
AppendFloat/32Fixed9Hard-16 71.08n ± 0% 66.81n ± 1% -6.01% (p=0.000 n=20)
AppendFloat/64Fixed1-16 59.27n ± 2% 51.49n ± 1% -13.13% (p=0.000 n=20)
AppendFloat/64Fixed2-16 57.89n ± 1% 50.87n ± 1% -12.13% (p=0.000 n=20)
AppendFloat/64Fixed2.5-16 61.04n ± 1% 49.40n ± 1% -19.08% (p=0.000 n=20)
AppendFloat/64Fixed3-16 58.42n ± 1% 52.14n ± 1% -10.75% (p=0.000 n=20)
AppendFloat/64Fixed4-16 56.52n ± 1% 50.27n ± 1% -11.07% (p=0.000 n=20)
AppendFloat/64Fixed5Hard-16 97.79n ± 1% 57.86n ± 1% -40.83% (p=0.000 n=20)
AppendFloat/64Fixed12-16 90.78n ± 1% 83.01n ± 1% -8.56% (p=0.000 n=20)
AppendFloat/64Fixed16-16 76.11n ± 1% 70.84n ± 0% -6.92% (p=0.000 n=20)
AppendFloat/64Fixed12Hard-16 73.56n ± 1% 68.98n ± 2% -6.23% (p=0.000 n=20)
AppendFloat/64Fixed17Hard-16 83.20n ± 1% 79.85n ± 1% -4.03% (p=0.000 n=20)
AppendFloat/64Fixed18Hard-16 4.947µ ± 1% 4.915µ ± 1% ~ (p=0.229 n=20)
AppendFloat/64FixedF1-16 242.4n ± 1% 239.4n ± 1% ~ (p=0.038 n=20)
AppendFloat/64FixedF2-16 257.7n ± 2% 252.6n ± 1% -1.98% (p=0.000 n=20)
AppendFloat/64FixedF3-16 237.5n ± 0% 237.5n ± 1% ~ (p=0.440 n=20)
AppendFloat/Slowpath64-16 99.75n ± 1% 99.78n ± 1% ~ (p=0.995 n=20)
AppendFloat/SlowpathDenormal64-16 97.41n ± 1% 98.20n ± 1% ~ (p=0.006 n=20)
geomean 100.7n 95.60n -5.05%
host: s7
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ 14b7e09f493 │ f9bf7fcb8e2 │
│ sec/op │ sec/op vs base │
AppendFloat/Decimal-32 22.19n ± 0% 22.04n ± 0% -0.68% (p=0.000 n=20)
AppendFloat/Float-32 34.59n ± 0% 34.88n ± 0% +0.84% (p=0.000 n=20)
AppendFloat/Exp-32 34.47n ± 0% 34.88n ± 0% +1.20% (p=0.000 n=20)
AppendFloat/NegExp-32 34.85n ± 0% 35.32n ± 0% +1.35% (p=0.000 n=20)
AppendFloat/LongExp-32 37.23n ± 0% 37.09n ± 0% ~ (p=0.003 n=20)
AppendFloat/Big-32 39.27n ± 0% 38.50n ± 0% -1.97% (p=0.000 n=20)
AppendFloat/BinaryExp-32 17.38n ± 0% 17.61n ± 0% +1.35% (p=0.000 n=20)
AppendFloat/32Integer-32 22.26n ± 0% 22.08n ± 0% -0.79% (p=0.000 n=20)
AppendFloat/32ExactFraction-32 32.82n ± 0% 32.91n ± 0% ~ (p=0.018 n=20)
AppendFloat/32Point-32 32.88n ± 0% 33.22n ± 0% +1.03% (p=0.000 n=20)
AppendFloat/32Exp-32 34.95n ± 0% 34.62n ± 0% -0.94% (p=0.000 n=20)
AppendFloat/32NegExp-32 33.23n ± 0% 33.55n ± 0% +0.98% (p=0.000 n=20)
AppendFloat/32Shortest-32 30.19n ± 0% 30.12n ± 0% ~ (p=0.122 n=20)
AppendFloat/32Fixed8Hard-32 22.94n ± 0% 22.88n ± 0% ~ (p=0.124 n=20)
AppendFloat/32Fixed9Hard-32 26.20n ± 0% 25.94n ± 1% -0.97% (p=0.000 n=20)
AppendFloat/64Fixed1-32 21.10n ± 0% 18.84n ± 0% -10.71% (p=0.000 n=20)
AppendFloat/64Fixed2-32 20.75n ± 0% 18.70n ± 0% -9.88% (p=0.000 n=20)
AppendFloat/64Fixed2.5-32 21.07n ± 0% 17.96n ± 0% -14.74% (p=0.000 n=20)
AppendFloat/64Fixed3-32 21.24n ± 0% 19.64n ± 0% -7.53% (p=0.000 n=20)
AppendFloat/64Fixed4-32 20.63n ± 0% 18.61n ± 0% -9.79% (p=0.000 n=20)
AppendFloat/64Fixed5Hard-32 34.48n ± 0% 21.70n ± 0% -37.06% (p=0.000 n=20)
AppendFloat/64Fixed12-32 32.26n ± 0% 30.87n ± 1% -4.31% (p=0.000 n=20)
AppendFloat/64Fixed16-32 27.95n ± 0% 26.86n ± 0% -3.92% (p=0.000 n=20)
AppendFloat/64Fixed12Hard-32 27.30n ± 0% 25.98n ± 1% -4.82% (p=0.000 n=20)
AppendFloat/64Fixed17Hard-32 30.80n ± 0% 29.93n ± 0% -2.84% (p=0.000 n=20)
AppendFloat/64Fixed18Hard-32 1.833µ ± 0% 1.831µ ± 0% ~ (p=0.663 n=20)
AppendFloat/64FixedF1-32 83.42n ± 1% 84.00n ± 1% ~ (p=0.003 n=20)
AppendFloat/64FixedF2-32 90.10n ± 0% 89.23n ± 1% -0.95% (p=0.001 n=20)
AppendFloat/64FixedF3-32 84.42n ± 1% 84.39n ± 0% ~ (p=0.878 n=20)
AppendFloat/Slowpath64-32 35.72n ± 0% 35.59n ± 0% ~ (p=0.007 n=20)
AppendFloat/SlowpathDenormal64-32 35.36n ± 0% 35.05n ± 0% -0.88% (p=0.000 n=20)
geomean 36.05n 34.69n -3.77%
host: linux-386
goarch: 386
cpu: Intel(R) Xeon(R) CPU @ 2.30GHz
│ 14b7e09f493 │ f9bf7fcb8e2 │
│ sec/op │ sec/op vs base │
AppendFloat/Decimal-16 132.8n ± 0% 133.5n ± 0% +0.49% (p=0.001 n=20)
AppendFloat/Float-16 242.6n ± 0% 241.7n ± 0% -0.37% (p=0.000 n=20)
AppendFloat/Exp-16 252.2n ± 0% 249.1n ± 0% -1.27% (p=0.000 n=20)
AppendFloat/NegExp-16 253.6n ± 0% 247.7n ± 0% -2.33% (p=0.000 n=20)
AppendFloat/LongExp-16 260.9n ± 0% 257.1n ± 0% -1.48% (p=0.000 n=20)
AppendFloat/Big-16 293.7n ± 0% 285.2n ± 0% -2.89% (p=0.000 n=20)
AppendFloat/BinaryExp-16 89.63n ± 1% 89.06n ± 0% -0.64% (p=0.000 n=20)
AppendFloat/32Integer-16 132.6n ± 0% 133.2n ± 0% ~ (p=0.016 n=20)
AppendFloat/32ExactFraction-16 216.9n ± 0% 214.2n ± 0% -1.24% (p=0.000 n=20)
AppendFloat/32Point-16 205.0n ± 0% 202.2n ± 0% -1.37% (p=0.000 n=20)
AppendFloat/32Exp-16 250.2n ± 0% 235.9n ± 0% -5.72% (p=0.000 n=20)
AppendFloat/32NegExp-16 213.5n ± 0% 210.6n ± 0% -1.34% (p=0.000 n=20)
AppendFloat/32Shortest-16 198.3n ± 0% 197.8n ± 0% ~ (p=0.147 n=20)
AppendFloat/32Fixed8Hard-16 114.9n ± 1% 136.0n ± 1% +18.46% (p=0.000 n=20)
AppendFloat/32Fixed9Hard-16 189.8n ± 0% 155.0n ± 1% -18.31% (p=0.000 n=20)
AppendFloat/64Fixed1-16 175.8n ± 0% 132.7n ± 0% -24.52% (p=0.000 n=20)
AppendFloat/64Fixed2-16 166.6n ± 0% 128.7n ± 0% -22.73% (p=0.000 n=20)
AppendFloat/64Fixed2.5-16 176.5n ± 0% 126.8n ± 0% -28.11% (p=0.000 n=20)
AppendFloat/64Fixed3-16 165.3n ± 0% 127.1n ± 0% -23.11% (p=0.000 n=20)
AppendFloat/64Fixed4-16 141.3n ± 0% 120.8n ± 1% -14.51% (p=0.000 n=20)
AppendFloat/64Fixed5Hard-16 344.6n ± 0% 136.0n ± 0% -60.51% (p=0.000 n=20)
AppendFloat/64Fixed12-16 184.2n ± 0% 158.7n ± 0% -13.82% (p=0.000 n=20)
AppendFloat/64Fixed16-16 174.0n ± 0% 151.3n ± 0% -12.99% (p=0.000 n=20)
AppendFloat/64Fixed12Hard-16 169.7n ± 0% 146.7n ± 0% -13.58% (p=0.000 n=20)
AppendFloat/64Fixed17Hard-16 207.7n ± 0% 166.6n ± 0% -19.81% (p=0.000 n=20)
AppendFloat/64Fixed18Hard-16 10.66µ ± 0% 10.63µ ± 0% ~ (p=0.030 n=20)
AppendFloat/64FixedF1-16 615.9n ± 0% 613.5n ± 0% -0.40% (p=0.000 n=20)
AppendFloat/64FixedF2-16 846.6n ± 0% 847.4n ± 0% ~ (p=0.551 n=20)
AppendFloat/64FixedF3-16 609.9n ± 0% 609.5n ± 0% ~ (p=0.213 n=20)
AppendFloat/Slowpath64-16 254.1n ± 0% 252.6n ± 1% ~ (p=0.048 n=20)
AppendFloat/SlowpathDenormal64-16 251.5n ± 0% 249.4n ± 0% -0.83% (p=0.000 n=20)
geomean 249.2n 225.4n -9.54%
host: s7:GOARCH=386
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ 14b7e09f493 │ f9bf7fcb8e2 │
│ sec/op │ sec/op vs base │
AppendFloat/Decimal-32 42.65n ± 0% 42.31n ± 0% -0.79% (p=0.000 n=20)
AppendFloat/Float-32 71.56n ± 0% 71.06n ± 0% -0.69% (p=0.000 n=20)
AppendFloat/Exp-32 75.61n ± 1% 74.85n ± 1% -1.01% (p=0.000 n=20)
AppendFloat/NegExp-32 74.36n ± 0% 74.30n ± 0% ~ (p=0.482 n=20)
AppendFloat/LongExp-32 75.82n ± 0% 75.73n ± 0% ~ (p=0.490 n=20)
AppendFloat/Big-32 85.10n ± 0% 82.61n ± 0% -2.93% (p=0.000 n=20)
AppendFloat/BinaryExp-32 33.02n ± 0% 32.48n ± 1% -1.64% (p=0.000 n=20)
AppendFloat/32Integer-32 41.54n ± 1% 41.27n ± 1% -0.66% (p=0.000 n=20)
AppendFloat/32ExactFraction-32 62.48n ± 0% 62.91n ± 0% +0.69% (p=0.000 n=20)
AppendFloat/32Point-32 60.17n ± 0% 60.65n ± 0% +0.80% (p=0.000 n=20)
AppendFloat/32Exp-32 73.34n ± 0% 68.99n ± 0% -5.92% (p=0.000 n=20)
AppendFloat/32NegExp-32 63.29n ± 0% 62.83n ± 0% -0.73% (p=0.000 n=20)
AppendFloat/32Shortest-32 58.97n ± 0% 59.07n ± 0% ~ (p=0.029 n=20)
AppendFloat/32Fixed8Hard-32 37.42n ± 0% 41.76n ± 1% +11.61% (p=0.000 n=20)
AppendFloat/32Fixed9Hard-32 55.18n ± 0% 50.13n ± 1% -9.16% (p=0.000 n=20)
AppendFloat/64Fixed1-32 50.89n ± 1% 41.25n ± 0% -18.94% (p=0.000 n=20)
AppendFloat/64Fixed2-32 48.33n ± 1% 40.85n ± 1% -15.48% (p=0.000 n=20)
AppendFloat/64Fixed2.5-32 52.46n ± 0% 39.39n ± 0% -24.92% (p=0.000 n=20)
AppendFloat/64Fixed3-32 48.28n ± 1% 40.66n ± 0% -15.78% (p=0.000 n=20)
AppendFloat/64Fixed4-32 44.57n ± 0% 38.58n ± 0% -13.44% (p=0.000 n=20)
AppendFloat/64Fixed5Hard-32 96.16n ± 0% 42.99n ± 1% -55.29% (p=0.000 n=20)
AppendFloat/64Fixed12-32 56.84n ± 0% 51.95n ± 1% -8.61% (p=0.000 n=20)
AppendFloat/64Fixed16-32 54.23n ± 0% 49.33n ± 0% -9.03% (p=0.000 n=20)
AppendFloat/64Fixed12Hard-32 53.47n ± 0% 48.67n ± 0% -8.99% (p=0.000 n=20)
AppendFloat/64Fixed17Hard-32 61.76n ± 0% 55.42n ± 1% -10.27% (p=0.000 n=20)
AppendFloat/64Fixed18Hard-32 3.998µ ± 1% 4.001µ ± 0% ~ (p=0.449 n=20)
AppendFloat/64FixedF1-32 161.8n ± 0% 166.2n ± 1% +2.72% (p=0.000 n=20)
AppendFloat/64FixedF2-32 223.4n ± 2% 226.2n ± 1% +1.25% (p=0.000 n=20)
AppendFloat/64FixedF3-32 159.6n ± 0% 161.6n ± 1% +1.22% (p=0.000 n=20)
AppendFloat/Slowpath64-32 76.69n ± 0% 75.03n ± 0% -2.16% (p=0.000 n=20)
AppendFloat/SlowpathDenormal64-32 75.02n ± 0% 74.36n ± 1% ~ (p=0.003 n=20)
geomean 74.66n 69.39n -7.06%
Change-Id: I9db46471a93bd2aab3c2796e563d154cb531d4cb
Reviewed-on: https://go-review.googlesource.com/c/go/+/717182
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Russ Cox <rsc@golang.org>
352 lines
10 KiB
Go
352 lines
10 KiB
Go
// Copyright 2009 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.
|
|
|
|
package strconv_test
|
|
|
|
import (
|
|
. "internal/strconv"
|
|
"math"
|
|
"math/rand"
|
|
"testing"
|
|
)
|
|
|
|
type ftoaTest struct {
|
|
f float64
|
|
fmt byte
|
|
prec int
|
|
s string
|
|
}
|
|
|
|
func fdiv(a, b float64) float64 { return a / b }
|
|
|
|
const (
|
|
below1e23 = 99999999999999974834176
|
|
above1e23 = 100000000000000008388608
|
|
)
|
|
|
|
var ftoatests = []ftoaTest{
|
|
{1, 'e', 5, "1.00000e+00"},
|
|
{1, 'f', 5, "1.00000"},
|
|
{1, 'g', 5, "1"},
|
|
{1, 'g', -1, "1"},
|
|
{1, 'x', -1, "0x1p+00"},
|
|
{1, 'x', 5, "0x1.00000p+00"},
|
|
{20, 'g', -1, "20"},
|
|
{20, 'x', -1, "0x1.4p+04"},
|
|
{1234567.8, 'g', -1, "1.2345678e+06"},
|
|
{1234567.8, 'x', -1, "0x1.2d687cccccccdp+20"},
|
|
{200000, 'g', -1, "200000"},
|
|
{200000, 'x', -1, "0x1.86ap+17"},
|
|
{200000, 'X', -1, "0X1.86AP+17"},
|
|
{2000000, 'g', -1, "2e+06"},
|
|
{1e10, 'g', -1, "1e+10"},
|
|
|
|
// g conversion and zero suppression
|
|
{400, 'g', 2, "4e+02"},
|
|
{40, 'g', 2, "40"},
|
|
{4, 'g', 2, "4"},
|
|
{.4, 'g', 2, "0.4"},
|
|
{.04, 'g', 2, "0.04"},
|
|
{.004, 'g', 2, "0.004"},
|
|
{.0004, 'g', 2, "0.0004"},
|
|
{.00004, 'g', 2, "4e-05"},
|
|
{.000004, 'g', 2, "4e-06"},
|
|
|
|
{0, 'e', 5, "0.00000e+00"},
|
|
{0, 'f', 5, "0.00000"},
|
|
{0, 'g', 5, "0"},
|
|
{0, 'g', -1, "0"},
|
|
{0, 'x', 5, "0x0.00000p+00"},
|
|
|
|
{-1, 'e', 5, "-1.00000e+00"},
|
|
{-1, 'f', 5, "-1.00000"},
|
|
{-1, 'g', 5, "-1"},
|
|
{-1, 'g', -1, "-1"},
|
|
|
|
{12, 'e', 5, "1.20000e+01"},
|
|
{12, 'f', 5, "12.00000"},
|
|
{12, 'g', 5, "12"},
|
|
{12, 'g', -1, "12"},
|
|
|
|
{123456700, 'e', 5, "1.23457e+08"},
|
|
{123456700, 'f', 5, "123456700.00000"},
|
|
{123456700, 'g', 5, "1.2346e+08"},
|
|
{123456700, 'g', -1, "1.234567e+08"},
|
|
|
|
{1.2345e6, 'e', 5, "1.23450e+06"},
|
|
{1.2345e6, 'f', 5, "1234500.00000"},
|
|
{1.2345e6, 'g', 5, "1.2345e+06"},
|
|
|
|
// Round to even
|
|
{1.2345e6, 'e', 3, "1.234e+06"},
|
|
{1.2355e6, 'e', 3, "1.236e+06"},
|
|
{1.2345, 'f', 3, "1.234"},
|
|
{1.2355, 'f', 3, "1.236"},
|
|
{1234567890123456.5, 'e', 15, "1.234567890123456e+15"},
|
|
{1234567890123457.5, 'e', 15, "1.234567890123458e+15"},
|
|
{108678236358137.625, 'g', -1, "1.0867823635813762e+14"},
|
|
|
|
{1e23, 'e', 17, "9.99999999999999916e+22"},
|
|
{1e23, 'f', 17, "99999999999999991611392.00000000000000000"},
|
|
{1e23, 'g', 17, "9.9999999999999992e+22"},
|
|
|
|
{1e23, 'e', -1, "1e+23"},
|
|
{1e23, 'f', -1, "100000000000000000000000"},
|
|
{1e23, 'g', -1, "1e+23"},
|
|
|
|
{below1e23, 'e', 17, "9.99999999999999748e+22"},
|
|
{below1e23, 'f', 17, "99999999999999974834176.00000000000000000"},
|
|
{below1e23, 'g', 17, "9.9999999999999975e+22"},
|
|
|
|
{below1e23, 'e', -1, "9.999999999999997e+22"},
|
|
{below1e23, 'f', -1, "99999999999999970000000"},
|
|
{below1e23, 'g', -1, "9.999999999999997e+22"},
|
|
|
|
{above1e23, 'e', 17, "1.00000000000000008e+23"},
|
|
{above1e23, 'f', 17, "100000000000000008388608.00000000000000000"},
|
|
{above1e23, 'g', 17, "1.0000000000000001e+23"},
|
|
|
|
{above1e23, 'e', -1, "1.0000000000000001e+23"},
|
|
{above1e23, 'f', -1, "100000000000000010000000"},
|
|
{above1e23, 'g', -1, "1.0000000000000001e+23"},
|
|
|
|
{fdiv(5e-304, 1e20), 'g', -1, "5e-324"}, // avoid constant arithmetic
|
|
{fdiv(-5e-304, 1e20), 'g', -1, "-5e-324"}, // avoid constant arithmetic
|
|
|
|
{32, 'g', -1, "32"},
|
|
{32, 'g', 0, "3e+01"},
|
|
|
|
{100, 'x', -1, "0x1.9p+06"},
|
|
{100, 'y', -1, "%y"},
|
|
|
|
{math.NaN(), 'g', -1, "NaN"},
|
|
{-math.NaN(), 'g', -1, "NaN"},
|
|
{math.Inf(0), 'g', -1, "+Inf"},
|
|
{math.Inf(-1), 'g', -1, "-Inf"},
|
|
{-math.Inf(0), 'g', -1, "-Inf"},
|
|
|
|
{-1, 'b', -1, "-4503599627370496p-52"},
|
|
|
|
// fixed bugs
|
|
{0.9, 'f', 1, "0.9"},
|
|
{0.09, 'f', 1, "0.1"},
|
|
{0.0999, 'f', 1, "0.1"},
|
|
{0.05, 'f', 1, "0.1"},
|
|
{0.05, 'f', 0, "0"},
|
|
{0.5, 'f', 1, "0.5"},
|
|
{0.5, 'f', 0, "0"},
|
|
{1.5, 'f', 0, "2"},
|
|
|
|
// https://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/
|
|
{2.2250738585072012e-308, 'g', -1, "2.2250738585072014e-308"},
|
|
// https://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/
|
|
{2.2250738585072011e-308, 'g', -1, "2.225073858507201e-308"},
|
|
|
|
// Issue 2625.
|
|
{383260575764816448, 'f', 0, "383260575764816448"},
|
|
{383260575764816448, 'g', -1, "3.8326057576481645e+17"},
|
|
|
|
// Issue 29491.
|
|
{498484681984085570, 'f', -1, "498484681984085570"},
|
|
{-5.8339553793802237e+23, 'g', -1, "-5.8339553793802237e+23"},
|
|
|
|
// Issue 52187
|
|
{123.45, '?', 0, "%?"},
|
|
{123.45, '?', 1, "%?"},
|
|
{123.45, '?', -1, "%?"},
|
|
|
|
// rounding
|
|
{2.275555555555555, 'x', -1, "0x1.23456789abcdep+01"},
|
|
{2.275555555555555, 'x', 0, "0x1p+01"},
|
|
{2.275555555555555, 'x', 2, "0x1.23p+01"},
|
|
{2.275555555555555, 'x', 16, "0x1.23456789abcde000p+01"},
|
|
{2.275555555555555, 'x', 21, "0x1.23456789abcde00000000p+01"},
|
|
{2.2755555510520935, 'x', -1, "0x1.2345678p+01"},
|
|
{2.2755555510520935, 'x', 6, "0x1.234568p+01"},
|
|
{2.275555431842804, 'x', -1, "0x1.2345668p+01"},
|
|
{2.275555431842804, 'x', 6, "0x1.234566p+01"},
|
|
{3.999969482421875, 'x', -1, "0x1.ffffp+01"},
|
|
{3.999969482421875, 'x', 4, "0x1.ffffp+01"},
|
|
{3.999969482421875, 'x', 3, "0x1.000p+02"},
|
|
{3.999969482421875, 'x', 2, "0x1.00p+02"},
|
|
{3.999969482421875, 'x', 1, "0x1.0p+02"},
|
|
{3.999969482421875, 'x', 0, "0x1p+02"},
|
|
|
|
// Cases that Java once mishandled, from David Chase.
|
|
{1.801439850948199e+16, 'g', -1, "1.801439850948199e+16"},
|
|
{5.960464477539063e-08, 'g', -1, "5.960464477539063e-08"},
|
|
{1.012e-320, 'g', -1, "1.012e-320"},
|
|
|
|
// Cases from TestFtoaRandom that caught bugs in fixedFtoa.
|
|
{8177880169308380. * (1 << 1), 'e', 14, "1.63557603386168e+16"},
|
|
{8393378656576888. * (1 << 1), 'e', 15, "1.678675731315378e+16"},
|
|
{8738676561280626. * (1 << 4), 'e', 16, "1.3981882498049002e+17"},
|
|
{8291032395191335. / (1 << 30), 'e', 5, "7.72163e+06"},
|
|
|
|
// Exercise divisiblePow5 case in fixedFtoa
|
|
{2384185791015625. * (1 << 12), 'e', 5, "9.76562e+18"},
|
|
{2384185791015625. * (1 << 13), 'e', 5, "1.95312e+19"},
|
|
}
|
|
|
|
func TestFtoa(t *testing.T) {
|
|
for i := 0; i < len(ftoatests); i++ {
|
|
test := &ftoatests[i]
|
|
s := FormatFloat(test.f, test.fmt, test.prec, 64)
|
|
if s != test.s {
|
|
t.Error("testN=64", test.f, string(test.fmt), test.prec, "want", test.s, "got", s)
|
|
}
|
|
x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 64)
|
|
if string(x) != "abc"+test.s {
|
|
t.Error("AppendFloat testN=64", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x))
|
|
}
|
|
if float64(float32(test.f)) == test.f && test.fmt != 'b' {
|
|
test_s := test.s
|
|
if test.f == 5.960464477539063e-08 {
|
|
// This test is an exact float32 but asking for float64 precision in the string.
|
|
// (All our other float64-only tests fail to exactness check above.)
|
|
test_s = "5.9604645e-08"
|
|
continue
|
|
}
|
|
s := FormatFloat(test.f, test.fmt, test.prec, 32)
|
|
if s != test.s {
|
|
t.Error("testN=32", test.f, string(test.fmt), test.prec, "want", test_s, "got", s)
|
|
}
|
|
x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 32)
|
|
if string(x) != "abc"+test_s {
|
|
t.Error("AppendFloat testN=32", test.f, string(test.fmt), test.prec, "want", "abc"+test_s, "got", string(x))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFtoaPowersOfTwo(t *testing.T) {
|
|
for exp := -2048; exp <= 2048; exp++ {
|
|
f := math.Ldexp(1, exp)
|
|
if !math.IsInf(f, 0) {
|
|
s := FormatFloat(f, 'e', -1, 64)
|
|
if x, _ := ParseFloat(s, 64); x != f {
|
|
t.Errorf("failed roundtrip %v => %s => %v", f, s, x)
|
|
}
|
|
}
|
|
f32 := float32(f)
|
|
if !math.IsInf(float64(f32), 0) {
|
|
s := FormatFloat(float64(f32), 'e', -1, 32)
|
|
if x, _ := ParseFloat(s, 32); float32(x) != f32 {
|
|
t.Errorf("failed roundtrip %v => %s => %v", f32, s, float32(x))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFtoaRandom(t *testing.T) {
|
|
N := int(1e4)
|
|
if testing.Short() {
|
|
N = 100
|
|
}
|
|
t.Logf("testing %d random numbers with fast and slow FormatFloat", N)
|
|
for i := 0; i < N; i++ {
|
|
bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32())
|
|
x := math.Float64frombits(bits)
|
|
|
|
shortFast := FormatFloat(x, 'g', -1, 64)
|
|
SetOptimize(false)
|
|
shortSlow := FormatFloat(x, 'g', -1, 64)
|
|
SetOptimize(true)
|
|
if shortSlow != shortFast {
|
|
t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow)
|
|
}
|
|
|
|
prec := rand.Intn(12) + 5
|
|
shortFast = FormatFloat(x, 'e', prec, 64)
|
|
SetOptimize(false)
|
|
shortSlow = FormatFloat(x, 'e', prec, 64)
|
|
SetOptimize(true)
|
|
if shortSlow != shortFast {
|
|
t.Errorf("%b printed with %%.%de as %s, want %s", x, prec, shortFast, shortSlow)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatFloatInvalidBitSize(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Fatalf("expected panic due to invalid bitSize")
|
|
}
|
|
}()
|
|
_ = FormatFloat(3.14, 'g', -1, 100)
|
|
}
|
|
|
|
var ftoaBenches = []struct {
|
|
name string
|
|
float float64
|
|
fmt byte
|
|
prec int
|
|
bitSize int
|
|
}{
|
|
{"Decimal", 33909, 'g', -1, 64},
|
|
{"Float", 339.7784, 'g', -1, 64},
|
|
{"Exp", -5.09e75, 'g', -1, 64},
|
|
{"NegExp", -5.11e-95, 'g', -1, 64},
|
|
{"LongExp", 1.234567890123456e-78, 'g', -1, 64},
|
|
|
|
{"Big", 123456789123456789123456789, 'g', -1, 64},
|
|
{"BinaryExp", -1, 'b', -1, 64},
|
|
|
|
{"32Integer", 33909, 'g', -1, 32},
|
|
{"32ExactFraction", 3.375, 'g', -1, 32},
|
|
{"32Point", 339.7784, 'g', -1, 32},
|
|
{"32Exp", -5.09e25, 'g', -1, 32},
|
|
{"32NegExp", -5.11e-25, 'g', -1, 32},
|
|
{"32Shortest", 1.234567e-8, 'g', -1, 32},
|
|
{"32Fixed8Hard", math.Ldexp(15961084, -125), 'e', 8, 32},
|
|
{"32Fixed9Hard", math.Ldexp(14855922, -83), 'e', 9, 32},
|
|
|
|
{"64Fixed1", 123456, 'e', 3, 64},
|
|
{"64Fixed2", 123.456, 'e', 3, 64},
|
|
{"64Fixed2.5", 1.2345e+06, 'e', 3, 64},
|
|
{"64Fixed3", 1.23456e+78, 'e', 3, 64},
|
|
{"64Fixed4", 1.23456e-78, 'e', 3, 64},
|
|
{"64Fixed5Hard", 4.096e+25, 'e', 5, 64}, // needs divisiblePow5(..., 20)
|
|
{"64Fixed12", 1.23456e-78, 'e', 12, 64},
|
|
{"64Fixed16", 1.23456e-78, 'e', 16, 64},
|
|
// From testdata/testfp.txt
|
|
{"64Fixed12Hard", math.Ldexp(6965949469487146, -249), 'e', 12, 64},
|
|
{"64Fixed17Hard", math.Ldexp(8887055249355788, 665), 'e', 17, 64},
|
|
{"64Fixed18Hard", math.Ldexp(6994187472632449, 690), 'e', 18, 64},
|
|
|
|
{"64FixedF1", 123.456, 'f', 6, 64},
|
|
{"64FixedF2", 0.0123, 'f', 6, 64},
|
|
{"64FixedF3", 12.3456, 'f', 2, 64},
|
|
|
|
// Trigger slow path (see issue #15672).
|
|
// The shortest is: 8.034137530808823e+43
|
|
{"Slowpath64", 8.03413753080882349e+43, 'e', -1, 64},
|
|
// This denormal is pathological because the lower/upper
|
|
// halfways to neighboring floats are:
|
|
// 622666234635.321003e-320 ~= 622666234635.321e-320
|
|
// 622666234635.321497e-320 ~= 622666234635.3215e-320
|
|
// making it hard to find the 3rd digit
|
|
{"SlowpathDenormal64", 622666234635.3213e-320, 'e', -1, 64},
|
|
}
|
|
|
|
func BenchmarkFormatFloat(b *testing.B) {
|
|
for _, c := range ftoaBenches {
|
|
b.Run(c.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
FormatFloat(c.float, c.fmt, c.prec, c.bitSize)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkAppendFloat(b *testing.B) {
|
|
dst := make([]byte, 30)
|
|
for _, c := range ftoaBenches {
|
|
b.Run(c.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
AppendFloat(dst[:0], c.float, c.fmt, c.prec, c.bitSize)
|
|
}
|
|
})
|
|
}
|
|
}
|