* perf: delegate NewFromFloat shortest-decimal conversion to strconv
NewFromFloat and NewFromFloat32 need the shortest decimal digit string
that round-trips back to the input float. strconv already computes
exactly that for FormatFloat with precision -1, but the library computes
it itself through decimal-go.go and rounding.go, a copy of the 2009
strconv/decimal.go: assign the mantissa into an 800-byte digit buffer,
shift it one binary bit at a time, then walk the digits against the two
neighbouring floats to find where they diverge.
Ask strconv for the value in 'e' format instead and read the digits back
out. The 'e' layout is [-]d[.ddd]e+dd, at most 24 bytes and trivial to
scan, unlike 'f' which can run past 750 bytes at the exponent extremes.
The result is identical, so no caller-visible behaviour changes.
Benchmarks (Ryzen 9 5900X, -benchtime 2s -count 5, median):
go1.22 go1.27
Float 366 -> 168 ns 332 -> 94 ns
Float32 271 -> 130 ns 241 -> 92 ns
Allocations are unchanged at 2 / 40 B, both from the big.NewInt that
Decimal.value requires. The gap widens on go1.27 because that release
replaced strconv's shortest-float conversion with the uscale algorithm.
Before go1.13, strconv.FormatFloat could return a shortest form that was
not the nearest one (golang/go#29491, the bug behind the two test table
entries added for it), so the old path is kept behind a !go1.13 build
tag. Nothing changes for go1.10 through go1.12; once the module's
minimum Go version passes 1.13, decimal-go.go, rounding.go and
newfromfloat_legacy.go can be deleted outright, removing 575 lines.
Verified equivalent against the previous implementation on go1.13 and
go1.27 over all 4,278,190,078 non-zero finite float32 bit patterns and
578,922,574 float64 values (random bit patterns, every power of two and
its neighbours, and a dense subnormal sweep), with no mismatch.
Tested on go1.10.8, go1.11.13, go1.12.17, go1.13.15, go1.22.12 and
go1.27.0.
* refactor: use strconv on every Go version, drop the vendored strconv copy
Keeping the old implementation behind build tags for Go 1.10-1.12 meant maintaining two
copies of NewFromFloat. Those versions now go through strconv too, which there can pick a
different, still round-tripping shortest form in rare cases (golang/go#29491), so the two
test cases for that only run on Go 1.13 and later.
---------
Co-authored-by: mwoss <wos.mateusz16@gmail.com>
ExpHullAbrham is documented to return overallPrecision significant
figures, but its final rounding used a separate branch when the result
was < 1 and rounded to overallPrecision decimal places instead. That
discarded the leading-zero positions, so results below 0.1 lost
significant figures and collapsed to 0 once the leading-zero count
reached overallPrecision -- e.g. exp(-50).ExpHullAbrham(10) returned 0
(true value 1.93e-22).
The >=1 branch formula currentPrecision - resNumDigits - res.exp is the
significant-figure-correct rounding position in both cases, so drop the
branch. Positive arguments and results in [0.1,1) are unchanged.
Correct the negative rows of the ExpHullAbrham table (they encoded the
truncated values) and add a significant-figure sweep over the negative
half-line, checked against exp(x) to 40+ digits.
Co-authored-by: mwoss <wos.mateusz16@gmail.com>
Some locales and copy-paste sources use U+2212 (−) instead of ASCII '-'.
Normalize it before parsing so NewFromString accepts both.
Closes#318
Co-authored-by: Archit Goyal <jabstack@Archits-MacBook-Air.local>
Co-authored-by: mwoss <wos.mateusz16@gmail.com>
decimal.go had grown to about 2,900 lines. This moves declarations,
byte for byte with their comments, into:
- math.go: power, exponential, logarithm and trigonometric functions
and their helpers (Pow, PowWithPrecision, PowInt32, PowBigInt,
ExpHullAbrham, ExpTaylor, Ln, Sin, Cos, Tan, Atan)
- encoding.go: JSON, text, binary, gob, SQL and Spanner methods, and
NullDecimal
- const.go: internal constants and lookup tables (zeroInt to twentyInt,
pow10Table, pow10Uint64, float64Pow10, pow5Int64, pow10, zeros)
Nothing else changes. The go doc -all output is the same.
To follow the history of moved code:
git blame -C <file>
git log -L :ExpTaylor:decimal.go aed8543
git show --color-moved=zebra --color-moved-ws=no <this commit>
* perf: faster QuoRem, DivRound, Round, RoundBank, RoundUp, RoundCeil and RoundFloor
* fix: keep the zero representation of RoundCeil and RoundFloor results
* perf: fewer allocations in Add, Sub, Cmp, Sum and Shift
* fix: keep the zero representation of Shift and Sum results
* fix: Shift of zero keeps the zero representation
* fix: panic when Shift overflows the exponent
Shift added the exponents as int32, so shifting 5 * 10^2147483647
up by one came back as 5 * 10^-2147483648. Mul already panics
when an exponent does not fit.
* Address the Shift overflow review.
The wrap comment was wrong, and the panic cases now use didPanic.
RoundUp, RoundDown, RoundFloor, and RoundCeil all returned the original
decimal (with its original exponent) when the value was already exact at
the requested precision. This caused the result to carry a trailing
exponent that did not match the requested places.
For example, (100.0).RoundUp(0) returned a Decimal with exp=-1 instead
of exp=0, so StringFixedBank(-d.Exponent()) would produce "100.0" rather
than "100".
Fix: when the rescaled value equals the original, return the rescaled
form (which has the canonical exponent) instead of the original.
Truncate previously rejected negative precision values due to a
precision >= 0 guard, silently returning the input unchanged. The
rescale function already uses integer division (Quo, truncating toward
zero), so removing the guard is sufficient to make negative precision
work correctly.
For example, (5432).Truncate(-2) now returns 5400 instead of 5432.
Fixes#390Fixes#406
Co-authored-by: Amit Mishra <amitmishraaa11@gmail.com>
* fix: Float64 hangs on decimals with huge exponents
Float64 went through Rat(), which builds 10^|exp| as a big.Int. With an
exponent like -1<<30 that never finishes, even when the coefficient is 0.
Zero and values whose magnitude is far outside the float64 range now
return 0 or +/-Inf directly, matching what big.Rat.Float64 would give.
Fixes#226
* docs: explain the Float64 magnitude bounds
math.Log10(1eN) rounds down to N-1 for several N (15, 24, 28, ...)
because the exact decimal can't survive a float64 round-trip. The
fast path returned int(math.Log10(...)) + 1, so a coefficient like
1000000000000000 (16 digits) came back as 15.
Count digits via int64 division for the IsInt64 branch. The slow
big.Int path is unchanged.
Regression tests cover 1e15 and -1e15 (both via plain literal and
scientific-notation input) and 1<<53 (the old fast-path boundary).
Fixes#420
Signed-off-by: Charlie Tonneslan <cst0520@gmail.com>