diff --git a/Makefile b/Makefile index 2d75be1..d4da4d8 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ fuzz-all: @sh scripts/fuzz-all.sh $(fuzzTime) bench: - @go test -bench=BenchmarkMarshalJSON -benchmem -benchmem -memprofile=mem.out -cpuprofile=cpu.out -run NONE + @go test -bench=BenchmarkString -benchmem -benchmem -memprofile=mem.out -cpuprofile=cpu.out -run NONE # https://stackoverflow.com/questions/6273608/how-to-pass-argument-to-makefile-from-command-line %: diff --git a/benchmarks/Makefile b/benchmarks/Makefile index 481c6af..e7d9d2f 100644 --- a/benchmarks/Makefile +++ b/benchmarks/Makefile @@ -1,7 +1,7 @@ bench: # @go test -bench BenchmarkMarshalJSON -benchmem -memprofile mem.out -cpuprofile cpu.out -run NONE - @go test -bench BenchmarkDiv -benchmem -count=10 -run NONE > new.txt + @go test -bench BenchmarkParse -benchmem -count=10 -run NONE > new.txt bench-udec: @rm -f bench-udec.txt diff --git a/benchmarks/README.md b/benchmarks/README.md index 3d3336d..57ba936 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -61,9 +61,9 @@ BenchmarkPow/1.01.Pow(10)-32 BenchmarkPow/1.01.Pow(100)-32 994129 1137 ns/op 817 B/op 13 allocs/op # Parsing string -BenchmarkParse/1234567890123456789.1234567890123456879-32 15700467 78.16 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 54752288 22.86 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 28947921 41.76 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 32111433 38.21 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 98585916 12.58 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 44339668 26.45 ns/op 0 B/op 0 allocs/op BenchmarkParseFallBack/123456789123456789123456.1234567890123456-32 2805122 473.3 ns/op 192 B/op 6 allocs/op BenchmarkParseFallBack/111222333444555666777888999.1234567890123456789-32 2442004 500.8 ns/op 216 B/op 6 allocs/op BenchmarkString/1234567890123456789.1234567890123456879-32 12797790 98.69 ns/op 48 B/op 1 allocs/op diff --git a/benchmarks/bench-ss.txt b/benchmarks/bench-ss.txt index 39156a0..d48f9f6 100644 --- a/benchmarks/bench-ss.txt +++ b/benchmarks/bench-ss.txt @@ -52,6 +52,16 @@ BenchmarkParse/0.1234567890123456879-32 4524166 BenchmarkParse/0.1234567890123456879-32 4930246 243.4 ns/op 144 B/op 5 allocs/op BenchmarkParse/0.1234567890123456879-32 4491036 241.7 ns/op 144 B/op 5 allocs/op BenchmarkParse/0.1234567890123456879-32 4911284 219.5 ns/op 144 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 3649983 313.6 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 4065206 310.8 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 3553578 293.6 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 4134546 326.1 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 3722556 294.2 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 3730910 337.1 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 4160892 308.3 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 3727047 301.0 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 3756850 342.8 ns/op 152 B/op 5 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 3766802 335.8 ns/op 152 B/op 5 allocs/op BenchmarkParseFallBack/123456789123456789123456.1234567890123456-32 3580087 325.3 ns/op 168 B/op 5 allocs/op BenchmarkParseFallBack/123456789123456789123456.1234567890123456-32 3728620 309.6 ns/op 168 B/op 5 allocs/op BenchmarkParseFallBack/123456789123456789123456.1234567890123456-32 3925362 328.2 ns/op 168 B/op 5 allocs/op diff --git a/benchmarks/bench-udec.txt b/benchmarks/bench-udec.txt index ae2960f..a88d3b6 100644 --- a/benchmarks/bench-udec.txt +++ b/benchmarks/bench-udec.txt @@ -2,56 +2,66 @@ goos: linux goarch: amd64 pkg: github.com/quagmt/udecimal/benchmarks cpu: Intel(R) Core(TM) i9-14900HX -BenchmarkParse/1234567890123456789.1234567890123456879-32 14480929 70.79 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 15750946 76.10 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 13101150 84.18 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 14365287 78.62 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 12634765 82.26 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 13364164 82.76 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 15356986 79.13 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 15347952 78.36 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 16158620 76.31 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890123456789.1234567890123456879-32 15837738 86.18 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 132207692 9.652 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 129531774 9.824 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 139214372 9.509 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 128602180 9.571 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 128681630 8.761 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 131462078 8.950 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 140413305 8.689 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 131624270 8.995 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 141288943 9.431 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123-32 128163589 9.337 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 40123141 27.87 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 46144542 26.20 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 46774532 25.98 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 43839273 24.81 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 50633299 24.72 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 53245113 24.46 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 49705689 23.29 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 50949015 24.66 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 52628265 22.96 ns/op 0 B/op 0 allocs/op -BenchmarkParse/123456.123456-32 51431748 23.50 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 50591940 20.63 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 50563016 21.10 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 55656282 20.80 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 58230686 21.70 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 55902580 21.93 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 56100603 21.60 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 53920798 21.77 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 55861123 22.21 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 54120472 21.64 ns/op 0 B/op 0 allocs/op -BenchmarkParse/1234567890-32 60566383 20.49 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 28710724 43.22 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 29146560 44.10 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 26922684 43.90 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 27707344 40.65 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 28752571 40.60 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 28529010 42.58 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 29355486 42.93 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 29871193 40.69 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 29841739 40.72 ns/op 0 B/op 0 allocs/op -BenchmarkParse/0.1234567890123456879-32 29470508 42.17 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 32111433 38.21 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 30370411 38.00 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 30983041 40.51 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 29734609 40.17 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 32103327 38.89 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 31406709 39.94 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 32262856 38.02 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 29613183 37.88 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 31656368 38.01 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890123456789.1234567890123456879-32 29451500 39.32 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 141880339 8.397 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 144087966 8.803 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 141612804 8.804 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 148056843 8.653 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 134661038 8.253 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 146080090 8.784 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 140681473 8.868 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 147862611 8.554 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 149067693 8.194 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123-32 139148565 8.671 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 76273048 15.31 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 83309959 14.66 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 69696046 15.22 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 87739012 14.25 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 84302694 15.17 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 86778757 14.38 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 87325648 14.65 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 82021698 13.88 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 83819546 14.01 ns/op 0 B/op 0 allocs/op +BenchmarkParse/123456.123456-32 85760779 14.93 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 100000000 12.30 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 98585916 12.58 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 100000000 12.40 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 96808634 12.44 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 100000000 12.29 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 91031727 12.68 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 93970161 12.66 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 96205394 12.25 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 99670058 11.67 ns/op 0 B/op 0 allocs/op +BenchmarkParse/1234567890-32 95630294 11.83 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 44339668 26.45 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 45756302 24.08 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 48353757 25.04 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 46825815 27.06 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 49001607 25.61 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 49650522 26.46 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 45742516 26.07 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 47493613 25.55 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 46787036 25.79 ns/op 0 B/op 0 allocs/op +BenchmarkParse/0.1234567890123456879-32 49189808 26.09 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 20359177 62.64 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 20100522 62.59 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 19438142 63.89 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 18644307 59.72 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 19709970 62.42 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 19311558 59.00 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 20234343 59.28 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 20193010 63.77 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 18147177 61.41 ns/op 0 B/op 0 allocs/op +BenchmarkParse/12345678901234567890123456789.123-32 19687285 61.19 ns/op 0 B/op 0 allocs/op BenchmarkParseFallBack/123456789123456789123456.1234567890123456-32 2585487 479.9 ns/op 192 B/op 6 allocs/op BenchmarkParseFallBack/123456789123456789123456.1234567890123456-32 2280304 507.8 ns/op 192 B/op 6 allocs/op BenchmarkParseFallBack/123456789123456789123456.1234567890123456-32 3002433 497.9 ns/op 192 B/op 6 allocs/op diff --git a/benchmarks/benchmarks_test.go b/benchmarks/benchmarks_test.go index d416539..d909c4b 100644 --- a/benchmarks/benchmarks_test.go +++ b/benchmarks/benchmarks_test.go @@ -18,6 +18,7 @@ func BenchmarkParse(b *testing.B) { "123456.123456", "1234567890", "0.1234567890123456879", + "12345678901234567890123456789.123", } for _, tc := range testcases { diff --git a/benchmarks/benchstat.txt b/benchmarks/benchstat.txt index 5a8a75f..7d403c0 100644 --- a/benchmarks/benchstat.txt +++ b/benchmarks/benchstat.txt @@ -4,11 +4,12 @@ pkg: github.com/quagmt/udecimal/benchmarks cpu: Intel(R) Core(TM) i9-14900HX │ shopspring │ udecimal │ │ sec/op │ sec/op vs base │ -Parse/1234567890123456789.1234567890123456879-32 384.35n ± 10% 78.87n ± 7% -79.48% (p=0.000 n=10) -Parse/123-32 77.475n ± 13% 9.384n ± 7% -87.89% (p=0.000 n=10) -Parse/123456.123456-32 128.35n ± 5% 24.69n ± 6% -80.76% (p=0.000 n=10) -Parse/1234567890-32 82.47n ± 7% 21.62n ± 5% -73.79% (p=0.000 n=10) -Parse/0.1234567890123456879-32 262.30n ± 17% 42.38n ± 4% -83.84% (p=0.000 n=10) +Parse/1234567890123456789.1234567890123456879-32 384.35n ± 10% 38.55n ± 4% -89.97% (p=0.000 n=10) +Parse/123-32 77.475n ± 13% 8.662n ± 5% -88.82% (p=0.000 n=10) +Parse/123456.123456-32 128.35n ± 5% 14.66n ± 4% -88.58% (p=0.000 n=10) +Parse/1234567890-32 82.47n ± 7% 12.35n ± 4% -85.03% (p=0.000 n=10) +Parse/0.1234567890123456879-32 262.30n ± 17% 25.93n ± 3% -90.11% (p=0.000 n=10) +Parse/12345678901234567890123456789.123-32 312.20n ± 8% 61.92n ± 4% -80.17% (p=0.000 n=10) ParseFallBack/123456789123456789123456.1234567890123456-32 373.2n ± 13% 477.1n ± 6% +27.83% (p=0.000 n=10) ParseFallBack/111222333444555666777888999.1234567890123456789-32 418.5n ± 5% 440.6n ± 18% ~ (p=0.353 n=10) String/1234567890123456789.1234567890123456879-32 284.45n ± 26% 94.91n ± 12% -66.63% (p=0.000 n=10) @@ -75,7 +76,7 @@ UnmarshalBinary/123456.123456-32 UnmarshalBinary/1234567890-32 65.140n ± 38% 1.790n ± 5% -97.25% (p=0.000 n=10) UnmarshalBinary/0.1234567890123456879-32 45.120n ± 18% 1.854n ± 5% -95.89% (p=0.000 n=10) UnmarshalBinary/12345678901234567891234567890123456789.1234567890123456879-32 72.44n ± 11% 75.91n ± 24% ~ (p=0.684 n=10) -geomean 182.4n 18.72n -89.74% +geomean 183.7n 18.41n -89.98% │ shopspring │ udecimal │ │ B/op │ B/op vs base │ @@ -84,6 +85,7 @@ Parse/123-32 Parse/123456.123456-32 56.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10) Parse/1234567890-32 40.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10) Parse/0.1234567890123456879-32 144.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) +Parse/12345678901234567890123456789.123-32 152.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) ParseFallBack/123456789123456789123456.1234567890123456-32 168.0 ± 0% 192.0 ± 0% +14.29% (p=0.000 n=10) ParseFallBack/111222333444555666777888999.1234567890123456789-32 168.0 ± 0% 216.0 ± 0% +28.57% (p=0.000 n=10) String/1234567890123456789.1234567890123456879-32 240.00 ± 0% 48.00 ± 0% -80.00% (p=0.000 n=10) @@ -150,7 +152,7 @@ UnmarshalBinary/123456.123456-32 UnmarshalBinary/1234567890-32 40.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10) UnmarshalBinary/0.1234567890123456879-32 40.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10) UnmarshalBinary/12345678901234567891234567890123456789.1234567890123456879-32 96.00 ± 0% 96.00 ± 0% ~ (p=1.000 n=10) ¹ -geomean 122.4 ? ² ³ +geomean 122.8 ? ² ³ ¹ all samples are equal ² summaries must be >0 to compute geomean ³ ratios must be >0 to compute geomean @@ -162,6 +164,7 @@ Parse/123-32 Parse/123456.123456-32 3.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) Parse/1234567890-32 2.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) Parse/0.1234567890123456879-32 5.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) +Parse/12345678901234567890123456789.123-32 5.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) ParseFallBack/123456789123456789123456.1234567890123456-32 5.000 ± 0% 6.000 ± 0% +20.00% (p=0.000 n=10) ParseFallBack/111222333444555666777888999.1234567890123456789-32 5.000 ± 0% 6.000 ± 0% +20.00% (p=0.000 n=10) String/1234567890123456789.1234567890123456879-32 5.000 ± 0% 1.000 ± 0% -80.00% (p=0.000 n=10) @@ -228,7 +231,7 @@ UnmarshalBinary/123456.123456-32 UnmarshalBinary/1234567890-32 2.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) UnmarshalBinary/0.1234567890123456879-32 2.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) UnmarshalBinary/12345678901234567891234567890123456789.1234567890123456879-32 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹ -geomean 4.313 ? ² ³ +geomean 4.322 ? ² ³ ¹ all samples are equal ² summaries must be >0 to compute geomean ³ ratios must be >0 to compute geomean diff --git a/bint.go b/bint.go index 7fefcd3..8854a91 100644 --- a/bint.go +++ b/bint.go @@ -210,48 +210,160 @@ func parseBintFromU128(s []byte) (bool, bint, uint8, error) { coef u128 prec uint8 ) - for ; pos < width; pos++ { - if s[pos] == '.' { + + if len(s[pos:]) <= 19 { + coef, prec, err = parseSmallToU128(s[pos:]) + } else { + coef, prec, err = parseLargeToU128(s[pos:]) + } + + if err == ErrInvalidFormat { + return neg, bint{}, 0, errInvalidFormat(s) + } + + return neg, bint{u128: coef}, prec, err +} + +func parseSmallToU128(s []byte) (u128, uint8, error) { + var ( + coef uint64 + prec uint8 + ) + + for i := 0; i < len(s); i++ { + if s[i] == '.' { // return err if we encounter the '.' more than once if prec != 0 { - return false, bint{}, 0, errInvalidFormat(s) + return u128{}, 0, ErrInvalidFormat } // nolint: gosec - prec = uint8(width - pos - 1) + prec = uint8(len(s) - i - 1) // prevent "123." or "-123." if prec == 0 { - return false, bint{}, 0, errInvalidFormat(s) + return u128{}, 0, ErrInvalidFormat } if prec > defaultPrec { - return false, bint{}, 0, ErrPrecOutOfRange + return u128{}, 0, ErrPrecOutOfRange } continue } - if s[pos] < '0' || s[pos] > '9' { - return false, bint{}, 0, errInvalidFormat(s) + if s[i] < '0' || s[i] > '9' { + return u128{}, 0, ErrInvalidFormat } - coef, err = coef.Mul64(10) - if err != nil { - return false, bint{}, 0, err + coef = coef*10 + uint64(s[i]-'0') + } + + if coef == 0 { + return u128{}, 0, nil + } + + return u128{lo: coef}, prec, nil +} + +func parseLargeToU128(s []byte) (u128, uint8, error) { + // find '.' position + l := len(s) + pos := -1 + + for i := 0; i < len(s); i++ { + if s[i] == '.' { + pos = i } + } + + if pos == 0 || pos == l-1 { + return u128{}, 0, ErrInvalidFormat + } + + var prec uint8 + if pos != -1 { + // nolint: gosec + prec = uint8(l - pos - 1) + if prec > defaultPrec { + return u128{}, 0, ErrPrecOutOfRange + } + } - coef, err = coef.Add64(uint64(s[pos] - '0')) + if pos == -1 { + // no decimal point + coef, err := digitToU128(s) if err != nil { - return false, bint{}, 0, err + return u128{}, 0, err + } + + return coef, 0, nil + } + + // number has a decimal point, split into 2 parts: integer and fraction + intPart, err := digitToU128(s[:pos]) + if err != nil { + return u128{}, 0, err + } + + // because max prec is 19, + // factionPart can't be larger than 10%20-1 and will fit into uint64 (fractionPart.hi == 0) + fractionPart, err := digitToU128(s[pos+1:]) + if err != nil { + return u128{}, 0, err + } + + // combine + coef, err := intPart.Mul64(pow10[uint64(prec)].lo) + if err != nil { + return u128{}, 0, err + } + + coef, err = coef.Add64(fractionPart.lo) + if err != nil { + return u128{}, 0, err + } + + return coef, prec, nil +} + +func digitToU128(s []byte) (u128, error) { + if len(s) <= 19 { + var u uint64 + for i := 0; i < len(s); i++ { + if s[i] < '0' || s[i] > '9' { + return u128{}, ErrInvalidFormat + } + + u = u*10 + uint64(s[i]-'0') } + + return u128{lo: u}, nil } - if coef.IsZero() { - return false, bint{}, 0, nil + // number is too large + var ( + u u128 + err error + ) + + for i := 0; i < len(s); i++ { + if s[i] < '0' || s[i] > '9' { + return u128{}, ErrInvalidFormat + } + + u, err = u.Mul64(10) + if err != nil { + return u128{}, err + } + + u, err = u.Add64(uint64(s[i] - '0')) + if err != nil { + return u128{}, err + } } - return neg, bint{u128: coef}, prec, nil + return u, nil } // GT returns true if u > v diff --git a/codec.go b/codec.go index 206e823..08bf4b3 100644 --- a/codec.go +++ b/codec.go @@ -14,13 +14,15 @@ import ( ) var ( - _ fmt.Stringer = (*Decimal)(nil) - _ sql.Scanner = (*Decimal)(nil) - _ driver.Valuer = (*Decimal)(nil) - _ encoding.TextMarshaler = (*Decimal)(nil) - _ encoding.TextUnmarshaler = (*Decimal)(nil) - _ json.Marshaler = (*Decimal)(nil) - _ json.Unmarshaler = (*Decimal)(nil) + _ fmt.Stringer = (*Decimal)(nil) + _ sql.Scanner = (*Decimal)(nil) + _ driver.Valuer = (*Decimal)(nil) + _ encoding.TextMarshaler = (*Decimal)(nil) + _ encoding.TextUnmarshaler = (*Decimal)(nil) + _ encoding.BinaryMarshaler = (*Decimal)(nil) + _ encoding.BinaryUnmarshaler = (*Decimal)(nil) + _ json.Marshaler = (*Decimal)(nil) + _ json.Unmarshaler = (*Decimal)(nil) ) // String returns the string representation of the decimal. @@ -109,8 +111,6 @@ var ( 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, // 110-119 bits 37, 37, 37, 38, 38, 38, 38, 39, 39, // 120-128 bits } - - digitBytes = [10]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} ) func (d Decimal) stringU128(trimTrailingZeros bool, withQuote bool) string { @@ -122,21 +122,13 @@ func (d Decimal) bytesU128(trimTrailingZeros bool, withQuote bool) []byte { var totalLen uint8 byteLen := maxByteMap[d.coef.u128.bitLen()] - if trimTrailingZeros { - // if d.prec > byteLen, that means we need to allocate upto d.prec to cover all the zeros of the fraction part - // e.g. 0.00000123, prec = 8, byteLen = 3 --> we need to allocate 8 bytes for the fraction part - if byteLen <= d.prec { - byteLen = d.prec + 1 // 1 for zero in the whole part - } - - totalLen = byteLen + 2 - } else { - // if not trimming trailing zeros, we can safely allocate 41 bytes - // 1 sign + 1 dot + len(u128) (which is max to 39 bytes) - // buf = []byte("00000000000000000000000000000000000000000") - totalLen = 41 + // if d.prec > byteLen, that means we need to allocate upto d.prec to cover all the zeros of the fraction part + // e.g. 0.00000123, prec = 8, byteLen = 3 --> we need to allocate 8 bytes for the fraction part + if byteLen <= d.prec { + byteLen = d.prec + 1 // 1 for zero in the whole part } + totalLen = byteLen + 2 if withQuote { // if withQuote is true, we need to add quotes at the beginning and the end totalLen += 2 @@ -173,7 +165,9 @@ func (d Decimal) fillBuffer(buf []byte, trimTrailingZeros bool) int { for ; rem != 0; rem /= 10 { n++ - buf[l-n] = digitBytes[rem%10] + + // nolint: gosec + buf[l-n] = byte(rem%10) + '0' } // fill remaining zeros @@ -192,10 +186,10 @@ func (d Decimal) fillBuffer(buf []byte, trimTrailingZeros bool) int { } else { for { q, r := quoRem64(quo, 10) - n++ - buf[l-n] = digitBytes[r] + // nolint: gosec + buf[l-n] = uint8(r%10) + '0' if q.IsZero() { break } @@ -224,7 +218,7 @@ func unsafeBytesToString(b []byte) string { return unsafe.String(unsafe.SliceData(b), len(b)) } -func unssafeStringToBytes(s string) []byte { +func unsafeStringToBytes(s string) []byte { return unsafe.Slice(unsafe.StringData(s), len(s)) } @@ -348,18 +342,19 @@ func (d *Decimal) unmarshalBinaryU128(data []byte) error { d.prec = data[1] totalBytes := data[2] - - // for u128, totalBytes must be 11 or 19 - if totalBytes != 11 && totalBytes != 19 { + if int(totalBytes) != len(data) { return ErrInvalidBinaryData } coef := u128{} - if totalBytes == 11 { - coef.lo = binary.BigEndian.Uint64(data[3:]) - } else { + switch totalBytes { + case 11: + coef = u128{lo: binary.BigEndian.Uint64(data[3:])} + case 19: coef.hi = binary.BigEndian.Uint64(data[3:]) coef.lo = binary.BigEndian.Uint64(data[11:]) + default: + return ErrInvalidBinaryData } d.coef.u128 = coef @@ -371,8 +366,7 @@ func (d *Decimal) unmarshalBinaryBigInt(data []byte) error { d.prec = data[1] totalBytes := data[2] - - if totalBytes < 3 { + if int(totalBytes) != len(data) { return ErrInvalidBinaryData } diff --git a/codec_test.go b/codec_test.go index dbe75ca..39d5a10 100644 --- a/codec_test.go +++ b/codec_test.go @@ -233,6 +233,7 @@ func TestInvalidUnmarshalBinary(t *testing.T) { }{ {"empty", []byte{}, fmt.Errorf("invalid binary data")}, {"invalid", []byte{0x01, 0x02, 0x03}, fmt.Errorf("invalid binary data")}, + {"total len mismatched", []byte{0x01, 0x02, 0x01, 0x04, 0x05}, fmt.Errorf("invalid binary data")}, {"len is less than 3", []byte{0x01, 0x02}, fmt.Errorf("invalid binary data")}, {"len is less than 3, bigInt", []byte{0x11, 0x02, 0x01}, fmt.Errorf("invalid binary data")}, } @@ -370,24 +371,23 @@ func BenchmarkMarshalBinaryBigInt(b *testing.B) { } } -func BenchmarkUnmarshalBinaryBigInt(b *testing.B) { +func BenchmarkUnmarshalJSON(b *testing.B) { b.StopTimer() - data, _ := MustParse("12345678901234567890123456789.1234567890123456789").MarshalBinary() + data := []byte("123456789.123456789") b.StartTimer() for i := 0; i < b.N; i++ { var d Decimal - _ = d.UnmarshalBinary(data) + _ = d.UnmarshalJSON(data) } } -func BenchmarkUnmarshalJSON(b *testing.B) { +func BenchmarkString(b *testing.B) { b.StopTimer() - data := []byte("123456789.123456789") + a := MustParse("0.1234567890123456789") b.StartTimer() for i := 0; i < b.N; i++ { - var d Decimal - _ = d.UnmarshalJSON(data) + _ = a.String() } } diff --git a/decimal.go b/decimal.go index b1505eb..eeb660c 100644 --- a/decimal.go +++ b/decimal.go @@ -279,7 +279,7 @@ func (d Decimal) InexactFloat64() float64 { // 1. empty/invalid string // 2. the number has more than 19 digits after the decimal point func Parse(s string) (Decimal, error) { - return parseBytes(unssafeStringToBytes(s)) + return parseBytes(unsafeStringToBytes(s)) } func parseBytes(b []byte) (Decimal, error) { diff --git a/decimal_test.go b/decimal_test.go index b52b097..63be7c2 100644 --- a/decimal_test.go +++ b/decimal_test.go @@ -31,6 +31,17 @@ func TestSetDefaultPrecision(t *testing.T) { }) } +func TestPrecOutOfRange(t *testing.T) { + defer SetDefaultPrecision(maxPrec) + + require.Equal(t, uint8(19), defaultPrec) + + SetDefaultPrecision(10) + + _, err := Parse("0.12345678901234569") + require.Equal(t, ErrPrecOutOfRange, err) +} + func TestNewFromHiLo(t *testing.T) { testcases := []struct { neg bool @@ -73,16 +84,16 @@ func TestParse(t *testing.T) { input, want string wantErr error }{ - {"", "", ErrEmptyString}, + {"340282366920938463463374.607431768211456", "340282366920938463463374.607431768211456", nil}, + {"0.123", "0.123", nil}, + {"1234567890123456789012345678901234567890", "1234567890123456789012345678901234567890", nil}, {"1234567890123456789.1234567890123456789", "1234567890123456789.1234567890123456789", nil}, {"0.0000123456", "0.0000123456", nil}, {"-0.0000123456", "-0.0000123456", nil}, {"0.0101010101010101", "0.0101010101010101", nil}, {"123.456000", "123.456", nil}, - {"1234567890123456789012345678901234567890", "1234567890123456789012345678901234567890", nil}, {"-12345678912345678901.1234567890123456789", "-12345678912345678901.1234567890123456789", nil}, {"123.0000", "123", nil}, - {"0.123", "0.123", nil}, {"-0.123", "-0.123", nil}, {"0", "0", nil}, {"0.00000", "0", nil}, @@ -110,6 +121,7 @@ func TestParse(t *testing.T) { {"-123456.0000000000000000001", "-123456.0000000000000000001", nil}, {"+123456.123456", "123456.123456", nil}, {"+123.123", "123.123", nil}, + {"923456789012345678901234567890123456.789", "923456789012345678901234567890123456.789", nil}, {"12345678901234567890.123456789", "12345678901234567890.123456789", nil}, {"1234567890123456789012345678901234567890", "1234567890123456789012345678901234567890", nil}, {"1234567890123456789123456789012345678901", "1234567890123456789123456789012345678901", nil}, @@ -123,6 +135,7 @@ func TestParse(t *testing.T) { {"-.1234567890123456789012345678901234567890123456", "", fmt.Errorf("%w: can't parse '-.1234567890123456789012345678901234567890123456' to Decimal", ErrInvalidFormat)}, {"1.12345678901234567890123.45678901234567890123456", "", fmt.Errorf("%w: can't parse '1.12345678901234567890123.45678901234567890123456' to Decimal", ErrInvalidFormat)}, {"340282366920938463463374607431768211459.123+--", "", fmt.Errorf("%w: can't parse '340282366920938463463374607431768211459.123+--' to Decimal", ErrInvalidFormat)}, + {"", "", ErrEmptyString}, {"1.234567890123456789012348901", "", ErrPrecOutOfRange}, {"1.123456789012345678912345678901234567890123456", "", ErrPrecOutOfRange}, {".", "", fmt.Errorf("%w: can't parse '.' to Decimal", ErrInvalidFormat)}, @@ -137,6 +150,9 @@ func TestParse(t *testing.T) { {"+.", "", fmt.Errorf("%w: can't parse '+.' to Decimal", ErrInvalidFormat)}, {"+", "", fmt.Errorf("%w: can't parse '+' to Decimal", ErrInvalidFormat)}, {"-", "", fmt.Errorf("%w: can't parse '-' to Decimal", ErrInvalidFormat)}, + {"abc.1234567890123456789", "", fmt.Errorf("%w: can't parse 'abc.1234567890123456789' to Decimal", ErrInvalidFormat)}, + {"123.1234567890123456abc", "", fmt.Errorf("%w: can't parse '123.1234567890123456abc' to Decimal", ErrInvalidFormat)}, + {"12345678901234567890123456789012345679801234567890.", "", fmt.Errorf("%w: can't parse '12345678901234567890123456789012345679801234567890.' to Decimal", ErrInvalidFormat)}, } for _, tc := range testcases { diff --git a/testdata/fuzz/FuzzParse/1dddc8bea8a723b5 b/testdata/fuzz/FuzzParse/1dddc8bea8a723b5 new file mode 100644 index 0000000..ecf7507 --- /dev/null +++ b/testdata/fuzz/FuzzParse/1dddc8bea8a723b5 @@ -0,0 +1,9 @@ +go test fuzz v1 +bool(false) +uint64(1) +uint64(0) +byte('\x00') +bool(false) +uint64(1) +uint64(0) +byte('"') diff --git a/u128.go b/u128.go index ff51d03..729e4cc 100644 --- a/u128.go +++ b/u128.go @@ -85,6 +85,11 @@ func (u u128) Add(v u128) (u128, error) { // Add64 returns u+v where v is a 64-bits unsigned integer. func (u u128) Add64(v uint64) (u128, error) { + if u.hi == 0 { + lo, carry := bits.Add64(u.lo, v, 0) + return u128{lo: lo, hi: carry}, nil + } + return u.Add(u128{lo: v}) } @@ -101,6 +106,11 @@ func (u u128) Sub(v u128) (u128, error) { // Mul64 returns u*v. If the result is greater than 2^128-1, Mul64 returns an overflow error. func (u u128) Mul64(v uint64) (u128, error) { + if u.hi == 0 { + hi, lo := bits.Mul64(u.lo, v) + return u128{hi: hi, lo: lo}, nil + } + hi, lo := bits.Mul64(u.lo, v) p0, p1 := bits.Mul64(u.hi, v) hi, c0 := bits.Add64(hi, p1, 0)