diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd6c836..1d618bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,18 @@ name: ci on: push: - branches: - - master + branches: [ master ] pull_request: + branches: [ master ] + types: [ opened, synchronize ] +permissions: + contents: read jobs: ci-job: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.10.x', '1.19', '1.20', '1.21', '1.22', '1.x' ] + go: [ '1.10.x', '1.24', '1.25', '1.26', '1.27', '1.x' ] name: Go ${{ matrix.go }} steps: - uses: actions/checkout@v4 @@ -19,3 +22,15 @@ jobs: go-version: ${{ matrix.go }} - run: go build . - run: go test -v + lint: + runs-on: ubuntu-latest + name: Lint + steps: + - uses: actions/checkout@v4 + - name: Use go + uses: actions/setup-go@v5 + with: + go-version: stable + - uses: golangci/golangci-lint-action@v9 + with: + version: v2.14.0 diff --git a/.gitignore b/.gitignore index ff36b98..6838f34 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ # VS code *.code-workspace +.vscode/ + +mise.toml \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..794257a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,64 @@ +version: "2" + +# golangci-lint takes the Go version from go.mod (1.10), so linters that only suggest newer +# language features or APIs stay off: modernize, intrange, copyloopvar, and errorlint +# (errors.Is and %w need Go 1.13). + +linters: + default: standard # errcheck, govet, ineffassign, staticcheck, unused + enable: + - bidichk + - dupword + - gocritic + - makezero + - misspell + - nilerr + - nolintlint + - predeclared + - revive + - unconvert + - unparam + - wastedassign + # Off on purpose: + # - mnd: a math library is full of meaningful numeric constants. + # - gosec: its findings here are G115 integer conversions, which are deliberate and range checked. + # - funlen, gocyclo, gocognit, cyclop: numeric algorithms are long by nature. + settings: + govet: + enable-all: true + disable: + - fieldalignment # field order follows readability, not padding + - shadow # shadowing err and loop variables is common and harmless here + staticcheck: + checks: + # golangci-lint's defaults + - all + - -ST1000 + - -ST1003 + - -ST1016 + - -ST1020 + - -ST1021 + - -ST1022 + - -QF1004 # suggests strings.ReplaceAll, which needs Go 1.12 + gocritic: + # Bug and performance checks only. The style checks are opinionated, and the experimental + # commentedOutCode check reads formula comments like "v * 10^(f-q) = e^s * 10^f" as code. + enabled-tags: + - diagnostic + - performance + disabled-tags: + - style + - experimental + - opinionated + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + +formatters: + enable: + - gofmt + - goimports + settings: + gofmt: + simplify: false # gofmt -s would rewrite many test tables that open pull requests also change diff --git a/CHANGELOG.md b/CHANGELOG.md index 432d0fd..2d3bad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,66 @@ +## Decimal v1.6.0 + +#### BEHAVIOR CHANGES +- `Decimal` implements `fmt.Formatter`. `%#v` prints Go code that recreates the value, like `decimal.New(314, -2)`, instead of the struct fields, and verbs that don't apply, like `%d`, print `%!d(decimal.Decimal=3.14)`. `%v`, `%s`, `%q`, `%x` and `fmt.Println` print the same as before [#463](https://github.com/shopspring/decimal/pull/463) +- `Ln` rounds correctly, so some results change in the last digit: `Ln` of 10 to 5 places is `2.30259`, not `2.30258` [#471](https://github.com/shopspring/decimal/pull/471) +- `ExpHullAbrham` returns `overallPrecision` significant figures for every input. Negative inputs used to lose digits or return 0, like `-50` at precision 10, and large inputs returned extra digits [#429](https://github.com/shopspring/decimal/pull/429) [#470](https://github.com/shopspring/decimal/pull/470) +- `Scan` converts `float32` with `NewFromFloat32`, so `54.33` stays `54.33` instead of becoming `54.33000183105469`. NaN and infinite floats return an error instead of panicking [#469](https://github.com/shopspring/decimal/pull/469) +- On Go 1.10 to 1.12, `NewFromFloat` can pick a different shortest form in rare cases ([golang/go#29491](https://github.com/golang/go/issues/29491)). It still converts back to the same float [#431](https://github.com/shopspring/decimal/pull/431) + +#### FEATURES +- Add BSON Decimal128 support for `Decimal` and `NullDecimal` with the official MongoDB Go driver v2, without a new dependency [#460](https://github.com/shopspring/decimal/pull/460) +- Add `%f` formatting with precision, width and flags, like `%.2f` and `%08.2f` [#463](https://github.com/shopspring/decimal/pull/463) +- Add `Normalize`, which strips trailing zeros so equal decimals also match with `reflect.DeepEqual`, testify and gomock [#464](https://github.com/shopspring/decimal/pull/464) +- Add `Sqrt` and `SqrtRound` [#465](https://github.com/shopspring/decimal/pull/465) +- Add `Root` and `RootRound` for n-th roots [#473](https://github.com/shopspring/decimal/pull/473) +- Add `NewFromBigFloat` constructor [#457](https://github.com/shopspring/decimal/pull/457) +- `Scan` accepts `Decimal`, `big.Int`, `*big.Int` and all integer sizes, as passed by ClickHouse and other drivers [#469](https://github.com/shopspring/decimal/pull/469) + +#### ENHANCEMENTS +- `NewFromFloat` and `NewFromFloat32` are 3 to 6 times faster. They use `strconv` instead of a vendored copy of it from 2009, which is removed [#431](https://github.com/shopspring/decimal/pull/431) +- `Ln` is 3 to 14 times faster [#471](https://github.com/shopspring/decimal/pull/471) +- Slightly faster `IsInteger` and `Ceil` [#299](https://github.com/shopspring/decimal/pull/299) +- `NewFromString` accepts the Unicode minus sign `−` (U+2212) [#428](https://github.com/shopspring/decimal/pull/428) +- Scanning NULL into a `Decimal` returns an error that points to `NullDecimal` or `*Decimal` [#474](https://github.com/shopspring/decimal/pull/474) +- Document that global settings aren't goroutine-safe, and list them with their per-call alternatives in the README [#466](https://github.com/shopspring/decimal/pull/466) +- README: show how to control the JSON format of one field, add `udecimal` to the alternative libraries, and update the Go doc and playground links [#475](https://github.com/shopspring/decimal/pull/475) [#476](https://github.com/shopspring/decimal/pull/476) [#264](https://github.com/shopspring/decimal/pull/264) +- Fix doc examples that were wrong or didn't compile [#468](https://github.com/shopspring/decimal/pull/468) +- Fix benchmarks that measured their setup or the wrong function [#467](https://github.com/shopspring/decimal/pull/467) + +#### BUGFIXES +- `QuoRem` and `DivRound` panic on precision `math.MinInt32` instead of returning a wrong quotient [#472](https://github.com/shopspring/decimal/pull/472) + +## Decimal v1.5.0 + +#### BEHAVIOR CHANGES +- `NewFromString` and `UnmarshalBinary`, and so JSON, text, SQL, gob and Spanner decoding, reject exponents larger than the new `MaxDecodeExponent` setting (default 10,000). Such input could hang the process or use gigabytes of memory. Raise `MaxDecodeExponent` if you need larger exponents [#440](https://github.com/shopspring/decimal/pull/440) +- `Pow`, `PowWithPrecision`, `PowInt32` and `PowBigInt` round correctly. Results for fractional exponents have `PowPrecisionNegativeExponent` (16) decimal places, and negative powers that used to return 0 keep 16 significant digits, so `10^-18` is `0.000000000000000001`. Results too large to represent return an error, or 0 from `Pow`, instead of hanging or panicking [#439](https://github.com/shopspring/decimal/pull/439) +- `RoundUp`, `RoundDown`, `RoundCeil` and `RoundFloor` always return exponent `-places`, like `Round`. Values don't change, but `Exponent()`, `Coefficient()` and `String()` with `TrimTrailingZeros = false` can [#426](https://github.com/shopspring/decimal/pull/426) [#441](https://github.com/shopspring/decimal/pull/441) +- `Truncate` supports negative precision: `5432` truncated to `-2` is `5400`. Before, it returned `5432` unchanged [#426](https://github.com/shopspring/decimal/pull/426) +- `Shift` panics when the exponent overflows an int32, instead of wrapping around [#437](https://github.com/shopspring/decimal/pull/437) +- `NewFromString` rejects a sign right after a leading decimal point, such as `".-5"` [#447](https://github.com/shopspring/decimal/pull/447) + +#### FEATURES +- Add `TrimTrailingZeros` and `UseScientificNotation` settings for string output [#389](https://github.com/shopspring/decimal/pull/389) [#416](https://github.com/shopspring/decimal/pull/416) +- Add Spanner support with `EncodeSpanner` and `DecodeSpanner` for `Decimal` and `NullDecimal` [#395](https://github.com/shopspring/decimal/pull/395) + +#### ENHANCEMENTS +- Most operations are faster and allocate less, about twice as fast on average across the benchmarks [#444](https://github.com/shopspring/decimal/pull/444) [#445](https://github.com/shopspring/decimal/pull/445) [#446](https://github.com/shopspring/decimal/pull/446) [#447](https://github.com/shopspring/decimal/pull/447) [#448](https://github.com/shopspring/decimal/pull/448) [#449](https://github.com/shopspring/decimal/pull/449) [#451](https://github.com/shopspring/decimal/pull/451) +- Faster `UnmarshalJSON`, `Scan` and `NewFromString` [#403](https://github.com/shopspring/decimal/pull/403) +- `ExpHullAbrham` raises its partial sum to a power by squaring. Large inputs that took minutes now take seconds [#450](https://github.com/shopspring/decimal/pull/450) +- Document that `NewFromFloat` returns the shortest decimal that converts back to the same float, and how to convert large whole numbers exactly [#442](https://github.com/shopspring/decimal/pull/442) +- Split `decimal.go` into `decimal.go`, `math.go` and `encoding.go` [#452](https://github.com/shopspring/decimal/pull/452) +- Lint with golangci-lint in CI, and test on Go 1.26 and 1.27 [#453](https://github.com/shopspring/decimal/pull/453) [#454](https://github.com/shopspring/decimal/pull/454) + +#### BUGFIXES +- Fix `Float64` hanging on decimals with huge exponents [#438](https://github.com/shopspring/decimal/pull/438) +- Fix `ExpHullAbrham` returning `10^exp` instead of 1 for tiny inputs [#433](https://github.com/shopspring/decimal/pull/433) +- Fix a data race in `ExpTaylor` [#414](https://github.com/shopspring/decimal/pull/414) +- Fix `NumDigits` for some exact powers of ten [#425](https://github.com/shopspring/decimal/pull/425) +- Fix the `omitzero` struct tag for `Decimal` fields in nested structs [#415](https://github.com/shopspring/decimal/pull/415) +- Fix `RoundCash` giving wrong results with a low `DivisionPrecision` [#443](https://github.com/shopspring/decimal/pull/443) +- `NullDecimal` is no longer `Valid` after a failed `Scan`, `UnmarshalJSON` or `DecodeSpanner` [#443](https://github.com/shopspring/decimal/pull/443) + ## Decimal v1.4.0 #### BREAKING - Drop support for Go version older than 1.10 [#361](https://github.com/shopspring/decimal/pull/361) diff --git a/README.md b/README.md index 318c9df..4b782f5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # decimal [![ci](https://github.com/shopspring/decimal/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/shopspring/decimal/actions/workflows/ci.yml) -[![GoDoc](https://godoc.org/github.com/shopspring/decimal?status.svg)](https://godoc.org/github.com/shopspring/decimal) -[![Go Report Card](https://goreportcard.com/badge/github.com/shopspring/decimal)](https://goreportcard.com/report/github.com/shopspring/decimal) +[![GoDoc](https://pkg.go.dev/badge/github.com/shopspring/decimal)](https://pkg.go.dev/github.com/shopspring/decimal) Arbitrary-precision fixed-point decimal numbers in go. _Note:_ Decimal library can "only" represent numbers with a maximum of 2^31 digits after the decimal point. +Results that aren't exact, like `Div` and `Sqrt`, are rounded to 16 decimal places by default, and decoded input is +limited to exponents up to 10,000. See [Global settings](#global-settings). ## Features @@ -15,6 +16,7 @@ _Note:_ Decimal library can "only" represent numbers with a maximum of 2^31 digi * Division with specified precision * Database/sql serialization/deserialization * JSON and XML serialization/deserialization + * MongoDB BSON serialization/deserialization as Decimal128, with the official Go driver v2 ## Install @@ -26,7 +28,7 @@ Decimal library requires Go version `>=1.10` ## Documentation -http://godoc.org/github.com/shopspring/decimal +https://pkg.go.dev/github.com/shopspring/decimal ## Usage @@ -64,6 +66,22 @@ func main() { } ``` +## Global settings + +These package-level variables change how some functions behave: + +| Variable | Default | What it changes | Per-call alternative | +|---|---|---|---| +| `DivisionPrecision` | `16` | Decimal places in `Div` and `Sqrt` results, and in `Avg`, `Atan` and `Tan` | `DivRound`, `SqrtRound` | +| `PowPrecisionNegativeExponent` | `16` | Decimal places in `Pow`, `PowInt32` and `PowBigInt` results for negative or fractional exponents | `PowWithPrecision` | +| `TrimTrailingZeros` | `true` | Whether `String`, and so JSON, text and SQL output, drops trailing zeros: `2.50` prints as `2.5` | `StringFixed` | +| `UseScientificNotation` | `false` | Whether `String` prints numbers stored with a positive exponent in scientific notation, like `1.2E3` | `StringFixed` | +| `MarshalJSONWithoutQuotes` | `false` | Whether JSON output is a number instead of a string | | +| `ExpMaxIterations` | `1000` | Iteration limit of `ExpHullAbrham` | | +| `MaxDecodeExponent` | `10000` | Largest exponent size that `NewFromString` and the decoders accept | | + +Set them once at program start. Changing one while other goroutines use the library is a data race. + ## Alternative libraries When working with decimal numbers, you might face problems this library is not perfectly suited for. @@ -73,6 +91,7 @@ Explore other alternatives to find the one that best fits your needs :) * [cockroachdb/apd](https://github.com/cockroachdb/apd) - arbitrary precision, mutable and rich API similar to `big.Int`, more performant than this library * [alpacahq/alpacadecimal](https://github.com/alpacahq/alpacadecimal) - high performance, low precision (12 digits), fully compatible API with this library * [govalues/decimal](https://github.com/govalues/decimal) - high performance, zero-allocation, low precision (19 digits) +* [quagmt/udecimal](https://github.com/quagmt/udecimal) - high performance, mostly zero-allocation, up to 19 digits after the decimal point * [greatcloak/decimal](https://github.com/greatcloak/decimal) - fork focusing on billing and e-commerce web application related use cases, includes out-of-the-box BSON marshaling support ## FAQ @@ -82,7 +101,7 @@ Explore other alternatives to find the one that best fits your needs :) Because float64 (or any binary floating point type, actually) can't represent numbers such as `0.1` exactly. -Consider this code: http://play.golang.org/p/TQBd4yJe6B You might expect that +Consider this code: https://go.dev/play/p/TQBd4yJe6B You might expect that it prints out `10`, but it actually prints `9.999999999999831`. Over time, these small errors can really add up! @@ -97,7 +116,7 @@ out, the string output has to stop somewhere (let's say it stops at 3 decimal digits, for simplicity), so you'll get 0.333, 0.333, and 0.333. But where did the other 0.001 go? -Here's the above example as code: http://play.golang.org/p/lCZZs0w9KE +Here's the above example as code: https://go.dev/play/p/lCZZs0w9KE With Decimal, the strings being printed out represent the number exactly. So, if you have `x = y = 1/3` (with precision 3), they will actually be equal to @@ -122,7 +141,7 @@ modifies `a` and sets `z` as an alias for `a`, which they might not expect. It also modifies any other aliases to `a`. Here's an example of the subtle bugs you can introduce with big.Int's API: -https://play.golang.org/p/x2R_78pa8r +https://go.dev/play/p/x2R_78pa8r In contrast, it's difficult to make such mistakes with decimal. Decimals behave like other go numbers types: even though `a = b` will not deep copy @@ -132,6 +151,22 @@ this causes extra allocations, so Decimal is less performant. My assumption is that if you're using Decimals, you probably care more about correctness than performance. +#### How do I control the JSON format of one field? + +Wrap the field in a small type with its own `MarshalJSON`. For example, to always write money with 2 decimal places: + +```go +type Money struct{ decimal.Decimal } + +func (m Money) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.StringFixed(2) + `"`), nil +} +``` + +A `Money` field marshals as `"12.50"`, other decimals keep their usual format, and unmarshaling still works through +the embedded `Decimal`. To write one field as a JSON number instead of a string, return `[]byte(m.String())`. +This is safer than the global settings, which change every decimal in the program. + ## License The MIT License (MIT) diff --git a/const.go b/const.go index e5d6fa8..f2f8647 100644 --- a/const.go +++ b/const.go @@ -1,63 +1,59 @@ package decimal import ( - "strings" + "math/big" ) const ( strLn10 = "2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286248633409525465082806756666287369098781689482907208325554680843799894826233198528393505308965377732628846163366222287698219886746543667474404243274365155048934314939391479619404400222105101714174800368808401264708068556774321622835522011480466371565912137345074785694768346361679210180644507064800027750268491674655058685693567342067058113642922455440575892572420824131469568901675894025677631135691929203337658714166023010570308963457207544037084746994016826928280848118428931484852494864487192780967627127577539702766860595249671667418348570442250719796500471495105049221477656763693866297697952211071826454973477266242570942932258279850258550978526538320760672631716430950599508780752371033310119785754733154142180842754386359177811705430982748238504564801909561029929182431823752535770975053956518769751037497088869218020518933950723853920514463419726528728696511086257149219884997874887377134568620916705849807828059751193854445009978131146915934666241071846692310107598438319191292230792503747298650929009880391941702654416816335727555703151596113564846546190897042819763365836983716328982174407366009162177850541779276367731145041782137660111010731042397832521894898817597921798666394319523936855916447118246753245630912528778330963604262982153040874560927760726641354787576616262926568298704957954913954918049209069438580790032763017941503117866862092408537949861264933479354871737451675809537088281067452440105892444976479686075120275724181874989395971643105518848195288330746699317814634930000321200327765654130472621883970596794457943468343218395304414844803701305753674262153675579814770458031413637793236291560128185336498466942261465206459942072917119370602444929358037007718981097362533224548366988505528285966192805098447175198503666680874970496982273220244823343097169111136813588418696549323714996941979687803008850408979618598756579894836445212043698216415292987811742973332588607915912510967187510929248475023930572665446276200923068791518135803477701295593646298412366497023355174586195564772461857717369368404676577047874319780573853271810933883496338813069945569399346101090745616033312247949360455361849123333063704751724871276379140924398331810164737823379692265637682071706935846394531616949411701841938119405416449466111274712819705817783293841742231409930022911502362192186723337268385688273533371925103412930705632544426611429765388301822384091026198582888433587455960453004548370789052578473166283701953392231047527564998119228742789713715713228319641003422124210082180679525276689858180956119208391760721080919923461516952599099473782780648128058792731993893453415320185969711021407542282796298237068941764740642225757212455392526179373652434440560595336591539160312524480149313234572453879524389036839236450507881731359711238145323701508413491122324390927681724749607955799151363982881058285740538000653371655553014196332241918087621018204919492651483892" ) -var ( - ln10 = newConstApproximation(strLn10) -) +var zeroInt = big.NewInt(0) +var oneInt = big.NewInt(1) +var twoInt = big.NewInt(2) +var fourInt = big.NewInt(4) +var fiveInt = big.NewInt(5) +var tenInt = big.NewInt(10) +var twentyInt = big.NewInt(20) -type constApproximation struct { - exact Decimal - approximations []Decimal +// pow10Table holds 10^0 ... 10^127, its values must not be modified. +var pow10Table = func() []*big.Int { + t := make([]*big.Int, 128) + t[0] = big.NewInt(1) + for i := 1; i < len(t); i++ { + t[i] = new(big.Int).Mul(t[i-1], tenInt) + } + return t +}() + +// pow10Uint64 holds 10^0 ... 10^19. +var pow10Uint64 = func() (t [20]uint64) { + t[0] = 1 + for i := 1; i < len(t); i++ { + t[i] = t[i-1] * 10 + } + return t +}() + +// float64Pow10 holds 10^0 ... 10^22, the powers of ten that float64 represents exactly. +var float64Pow10 = [...]float64{1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, + 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22} + +// pow5Int64 holds 5^0 ... 5^22. +var pow5Int64 = func() (t [23]int64) { + t[0] = 1 + for i := 1; i < len(t); i++ { + t[i] = t[i-1] * 5 + } + return t +}() + +// pow10 returns 10^n for n >= 0, the result must not be modified. +func pow10(n int64) *big.Int { + if n < int64(len(pow10Table)) { + return pow10Table[n] + } + return new(big.Int).Exp(tenInt, big.NewInt(n), nil) } -func newConstApproximation(value string) constApproximation { - parts := strings.Split(value, ".") - coeff, fractional := parts[0], parts[1] - - coeffLen := len(coeff) - maxPrecision := len(fractional) - - var approximations []Decimal - for p := 1; p < maxPrecision; p *= 2 { - r := RequireFromString(value[:coeffLen+p]) - approximations = append(approximations, r) - } - - return constApproximation{ - RequireFromString(value), - approximations, - } -} - -// Returns the smallest approximation available that's at least as precise -// as the passed precision (places after decimal point), i.e. Floor[ log2(precision) ] + 1 -func (c constApproximation) withPrecision(precision int32) Decimal { - i := 0 - - if precision >= 1 { - i++ - } - - for precision >= 16 { - precision /= 16 - i += 4 - } - - for precision >= 2 { - precision /= 2 - i++ - } - - if i >= len(c.approximations) { - return c.exact - } - - return c.approximations[i] -} +const zeros = "0000000000000000000000000000000000000000000000000000000000000000" diff --git a/const_test.go b/const_test.go deleted file mode 100644 index 67a27a7..0000000 --- a/const_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package decimal - -import "testing" - -func TestConstApproximation(t *testing.T) { - for _, testCase := range []struct { - Const string - Precision int32 - ExpectedApproximation string - }{ - {"2.3025850929940456840179914546", 0, "2"}, - {"2.3025850929940456840179914546", 1, "2.3"}, - {"2.3025850929940456840179914546", 3, "2.302"}, - {"2.3025850929940456840179914546", 5, "2.302585"}, - {"2.3025850929940456840179914546", 10, "2.302585092994045"}, - {"2.3025850929940456840179914546", 100, "2.3025850929940456840179914546"}, - {"2.3025850929940456840179914546", -1, "2"}, - {"2.3025850929940456840179914546", -5, "2"}, - {"3.14159265359", 0, "3"}, - {"3.14159265359", 1, "3.1"}, - {"3.14159265359", 2, "3.141"}, - {"3.14159265359", 4, "3.1415926"}, - {"3.14159265359", 13, "3.14159265359"}, - } { - ca := newConstApproximation(testCase.Const) - expected, _ := NewFromString(testCase.ExpectedApproximation) - - approximation := ca.withPrecision(testCase.Precision) - - if approximation.Cmp(expected) != 0 { - t.Errorf("expected approximation %s, got %s - for const with %s precision %d", testCase.ExpectedApproximation, approximation.String(), testCase.Const, testCase.Precision) - } - } -} diff --git a/decimal-go.go b/decimal-go.go deleted file mode 100644 index 9958d69..0000000 --- a/decimal-go.go +++ /dev/null @@ -1,415 +0,0 @@ -// 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. - -// Multiprecision decimal numbers. -// For floating-point formatting only; not general purpose. -// Only operations are assign and (binary) left/right shift. -// Can do binary floating point in multiprecision decimal precisely -// because 2 divides 10; cannot do decimal floating point -// in multiprecision binary precisely. - -package decimal - -type decimal struct { - d [800]byte // digits, big-endian representation - nd int // number of digits used - dp int // decimal point - neg bool // negative flag - trunc bool // discarded nonzero digits beyond d[:nd] -} - -func (a *decimal) String() string { - n := 10 + a.nd - if a.dp > 0 { - n += a.dp - } - if a.dp < 0 { - n += -a.dp - } - - buf := make([]byte, n) - w := 0 - switch { - case a.nd == 0: - return "0" - - case a.dp <= 0: - // zeros fill space between decimal point and digits - buf[w] = '0' - w++ - buf[w] = '.' - w++ - w += digitZero(buf[w : w+-a.dp]) - w += copy(buf[w:], a.d[0:a.nd]) - - case a.dp < a.nd: - // decimal point in middle of digits - w += copy(buf[w:], a.d[0:a.dp]) - buf[w] = '.' - w++ - w += copy(buf[w:], a.d[a.dp:a.nd]) - - default: - // zeros fill space between digits and decimal point - w += copy(buf[w:], a.d[0:a.nd]) - w += digitZero(buf[w : w+a.dp-a.nd]) - } - return string(buf[0:w]) -} - -func digitZero(dst []byte) int { - for i := range dst { - dst[i] = '0' - } - return len(dst) -} - -// trim trailing zeros from number. -// (They are meaningless; the decimal point is tracked -// independent of the number of digits.) -func trim(a *decimal) { - for a.nd > 0 && a.d[a.nd-1] == '0' { - a.nd-- - } - if a.nd == 0 { - a.dp = 0 - } -} - -// Assign v to a. -func (a *decimal) Assign(v uint64) { - var buf [24]byte - - // Write reversed decimal in buf. - n := 0 - for v > 0 { - v1 := v / 10 - v -= 10 * v1 - buf[n] = byte(v + '0') - n++ - v = v1 - } - - // Reverse again to produce forward decimal in a.d. - a.nd = 0 - for n--; n >= 0; n-- { - a.d[a.nd] = buf[n] - a.nd++ - } - a.dp = a.nd - trim(a) -} - -// Maximum shift that we can do in one pass without overflow. -// A uint has 32 or 64 bits, and we have to be able to accommodate 9<> 63) -const maxShift = uintSize - 4 - -// Binary shift right (/ 2) by k bits. k <= maxShift to avoid overflow. -func rightShift(a *decimal, k uint) { - r := 0 // read pointer - w := 0 // write pointer - - // Pick up enough leading digits to cover first shift. - var n uint - for ; n>>k == 0; r++ { - if r >= a.nd { - if n == 0 { - // a == 0; shouldn't get here, but handle anyway. - a.nd = 0 - return - } - for n>>k == 0 { - n = n * 10 - r++ - } - break - } - c := uint(a.d[r]) - n = n*10 + c - '0' - } - a.dp -= r - 1 - - var mask uint = (1 << k) - 1 - - // Pick up a digit, put down a digit. - for ; r < a.nd; r++ { - c := uint(a.d[r]) - dig := n >> k - n &= mask - a.d[w] = byte(dig + '0') - w++ - n = n*10 + c - '0' - } - - // Put down extra digits. - for n > 0 { - dig := n >> k - n &= mask - if w < len(a.d) { - a.d[w] = byte(dig + '0') - w++ - } else if dig > 0 { - a.trunc = true - } - n = n * 10 - } - - a.nd = w - trim(a) -} - -// Cheat sheet for left shift: table indexed by shift count giving -// number of new digits that will be introduced by that shift. -// -// For example, leftcheats[4] = {2, "625"}. That means that -// if we are shifting by 4 (multiplying by 16), it will add 2 digits -// when the string prefix is "625" through "999", and one fewer digit -// if the string prefix is "000" through "624". -// -// Credit for this trick goes to Ken. - -type leftCheat struct { - delta int // number of new digits - cutoff string // minus one digit if original < a. -} - -var leftcheats = []leftCheat{ - // Leading digits of 1/2^i = 5^i. - // 5^23 is not an exact 64-bit floating point number, - // so have to use bc for the math. - // Go up to 60 to be large enough for 32bit and 64bit platforms. - /* - seq 60 | sed 's/^/5^/' | bc | - awk 'BEGIN{ print "\t{ 0, \"\" }," } - { - log2 = log(2)/log(10) - printf("\t{ %d, \"%s\" },\t// * %d\n", - int(log2*NR+1), $0, 2**NR) - }' - */ - {0, ""}, - {1, "5"}, // * 2 - {1, "25"}, // * 4 - {1, "125"}, // * 8 - {2, "625"}, // * 16 - {2, "3125"}, // * 32 - {2, "15625"}, // * 64 - {3, "78125"}, // * 128 - {3, "390625"}, // * 256 - {3, "1953125"}, // * 512 - {4, "9765625"}, // * 1024 - {4, "48828125"}, // * 2048 - {4, "244140625"}, // * 4096 - {4, "1220703125"}, // * 8192 - {5, "6103515625"}, // * 16384 - {5, "30517578125"}, // * 32768 - {5, "152587890625"}, // * 65536 - {6, "762939453125"}, // * 131072 - {6, "3814697265625"}, // * 262144 - {6, "19073486328125"}, // * 524288 - {7, "95367431640625"}, // * 1048576 - {7, "476837158203125"}, // * 2097152 - {7, "2384185791015625"}, // * 4194304 - {7, "11920928955078125"}, // * 8388608 - {8, "59604644775390625"}, // * 16777216 - {8, "298023223876953125"}, // * 33554432 - {8, "1490116119384765625"}, // * 67108864 - {9, "7450580596923828125"}, // * 134217728 - {9, "37252902984619140625"}, // * 268435456 - {9, "186264514923095703125"}, // * 536870912 - {10, "931322574615478515625"}, // * 1073741824 - {10, "4656612873077392578125"}, // * 2147483648 - {10, "23283064365386962890625"}, // * 4294967296 - {10, "116415321826934814453125"}, // * 8589934592 - {11, "582076609134674072265625"}, // * 17179869184 - {11, "2910383045673370361328125"}, // * 34359738368 - {11, "14551915228366851806640625"}, // * 68719476736 - {12, "72759576141834259033203125"}, // * 137438953472 - {12, "363797880709171295166015625"}, // * 274877906944 - {12, "1818989403545856475830078125"}, // * 549755813888 - {13, "9094947017729282379150390625"}, // * 1099511627776 - {13, "45474735088646411895751953125"}, // * 2199023255552 - {13, "227373675443232059478759765625"}, // * 4398046511104 - {13, "1136868377216160297393798828125"}, // * 8796093022208 - {14, "5684341886080801486968994140625"}, // * 17592186044416 - {14, "28421709430404007434844970703125"}, // * 35184372088832 - {14, "142108547152020037174224853515625"}, // * 70368744177664 - {15, "710542735760100185871124267578125"}, // * 140737488355328 - {15, "3552713678800500929355621337890625"}, // * 281474976710656 - {15, "17763568394002504646778106689453125"}, // * 562949953421312 - {16, "88817841970012523233890533447265625"}, // * 1125899906842624 - {16, "444089209850062616169452667236328125"}, // * 2251799813685248 - {16, "2220446049250313080847263336181640625"}, // * 4503599627370496 - {16, "11102230246251565404236316680908203125"}, // * 9007199254740992 - {17, "55511151231257827021181583404541015625"}, // * 18014398509481984 - {17, "277555756156289135105907917022705078125"}, // * 36028797018963968 - {17, "1387778780781445675529539585113525390625"}, // * 72057594037927936 - {18, "6938893903907228377647697925567626953125"}, // * 144115188075855872 - {18, "34694469519536141888238489627838134765625"}, // * 288230376151711744 - {18, "173472347597680709441192448139190673828125"}, // * 576460752303423488 - {19, "867361737988403547205962240695953369140625"}, // * 1152921504606846976 -} - -// Is the leading prefix of b lexicographically less than s? -func prefixIsLessThan(b []byte, s string) bool { - for i := 0; i < len(s); i++ { - if i >= len(b) { - return true - } - if b[i] != s[i] { - return b[i] < s[i] - } - } - return false -} - -// Binary shift left (* 2) by k bits. k <= maxShift to avoid overflow. -func leftShift(a *decimal, k uint) { - delta := leftcheats[k].delta - if prefixIsLessThan(a.d[0:a.nd], leftcheats[k].cutoff) { - delta-- - } - - r := a.nd // read index - w := a.nd + delta // write index - - // Pick up a digit, put down a digit. - var n uint - for r--; r >= 0; r-- { - n += (uint(a.d[r]) - '0') << k - quo := n / 10 - rem := n - 10*quo - w-- - if w < len(a.d) { - a.d[w] = byte(rem + '0') - } else if rem != 0 { - a.trunc = true - } - n = quo - } - - // Put down extra digits. - for n > 0 { - quo := n / 10 - rem := n - 10*quo - w-- - if w < len(a.d) { - a.d[w] = byte(rem + '0') - } else if rem != 0 { - a.trunc = true - } - n = quo - } - - a.nd += delta - if a.nd >= len(a.d) { - a.nd = len(a.d) - } - a.dp += delta - trim(a) -} - -// Binary shift left (k > 0) or right (k < 0). -func (a *decimal) Shift(k int) { - switch { - case a.nd == 0: - // nothing to do: a == 0 - case k > 0: - for k > maxShift { - leftShift(a, maxShift) - k -= maxShift - } - leftShift(a, uint(k)) - case k < 0: - for k < -maxShift { - rightShift(a, maxShift) - k += maxShift - } - rightShift(a, uint(-k)) - } -} - -// If we chop a at nd digits, should we round up? -func shouldRoundUp(a *decimal, nd int) bool { - if nd < 0 || nd >= a.nd { - return false - } - if a.d[nd] == '5' && nd+1 == a.nd { // exactly halfway - round to even - // if we truncated, a little higher than what's recorded - always round up - if a.trunc { - return true - } - return nd > 0 && (a.d[nd-1]-'0')%2 != 0 - } - // not halfway - digit tells all - return a.d[nd] >= '5' -} - -// Round a to nd digits (or fewer). -// If nd is zero, it means we're rounding -// just to the left of the digits, as in -// 0.09 -> 0.1. -func (a *decimal) Round(nd int) { - if nd < 0 || nd >= a.nd { - return - } - if shouldRoundUp(a, nd) { - a.RoundUp(nd) - } else { - a.RoundDown(nd) - } -} - -// Round a down to nd digits (or fewer). -func (a *decimal) RoundDown(nd int) { - if nd < 0 || nd >= a.nd { - return - } - a.nd = nd - trim(a) -} - -// Round a up to nd digits (or fewer). -func (a *decimal) RoundUp(nd int) { - if nd < 0 || nd >= a.nd { - return - } - - // round up - for i := nd - 1; i >= 0; i-- { - c := a.d[i] - if c < '9' { // can stop after this digit - a.d[i]++ - a.nd = i + 1 - return - } - } - - // Number is all 9s. - // Change to single 1 with adjusted decimal point. - a.d[0] = '1' - a.nd = 1 - a.dp++ -} - -// Extract integer part, rounded appropriately. -// No guarantees about overflow. -func (a *decimal) RoundedInteger() uint64 { - if a.dp > 20 { - return 0xFFFFFFFFFFFFFFFF - } - var i int - n := uint64(0) - for i = 0; i < a.dp && i < a.nd; i++ { - n = n*10 + uint64(a.d[i]-'0') - } - for ; i < a.dp; i++ { - n *= 10 - } - if shouldRoundUp(a, a.dp) { - n++ - } - return n -} diff --git a/decimal.go b/decimal.go index a37a230..90a17e3 100644 --- a/decimal.go +++ b/decimal.go @@ -17,18 +17,21 @@ package decimal import ( - "database/sql/driver" - "encoding/binary" "fmt" + "io" "math" "math/big" + "math/bits" "regexp" "strconv" "strings" ) // DivisionPrecision is the number of decimal places in the result when it -// doesn't divide exactly. +// doesn't divide exactly. Div and Sqrt read it, and so do Avg, Atan and Tan through Div. +// +// Set it once at program start. Changing it while other goroutines use the package is a data race. +// To choose the precision per call, use DivRound instead of Div and SqrtRound instead of Sqrt. // // Example: // @@ -43,9 +46,13 @@ import ( // d4.String() // output: "0.667" var DivisionPrecision = 16 -// PowPrecisionNegativeExponent specifies the maximum precision of the result (digits after decimal point) -// when calculating decimal power. Only used for cases where the exponent is a negative number. -// This constant applies to Pow, PowInt32 and PowBigInt methods, PowWithPrecision method is not constrained by it. +// PowPrecisionNegativeExponent specifies the number of digits after the decimal point in the results of +// Pow, PowInt32 and PowBigInt for negative or non-integer exponents. A result that would round to 0 keeps +// PowPrecisionNegativeExponent significant digits instead (at least one). +// PowWithPrecision is not affected, it takes the precision as an argument. +// +// Set it once at program start. Changing it while other goroutines use the package is a data race. +// To choose the precision per call, use PowWithPrecision. // // Example: // @@ -63,25 +70,54 @@ var PowPrecisionNegativeExponent = 16 // unmarshallers (ex: Javascript's) will unmarshal JSON numbers to IEEE 754 // double-precision floating point numbers, which means you can potentially // silently lose precision. +// +// Set it once at program start. Changing it while other goroutines use the package is a data race. var MarshalJSONWithoutQuotes = false +// TrimTrailingZeros specifies whether trailing zeroes should be trimmed from a string representation of decimal. +// If set to true, trailing zeroes will be truncated (2.00 -> 2, 3.11 -> 3.11, 13.000 -> 13), +// otherwise trailing zeroes will be preserved (2.00 -> 2.00, 3.11 -> 3.11, 13.000 -> 13.000). +// Setting this value to false can be useful for APIs where exact decimal string representation matters. +// It affects String and everything built on it, such as MarshalJSON, MarshalText and Value. +// +// Set it once at program start. Changing it while other goroutines use the package is a data race. +// For a fixed number of decimal places per call, use StringFixed. +var TrimTrailingZeros = true + +// UseScientificNotation specifies whether scientific notation should be used when a decimal is turned +// into a string that has a "negative" precision. +// +// For example, 1200 rounded to the nearest 100 cannot accurately be shown as "1200" because the last two +// digits are unknown. With this set to true, that number would be expressed as "1.2E3" instead. +// +// Set it once at program start. Changing it while other goroutines use the package is a data race. +// StringFixed never uses scientific notation. +var UseScientificNotation = false + // ExpMaxIterations specifies the maximum number of iterations needed to calculate // precise natural exponent value using ExpHullAbrham method. +// +// Set it once at program start. Changing it while other goroutines use the package is a data race. var ExpMaxIterations = 1000 +// MaxDecodeExponent is the largest exponent magnitude that NewFromString and UnmarshalBinary accept, +// and so UnmarshalJSON, UnmarshalText, Scan, GobDecode, DecodeSpanner, UnmarshalBSONValue and their +// NullDecimal variants too. +// Operations like String, Add and Float64 take time and memory that grow with the exponent, so without +// this limit a short input like "1e-2000000000" can stall the process or exhaust its memory. +// The default covers the float64 and IEEE 754 decimal128 exponent ranges. +// +// Values created with New, NewFromBigInt or arithmetic are not checked, so they may not decode back from +// their own String or MarshalBinary output. The limit does not bound the cost of Pow or ExpTaylor +// arguments or of very long inputs; validate those separately. +// +// Set it once at program start, before any decoding. Changing it while other goroutines decode is a +// data race. Raise it only for trusted input. +var MaxDecodeExponent = 10000 + // Zero constant, to make computations faster. // Zero should never be compared with == or != directly, please use decimal.Equal or decimal.Cmp instead. -var Zero = New(0, 1) - -var zeroInt = big.NewInt(0) -var oneInt = big.NewInt(1) -var twoInt = big.NewInt(2) -var fourInt = big.NewInt(4) -var fiveInt = big.NewInt(5) -var tenInt = big.NewInt(10) -var twentyInt = big.NewInt(20) - -var factorials = []Decimal{New(1, 0)} +var Zero = Decimal{} // Decimal represents a fixed-point decimal. It is immutable. // number = value * 10 ^ exp @@ -96,6 +132,13 @@ type Decimal struct { exp int32 } +func (d Decimal) getValue() *big.Int { + if d.value == nil { + return zeroInt + } + return d.value +} + // New returns a new fixed-point decimal, value * 10 ^ exp. func New(value int64, exp int32) Decimal { return Decimal{ @@ -169,6 +212,28 @@ func NewFromBigRat(value *big.Rat, precision int32) Decimal { }, precision) } +// NewFromBigFloat returns a new Decimal from a big.Float. The result is the shortest decimal +// that rounds back to value at value's precision, as NewFromFloat does for a float64. +// It returns an error for nil and infinite values, and for exponents beyond MaxDecodeExponent. +// +// Example: +// +// f, _ := new(big.Float).SetString("-123.45") +// d, err := NewFromBigFloat(f) +// d.String() // output: "-123.45" +func NewFromBigFloat(value *big.Float) (Decimal, error) { + if value == nil { + return Decimal{}, fmt.Errorf("can't convert nil *big.Float to decimal") + } + if value.IsInf() { + return Decimal{}, fmt.Errorf("can't convert infinite *big.Float to decimal") + } + if value.Sign() == 0 { + return New(0, 0), nil + } + return NewFromString(value.Text('g', -1)) +} + // NewFromString returns a new Decimal from a string representation. // Trailing zeroes are not trimmed. // @@ -179,11 +244,31 @@ func NewFromBigRat(value *big.Rat, precision int32) Decimal { // d3, err := NewFromString("1.47000") func NewFromString(value string) (Decimal, error) { originalInput := value - var intString string var exp int64 - // Check if number is using scientific notation - eIndex := strings.IndexAny(value, "Ee") + // Accept Unicode minus sign (U+2212) in addition to ASCII '-'. + value = strings.Replace(value, "\u2212", "-", -1) + + // Check if number is using scientific notation and find dots + eIndex := -1 + pIndex := -1 + for i, r := range value { + if r == 'E' || r == 'e' { + if eIndex > -1 { + return Decimal{}, fmt.Errorf("can't convert %s to decimal: multiple 'E' characters found", value) + } + eIndex = i + continue + } + + if r == '.' { + if pIndex > -1 { + return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value) + } + pIndex = i + } + } + if eIndex != -1 { expInt, err := strconv.ParseInt(value[eIndex+1:], 10, 32) if err != nil { @@ -196,40 +281,30 @@ func NewFromString(value string) (Decimal, error) { exp = expInt } - pIndex := -1 - vLen := len(value) - for i := 0; i < vLen; i++ { - if value[i] == '.' { - if pIndex > -1 { - return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value) - } - pIndex = i - } - } - - if pIndex == -1 { - // There is no decimal point, we can just parse the original string as - // an int - intString = value - } else { - if pIndex+1 < vLen { - intString = value[:pIndex] + value[pIndex+1:] - } else { - intString = value[:pIndex] + numLen := len(value) + if pIndex != -1 { + if pIndex+1 < len(value) && (value[pIndex+1] == '-' || value[pIndex+1] == '+') { + // ParseInt and SetString would accept the sign once the point is removed, as in ".-5" + return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) } + numLen-- expInt := -len(value[pIndex+1:]) exp += int64(expInt) } var dValue *big.Int - // strconv.ParseInt is faster than new(big.Int).SetString so this is just a shortcut for strings we know won't overflow - if len(intString) <= 18 { - parsed64, err := strconv.ParseInt(intString, 10, 64) - if err != nil { + // parsing in an int64 is faster than new(big.Int).SetString so this is just a shortcut for strings we know won't overflow + if numLen <= 18 { + parsed64, ok := parseInt64SkipIndex(value, pIndex) + if !ok { return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) } dValue = big.NewInt(parsed64) } else { + intString := value + if pIndex != -1 { + intString = value[:pIndex] + value[pIndex+1:] + } dValue = new(big.Int) _, ok := dValue.SetString(intString, 10) if !ok { @@ -241,6 +316,9 @@ func NewFromString(value string) (Decimal, error) { // NOTE(vadim): I doubt a string could realistically be this long return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", originalInput) } + if exp > int64(MaxDecodeExponent) || exp < -int64(MaxDecodeExponent) { + return Decimal{}, fmt.Errorf("can't convert %s to decimal: exponent %d exceeds MaxDecodeExponent (%d)", originalInput, exp, MaxDecodeExponent) + } return Decimal{ value: dValue, @@ -248,6 +326,32 @@ func NewFromString(value string) (Decimal, error) { }, nil } +// parseInt64SkipIndex parses s without the byte at index skip as strconv.ParseInt(s, 10, 64) would, +// s must have at most 18 other bytes so that the result can't overflow. +func parseInt64SkipIndex(s string, skip int) (int64, bool) { + i, neg := 0, false + if len(s) > 0 && skip != 0 && (s[0] == '+' || s[0] == '-') { + i, neg = 1, s[0] == '-' + } + var n int64 + digits := 0 + for ; i < len(s); i++ { + if i == skip { + continue + } + c := s[i] + if c < '0' || c > '9' { + return 0, false + } + n = n*10 + int64(c-'0') + digits++ + } + if neg { + n = -n + } + return n, digits > 0 +} + // NewFromFormattedString returns a new Decimal from a formatted string representation. // The second argument - replRegexp, is a regular expression that is used to find characters that should be // removed from given decimal string representation. All matched characters will be replaced with an empty string. @@ -288,11 +392,16 @@ func RequireFromString(value string) Decimal { // NewFromFloat converts a float64 to Decimal. // -// The converted number will contain the number of significant digits that can be -// represented in a float with reliable roundtrip. +// The result is the shortest decimal that converts back to the same float64, +// the same number that strconv.FormatFloat(value, 'f', -1, 64) prints. // This is typically 15 digits, but may be more in some cases. // See https://www.exploringbinary.com/decimal-precision-of-binary-floating-point-numbers/ for more information. // +// This is not always the exact value stored in the float. Whole numbers larger +// than 2^53 can change: float64(1<<62) is exactly 4611686018427387904, but +// NewFromFloat returns 4611686018427388000. To convert whole numbers exactly, +// use NewFromFloatWithExponent(value, 0). +// // For slightly faster conversion, use NewFromFloatWithExponent where you can specify the precision in absolute terms. // // NOTE: this will panic on NaN, +/-inf @@ -300,16 +409,18 @@ func NewFromFloat(value float64) Decimal { if value == 0 { return New(0, 0) } - return newFromFloat(value, math.Float64bits(value), &float64info) + return newFromFloat(value, 64) } // NewFromFloat32 converts a float32 to Decimal. // -// The converted number will contain the number of significant digits that can be -// represented in a float with reliable roundtrip. +// The result is the shortest decimal that converts back to the same float32. // This is typically 6-8 digits depending on the input. // See https://www.exploringbinary.com/decimal-precision-of-binary-floating-point-numbers/ for more information. // +// As with NewFromFloat, whole numbers larger than 2^24 can change. To convert +// them exactly, use NewFromFloatWithExponent(float64(value), 0). +// // For slightly faster conversion, use NewFromFloatWithExponent where you can specify the precision in absolute terms. // // NOTE: this will panic on NaN, +/-inf @@ -317,55 +428,66 @@ func NewFromFloat32(value float32) Decimal { if value == 0 { return New(0, 0) } - // XOR is workaround for https://github.com/golang/go/issues/26285 - a := math.Float32bits(value) ^ 0x80808080 - return newFromFloat(float64(value), uint64(a)^0x80808080, &float32info) + return newFromFloat(float64(value), 32) } -func newFromFloat(val float64, bits uint64, flt *floatInfo) Decimal { +// newFromFloat converts val to a Decimal holding the shortest decimal digit +// string that round-trips back to val when parsed at the given bit size. +// +// strconv already computes exactly that string, so we ask it for the value in +// 'e' format with precision -1 and read the digits back out. The 'e' layout is +// [-]d[.ddd]e±dd, which is bounded (at most 24 bytes) and trivial to scan, +// unlike 'f' which can run to over 750 bytes for extreme exponents. +func newFromFloat(val float64, bitSize int) Decimal { if math.IsNaN(val) || math.IsInf(val, 0) { panic(fmt.Sprintf("Cannot create a Decimal from %v", val)) } - exp := int(bits>>flt.mantbits) & (1<>(flt.expbits+flt.mantbits) != 0 + // Split the significand from the exponent. b always contains an 'e'. + var i int + for b[i] != 'e' { + i++ + } + digits, expDigits := b[:i], b[i+1:] - roundShortest(&d, mant, exp, flt) - // If less than 19 digits, we can do calculation in an int64. - if d.nd < 19 { - tmp := int64(0) - m := int64(1) - for i := d.nd - 1; i >= 0; i-- { - tmp += m * int64(d.d[i]-'0') - m *= 10 + // The shortest round-trip form has at most 17 significant digits for a + // float64 and 9 for a float32, so the significand always fits in an int64. + var mant int64 + nd := 0 + for _, c := range digits { + if c == '.' { + continue } - if d.neg { - tmp *= -1 - } - return Decimal{value: big.NewInt(tmp), exp: int32(d.dp) - int32(d.nd)} + mant = mant*10 + int64(c-'0') + nd++ } - dValue := new(big.Int) - dValue, ok := dValue.SetString(string(d.d[:d.nd]), 10) - if ok { - return Decimal{value: dValue, exp: int32(d.dp) - int32(d.nd)} + if neg { + mant = -mant } - return NewFromFloatWithExponent(val, int32(d.dp)-int32(d.nd)) + esign := 1 + switch expDigits[0] { + case '-': + esign = -1 + expDigits = expDigits[1:] + case '+': + expDigits = expDigits[1:] + } + e := 0 + for _, c := range expDigits { + e = e*10 + int(c-'0') + } + + // digits is d.ddd, i.e. the significand scaled by 10^(nd-1). + return Decimal{value: big.NewInt(mant), exp: int32(esign*e - nd + 1)} } // NewFromFloatWithExponent converts a float64 to Decimal, with an arbitrary @@ -456,9 +578,8 @@ func NewFromFloatWithExponent(value float64, exp int32) Decimal { // Copy returns a copy of decimal with the same value and exponent, but a different pointer to value. func (d Decimal) Copy() Decimal { - d.ensureInitialized() return Decimal{ - value: new(big.Int).Set(d.value), + value: new(big.Int).Set(d.getValue()), exp: d.exp, } } @@ -470,37 +591,38 @@ func (d Decimal) Copy() Decimal { // // Example: // -// d := New(12345, -4) -// d2 := d.rescale(-1) +// d1 := New(12345, -4) +// d2 := d1.rescale(-1) // d3 := d2.rescale(-4) -// println(d1) -// println(d2) -// println(d3) +// fmt.Println(d1) +// fmt.Println(d2) +// fmt.Println(d3) // // Output: // // 1.2345 // 1.2 -// 1.2000 +// 1.2 func (d Decimal) rescale(exp int32) Decimal { - d.ensureInitialized() - if d.exp == exp { return Decimal{ - new(big.Int).Set(d.value), + new(big.Int).Set(d.getValue()), d.exp, } } // NOTE(vadim): must convert exps to float64 before - to prevent overflow diff := math.Abs(float64(exp) - float64(d.exp)) - value := new(big.Int).Set(d.value) - - expScale := new(big.Int).Exp(tenInt, big.NewInt(int64(diff)), nil) + value := new(big.Int) if exp > d.exp { - value = value.Quo(value, expScale) - } else if exp < d.exp { - value = value.Mul(value, expScale) + value.Quo(d.getValue(), pow10(int64(diff))) + if value.Sign() == 0 && d.getValue().Sign() != 0 { + // reflect.DeepEqual tells an empty word slice from nil, keep the empty one that + // dividing a copy of d.value in place used to leave + value.SetBits([]big.Word{}) + } + } else { + value.Mul(d.getValue(), pow10(int64(diff))) } return Decimal{ @@ -514,8 +636,7 @@ func (d Decimal) Abs() Decimal { if !d.IsNegative() { return d } - d.ensureInitialized() - d2Value := new(big.Int).Abs(d.value) + d2Value := new(big.Int).Abs(d.getValue()) return Decimal{ value: d2Value, exp: d.exp, @@ -524,30 +645,47 @@ func (d Decimal) Abs() Decimal { // Add returns d + d2. func (d Decimal) Add(d2 Decimal) Decimal { - rd, rd2 := RescalePair(d, d2) + // rescale returns a new value, so the sum can be stored in it + if d.exp < d2.exp { + r := d2.rescale(d.exp) + r.value.Add(d.getValue(), r.value) + return r + } else if d.exp > d2.exp { + r := d.rescale(d2.exp) + r.value.Add(r.value, d2.getValue()) + return r + } - d3Value := new(big.Int).Add(rd.value, rd2.value) + d3Value := new(big.Int).Add(d.getValue(), d2.getValue()) return Decimal{ value: d3Value, - exp: rd.exp, + exp: d.exp, } } // Sub returns d - d2. func (d Decimal) Sub(d2 Decimal) Decimal { - rd, rd2 := RescalePair(d, d2) + // rescale returns a new value, so the difference can be stored in it + if d.exp < d2.exp { + r := d2.rescale(d.exp) + r.value.Sub(d.getValue(), r.value) + return r + } else if d.exp > d2.exp { + r := d.rescale(d2.exp) + r.value.Sub(r.value, d2.getValue()) + return r + } - d3Value := new(big.Int).Sub(rd.value, rd2.value) + d3Value := new(big.Int).Sub(d.getValue(), d2.getValue()) return Decimal{ value: d3Value, - exp: rd.exp, + exp: d.exp, } } // Neg returns -d. func (d Decimal) Neg() Decimal { - d.ensureInitialized() - val := new(big.Int).Neg(d.value) + val := new(big.Int).Neg(d.getValue()) return Decimal{ value: val, exp: d.exp, @@ -556,9 +694,6 @@ func (d Decimal) Neg() Decimal { // Mul returns d * d2. func (d Decimal) Mul(d2 Decimal) Decimal { - d.ensureInitialized() - d2.ensureInitialized() - expInt64 := int64(d.exp) + int64(d2.exp) if expInt64 > math.MaxInt32 || expInt64 < math.MinInt32 { // NOTE(vadim): better to panic than give incorrect results, as @@ -566,7 +701,7 @@ func (d Decimal) Mul(d2 Decimal) Decimal { panic(fmt.Sprintf("exponent %v overflows an int32!", expInt64)) } - d3Value := new(big.Int).Mul(d.value, d2.value) + d3Value := new(big.Int).Mul(d.getValue(), d2.getValue()) return Decimal{ value: d3Value, exp: int32(expInt64), @@ -577,16 +712,67 @@ func (d Decimal) Mul(d2 Decimal) Decimal { // It shifts left when shift is positive and right if shift is negative. // In simpler terms, the given value for shift is added to the exponent // of the decimal. +// Shift panics if the resulting exponent does not fit in an int32. func (d Decimal) Shift(shift int32) Decimal { - d.ensureInitialized() + exp := int64(d.exp) + int64(shift) + if exp > math.MaxInt32 || exp < math.MinInt32 { + panic(fmt.Sprintf("exponent %v overflows an int32!", exp)) + } + value := d.getValue() + if value.Sign() == 0 { + // a copy of zero has a nil word slice, which reflect.DeepEqual tells from an empty one + value = zeroInt + } return Decimal{ - value: new(big.Int).Set(d.value), - exp: d.exp + shift, + value: value, + exp: int32(exp), } } +// Normalize returns d with the trailing zeros removed from its coefficient, so equal decimals get the +// same coefficient and exponent: 1.50, 1.5 and New(15, -1) all become New(15, -1), and every zero +// becomes Decimal{}, the same as Zero. It's like Java's BigDecimal.stripTrailingZeros. +// +// reflect.DeepEqual, and test helpers built on it such as testify's assert.Equal and gomock.Eq, compare +// the coefficient and exponent rather than the value, so normalize decimals before comparing them that +// way. Equal and Cmp compare values and don't need it. +// +// The value doesn't change, but its string form can: with TrimTrailingZeros = false, 1.50 prints as +// 1.5, and with UseScientificNotation = true, 1500 prints as 1.5E3. +func (d Decimal) Normalize() Decimal { + v := d.getValue() + if v.Sign() == 0 { + return Decimal{} + } + + // Count the trailing zeros with integer math: in v if it fits an int64, else in v mod 10^19, + // and in all the digits of v only when those 19 are all zeros. + var zeros int64 + if v.IsInt64() { + for x := v.Int64(); x%10 == 0; x /= 10 { + zeros++ + } + } else { + r := new(big.Int).Rem(v, pow10(19)) + for n := r.Abs(r).Uint64(); n%10 == 0 && zeros < 19; n /= 10 { + zeros++ + } + if zeros == 19 { + s := v.String() + zeros = int64(len(s) - len(strings.TrimRight(s, "0"))) + } + } + if room := math.MaxInt32 - int64(d.exp); zeros > room { + zeros = room + } + if zeros == 0 { + return d + } + return Decimal{value: new(big.Int).Quo(v, pow10(zeros)), exp: d.exp + int32(zeros)} +} + // Div returns d / d2. If it doesn't divide exactly, the result will have -// DivisionPrecision digits after the decimal point. +// DivisionPrecision digits after the decimal point. Use DivRound to choose the precision per call. func (d Decimal) Div(d2 Decimal) Decimal { return d.DivRound(d2, int32(DivisionPrecision)) } @@ -600,39 +786,41 @@ func (d Decimal) Div(d2 Decimal) Decimal { // // Note that precision<0 is allowed as input. func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) { - d.ensureInitialized() - d2.ensureInitialized() - if d2.value.Sign() == 0 { + return d.quoRem(d2, precision, nil) +} + +// quoRem is QuoRem that also sets bb, if not nil, to the divisor in the units of the remainder. +func (d Decimal) quoRem(d2 Decimal, precision int32, bb *big.Int) (Decimal, Decimal) { + if d2.getValue().Sign() == 0 { panic("decimal division by 0") } scale := -precision e := int64(d.exp) - int64(d2.exp) - int64(scale) - if e > math.MaxInt32 || e < math.MinInt32 { + if precision == math.MinInt32 || e > math.MaxInt32 || e < math.MinInt32 { panic("overflow in decimal QuoRem") } - var aa, bb, expo big.Int + if bb == nil { + bb = new(big.Int) + } + var aa big.Int var scalerest int32 // d = a 10^ea // d2 = b 10^eb if e < 0 { - aa = *d.value - expo.SetInt64(-e) - bb.Exp(tenInt, &expo, nil) - bb.Mul(d2.value, &bb) + aa = *d.getValue() + bb.Mul(d2.getValue(), pow10(-e)) scalerest = d.exp // now aa = a // bb = b 10^(scale + eb - ea) } else { - expo.SetInt64(e) - aa.Exp(tenInt, &expo, nil) - aa.Mul(d.value, &aa) - bb = *d2.value + aa.Mul(d.getValue(), pow10(e)) + *bb = *d2.getValue() scalerest = scale + d2.exp // now aa = a ^ (ea - eb - scale) // bb = b } var q, r big.Int - q.QuoRem(&aa, &bb, &r) + q.QuoRem(&aa, bb, &r) dq := Decimal{value: &q, exp: scale} dr := Decimal{value: &r, exp: scalerest} return dq, dr @@ -646,27 +834,24 @@ func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) { // // Note that precision<0 is allowed as input. func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal { - // QuoRem already checks initialization - q, r := d.QuoRem(d2, precision) - // the actual rounding decision is based on comparing r*10^precision and d2/2 - // instead compare 2 r 10 ^precision and d2 + // quoRem already checks initialization + var bb big.Int + q, r := d.quoRem(d2, precision, &bb) + // the actual rounding decision is based on comparing r*10^precision and d2/2, + // which is comparing 2*|r.value| and |bb| var rv2 big.Int - rv2.Abs(r.value) + rv2.Abs(r.getValue()) rv2.Lsh(&rv2, 1) - // now rv2 = abs(r.value) * 2 - r2 := Decimal{value: &rv2, exp: r.exp + precision} - // r2 is now 2 * r * 10 ^ precision - var c = r2.Cmp(d2.Abs()) - - if c < 0 { + if rv2.CmpAbs(&bb) < 0 { return q } - if d.value.Sign()*d2.value.Sign() < 0 { - return q.Sub(New(1, -precision)) + if d.getValue().Sign()*d2.getValue().Sign() < 0 { + q.value.Sub(q.value, oneInt) + } else { + q.value.Add(q.value, oneInt) } - - return q.Add(New(1, -precision)) + return q } // Mod returns d % d2. @@ -675,590 +860,30 @@ func (d Decimal) Mod(d2 Decimal) Decimal { return r } -// Pow returns d to the power of d2. -// When exponent is negative the returned decimal will have maximum precision of PowPrecisionNegativeExponent places after decimal point. -// -// Pow returns 0 (zero-value of Decimal) instead of error for power operation edge cases, to handle those edge cases use PowWithPrecision -// Edge cases not handled by Pow: -// - 0 ** 0 => undefined value -// - 0 ** y, where y < 0 => infinity -// - x ** y, where x < 0 and y is non-integer decimal => imaginary value -// -// Example: -// -// d1 := decimal.NewFromFloat(4.0) -// d2 := decimal.NewFromFloat(4.0) -// res1 := d1.Pow(d2) -// res1.String() // output: "256" -// -// d3 := decimal.NewFromFloat(5.0) -// d4 := decimal.NewFromFloat(5.73) -// res2 := d3.Pow(d4) -// res2.String() // output: "10118.08037125" -func (d Decimal) Pow(d2 Decimal) Decimal { - baseSign := d.Sign() - expSign := d2.Sign() - - if baseSign == 0 { - if expSign == 0 { - return Decimal{} - } - if expSign == 1 { - return Decimal{zeroInt, 0} - } - if expSign == -1 { - return Decimal{} - } - } - - if expSign == 0 { - return Decimal{oneInt, 0} - } - - // TODO: optimize extraction of fractional part - one := Decimal{oneInt, 0} - expIntPart, expFracPart := d2.QuoRem(one, 0) - - if baseSign == -1 && !expFracPart.IsZero() { - return Decimal{} - } - - intPartPow, _ := d.PowBigInt(expIntPart.value) - - // if exponent is an integer we don't need to calculate d1**frac(d2) - if expFracPart.value.Sign() == 0 { - return intPartPow - } - - // TODO: optimize NumDigits for more performant precision adjustment - digitsBase := d.NumDigits() - digitsExponent := d2.NumDigits() - - precision := digitsBase - - if digitsExponent > precision { - precision += digitsExponent - } - - precision += 6 - - // Calculate x ** frac(y), where - // x ** frac(y) = exp(ln(x ** frac(y)) = exp(ln(x) * frac(y)) - fracPartPow, err := d.Abs().Ln(-d.exp + int32(precision)) - if err != nil { - return Decimal{} - } - - fracPartPow = fracPartPow.Mul(expFracPart) - - fracPartPow, err = fracPartPow.ExpTaylor(-d.exp + int32(precision)) - if err != nil { - return Decimal{} - } - - // Join integer and fractional part, - // base ** (expBase + expFrac) = base ** expBase * base ** expFrac - res := intPartPow.Mul(fracPartPow) - - return res -} - -// PowWithPrecision returns d to the power of d2. -// Precision parameter specifies minimum precision of the result (digits after decimal point). -// Returned decimal is not rounded to 'precision' places after decimal point. -// -// PowWithPrecision returns error when: -// - 0 ** 0 => undefined value -// - 0 ** y, where y < 0 => infinity -// - x ** y, where x < 0 and y is non-integer decimal => imaginary value -// -// Example: -// -// d1 := decimal.NewFromFloat(4.0) -// d2 := decimal.NewFromFloat(4.0) -// res1, err := d1.PowWithPrecision(d2, 2) -// res1.String() // output: "256" -// -// d3 := decimal.NewFromFloat(5.0) -// d4 := decimal.NewFromFloat(5.73) -// res2, err := d3.PowWithPrecision(d4, 5) -// res2.String() // output: "10118.080371595015625" -// -// d5 := decimal.NewFromFloat(-3.0) -// d6 := decimal.NewFromFloat(-6.0) -// res3, err := d5.PowWithPrecision(d6, 10) -// res3.String() // output: "0.0013717421" -func (d Decimal) PowWithPrecision(d2 Decimal, precision int32) (Decimal, error) { - baseSign := d.Sign() - expSign := d2.Sign() - - if baseSign == 0 { - if expSign == 0 { - return Decimal{}, fmt.Errorf("cannot represent undefined value of 0**0") - } - if expSign == 1 { - return Decimal{zeroInt, 0}, nil - } - if expSign == -1 { - return Decimal{}, fmt.Errorf("cannot represent infinity value of 0 ** y, where y < 0") - } - } - - if expSign == 0 { - return Decimal{oneInt, 0}, nil - } - - // TODO: optimize extraction of fractional part - one := Decimal{oneInt, 0} - expIntPart, expFracPart := d2.QuoRem(one, 0) - - if baseSign == -1 && !expFracPart.IsZero() { - return Decimal{}, fmt.Errorf("cannot represent imaginary value of x ** y, where x < 0 and y is non-integer decimal") - } - - intPartPow, _ := d.powBigIntWithPrecision(expIntPart.value, precision) - - // if exponent is an integer we don't need to calculate d1**frac(d2) - if expFracPart.value.Sign() == 0 { - return intPartPow, nil - } - - // TODO: optimize NumDigits for more performant precision adjustment - digitsBase := d.NumDigits() - digitsExponent := d2.NumDigits() - - if int32(digitsBase) > precision { - precision = int32(digitsBase) - } - if int32(digitsExponent) > precision { - precision += int32(digitsExponent) - } - // increase precision by 10 to compensate for errors in further calculations - precision += 10 - - // Calculate x ** frac(y), where - // x ** frac(y) = exp(ln(x ** frac(y)) = exp(ln(x) * frac(y)) - fracPartPow, err := d.Abs().Ln(precision) - if err != nil { - return Decimal{}, err - } - - fracPartPow = fracPartPow.Mul(expFracPart) - - fracPartPow, err = fracPartPow.ExpTaylor(precision) - if err != nil { - return Decimal{}, err - } - - // Join integer and fractional part, - // base ** (expBase + expFrac) = base ** expBase * base ** expFrac - res := intPartPow.Mul(fracPartPow) - - return res, nil -} - -// PowInt32 returns d to the power of exp, where exp is int32. -// Only returns error when d and exp is 0, thus result is undefined. -// -// When exponent is negative the returned decimal will have maximum precision of PowPrecisionNegativeExponent places after decimal point. -// -// Example: -// -// d1, err := decimal.NewFromFloat(4.0).PowInt32(4) -// d1.String() // output: "256" -// -// d2, err := decimal.NewFromFloat(3.13).PowInt32(5) -// d2.String() // output: "300.4150512793" -func (d Decimal) PowInt32(exp int32) (Decimal, error) { - if d.IsZero() && exp == 0 { - return Decimal{}, fmt.Errorf("cannot represent undefined value of 0**0") - } - - isExpNeg := exp < 0 - exp = abs(exp) - - n, result := d, New(1, 0) - - for exp > 0 { - if exp%2 == 1 { - result = result.Mul(n) - } - exp /= 2 - - if exp > 0 { - n = n.Mul(n) - } - } - - if isExpNeg { - return New(1, 0).DivRound(result, int32(PowPrecisionNegativeExponent)), nil - } - - return result, nil -} - -// PowBigInt returns d to the power of exp, where exp is big.Int. -// Only returns error when d and exp is 0, thus result is undefined. -// -// When exponent is negative the returned decimal will have maximum precision of PowPrecisionNegativeExponent places after decimal point. -// -// Example: -// -// d1, err := decimal.NewFromFloat(3.0).PowBigInt(big.NewInt(3)) -// d1.String() // output: "27" -// -// d2, err := decimal.NewFromFloat(629.25).PowBigInt(big.NewInt(5)) -// d2.String() // output: "98654323103449.5673828125" -func (d Decimal) PowBigInt(exp *big.Int) (Decimal, error) { - return d.powBigIntWithPrecision(exp, int32(PowPrecisionNegativeExponent)) -} - -func (d Decimal) powBigIntWithPrecision(exp *big.Int, precision int32) (Decimal, error) { - if d.IsZero() && exp.Sign() == 0 { - return Decimal{}, fmt.Errorf("cannot represent undefined value of 0**0") - } - - tmpExp := new(big.Int).Set(exp) - isExpNeg := exp.Sign() < 0 - - if isExpNeg { - tmpExp.Abs(tmpExp) - } - - n, result := d, New(1, 0) - - for tmpExp.Sign() > 0 { - if tmpExp.Bit(0) == 1 { - result = result.Mul(n) - } - tmpExp.Rsh(tmpExp, 1) - - if tmpExp.Sign() > 0 { - n = n.Mul(n) - } - } - - if isExpNeg { - return New(1, 0).DivRound(result, precision), nil - } - - return result, nil -} - -// ExpHullAbrham calculates the natural exponent of decimal (e to the power of d) using Hull-Abraham algorithm. -// OverallPrecision argument specifies the overall precision of the result (integer part + decimal part). -// -// ExpHullAbrham is faster than ExpTaylor for small precision values, but it is much slower for large precision values. -// -// Example: -// -// NewFromFloat(26.1).ExpHullAbrham(2).String() // output: "220000000000" -// NewFromFloat(26.1).ExpHullAbrham(20).String() // output: "216314672147.05767284" -func (d Decimal) ExpHullAbrham(overallPrecision uint32) (Decimal, error) { - // Algorithm based on Variable precision exponential function. - // ACM Transactions on Mathematical Software by T. E. Hull & A. Abrham. - if d.IsZero() { - return Decimal{oneInt, 0}, nil - } - - currentPrecision := overallPrecision - - // Algorithm does not work if currentPrecision * 23 < |x|. - // Precision is automatically increased in such cases, so the value can be calculated precisely. - // If newly calculated precision is higher than ExpMaxIterations the currentPrecision will not be changed. - f := d.Abs().InexactFloat64() - if ncp := f / 23; ncp > float64(currentPrecision) && ncp < float64(ExpMaxIterations) { - currentPrecision = uint32(math.Ceil(ncp)) - } - - // fail if abs(d) beyond an over/underflow threshold - overflowThreshold := New(23*int64(currentPrecision), 0) - if d.Abs().Cmp(overflowThreshold) > 0 { - return Decimal{}, fmt.Errorf("over/underflow threshold, exp(x) cannot be calculated precisely") - } - - // Return 1 if abs(d) small enough; this also avoids later over/underflow - overflowThreshold2 := New(9, -int32(currentPrecision)-1) - if d.Abs().Cmp(overflowThreshold2) <= 0 { - return Decimal{oneInt, d.exp}, nil - } - - // t is the smallest integer >= 0 such that the corresponding abs(d/k) < 1 - t := d.exp + int32(d.NumDigits()) // Add d.NumDigits because the paper assumes that d.value [0.1, 1) - - if t < 0 { - t = 0 - } - - k := New(1, t) // reduction factor - r := Decimal{new(big.Int).Set(d.value), d.exp - t} // reduced argument - p := int32(currentPrecision) + t + 2 // precision for calculating the sum - - // Determine n, the number of therms for calculating sum - // use first Newton step (1.435p - 1.182) / log10(p/abs(r)) - // for solving appropriate equation, along with directed - // roundings and simple rational bound for log10(p/abs(r)) - rf := r.Abs().InexactFloat64() - pf := float64(p) - nf := math.Ceil((1.453*pf - 1.182) / math.Log10(pf/rf)) - if nf > float64(ExpMaxIterations) || math.IsNaN(nf) { - return Decimal{}, fmt.Errorf("exact value cannot be calculated in <=ExpMaxIterations iterations") - } - n := int64(nf) - - tmp := New(0, 0) - sum := New(1, 0) - one := New(1, 0) - for i := n - 1; i > 0; i-- { - tmp.value.SetInt64(i) - sum = sum.Mul(r.DivRound(tmp, p)) - sum = sum.Add(one) - } - - ki := k.IntPart() - res := New(1, 0) - for i := ki; i > 0; i-- { - res = res.Mul(sum) - } - - resNumDigits := int32(res.NumDigits()) - - var roundDigits int32 - if resNumDigits > abs(res.exp) { - roundDigits = int32(currentPrecision) - resNumDigits - res.exp - } else { - roundDigits = int32(currentPrecision) - } - - res = res.Round(roundDigits) - - return res, nil -} - -// ExpTaylor calculates the natural exponent of decimal (e to the power of d) using Taylor series expansion. -// Precision argument specifies how precise the result must be (number of digits after decimal point). -// Negative precision is allowed. -// -// ExpTaylor is much faster for large precision values than ExpHullAbrham. -// -// Example: -// -// d, err := NewFromFloat(26.1).ExpTaylor(2).String() -// d.String() // output: "216314672147.06" -// -// NewFromFloat(26.1).ExpTaylor(20).String() -// d.String() // output: "216314672147.05767284062928674083" -// -// NewFromFloat(26.1).ExpTaylor(-10).String() -// d.String() // output: "220000000000" -func (d Decimal) ExpTaylor(precision int32) (Decimal, error) { - // Note(mwoss): Implementation can be optimized by exclusively using big.Int API only - if d.IsZero() { - return Decimal{oneInt, 0}.Round(precision), nil - } - - var epsilon Decimal - var divPrecision int32 - if precision < 0 { - epsilon = New(1, -1) - divPrecision = 8 - } else { - epsilon = New(1, -precision-1) - divPrecision = precision + 1 - } - - decAbs := d.Abs() - pow := d.Abs() - factorial := New(1, 0) - - result := New(1, 0) - - for i := int64(1); ; { - step := pow.DivRound(factorial, divPrecision) - result = result.Add(step) - - // Stop Taylor series when current step is smaller than epsilon - if step.Cmp(epsilon) < 0 { - break - } - - pow = pow.Mul(decAbs) - - i++ - - // Calculate next factorial number or retrieve cached value - if len(factorials) >= int(i) && !factorials[i-1].IsZero() { - factorial = factorials[i-1] - } else { - // To avoid any race conditions, firstly the zero value is appended to a slice to create - // a spot for newly calculated factorial. After that, the zero value is replaced by calculated - // factorial using the index notation. - factorial = factorials[i-2].Mul(New(i, 0)) - factorials = append(factorials, Zero) - factorials[i-1] = factorial - } - } - - if d.Sign() < 0 { - result = New(1, 0).DivRound(result, precision+1) - } - - result = result.Round(precision) - return result, nil -} - -// Ln calculates natural logarithm of d. -// Precision argument specifies how precise the result must be (number of digits after decimal point). -// Negative precision is allowed. -// -// Example: -// -// d1, err := NewFromFloat(13.3).Ln(2) -// d1.String() // output: "2.59" -// -// d2, err := NewFromFloat(579.161).Ln(10) -// d2.String() // output: "6.3615805046" -func (d Decimal) Ln(precision int32) (Decimal, error) { - // Algorithm based on The Use of Iteration Methods for Approximating the Natural Logarithm, - // James F. Epperson, The American Mathematical Monthly, Vol. 96, No. 9, November 1989, pp. 831-835. - if d.IsNegative() { - return Decimal{}, fmt.Errorf("cannot calculate natural logarithm for negative decimals") - } - - if d.IsZero() { - return Decimal{}, fmt.Errorf("cannot represent natural logarithm of 0, result: -infinity") - } - - calcPrecision := precision + 2 - z := d.Copy() - - var comp1, comp3, comp2, comp4, reduceAdjust Decimal - comp1 = z.Sub(Decimal{oneInt, 0}) - comp3 = Decimal{oneInt, -1} - - // for decimal in range [0.9, 1.1] where ln(d) is close to 0 - usePowerSeries := false - - if comp1.Abs().Cmp(comp3) <= 0 { - usePowerSeries = true - } else { - // reduce input decimal to range [0.1, 1) - expDelta := int32(z.NumDigits()) + z.exp - z.exp -= expDelta - - // Input decimal was reduced by factor of 10^expDelta, thus we will need to add - // ln(10^expDelta) = expDelta * ln(10) - // to the result to compensate that - ln10 := ln10.withPrecision(calcPrecision) - reduceAdjust = NewFromInt32(expDelta) - reduceAdjust = reduceAdjust.Mul(ln10) - - comp1 = z.Sub(Decimal{oneInt, 0}) - - if comp1.Abs().Cmp(comp3) <= 0 { - usePowerSeries = true - } else { - // initial estimate using floats - zFloat := z.InexactFloat64() - comp1 = NewFromFloat(math.Log(zFloat)) - } - } - - epsilon := Decimal{oneInt, -calcPrecision} - - if usePowerSeries { - // Power Series - https://en.wikipedia.org/wiki/Logarithm#Power_series - // Calculating n-th term of formula: ln(z+1) = 2 sum [ 1 / (2n+1) * (z / (z+2))^(2n+1) ] - // until the difference between current and next term is smaller than epsilon. - // Coverage quite fast for decimals close to 1.0 - - // z + 2 - comp2 = comp1.Add(Decimal{twoInt, 0}) - // z / (z + 2) - comp3 = comp1.DivRound(comp2, calcPrecision) - // 2 * (z / (z + 2)) - comp1 = comp3.Add(comp3) - comp2 = comp1.Copy() - - for n := 1; ; n++ { - // 2 * (z / (z+2))^(2n+1) - comp2 = comp2.Mul(comp3).Mul(comp3) - - // 1 / (2n+1) * 2 * (z / (z+2))^(2n+1) - comp4 = NewFromInt(int64(2*n + 1)) - comp4 = comp2.DivRound(comp4, calcPrecision) - - // comp1 = 2 sum [ 1 / (2n+1) * (z / (z+2))^(2n+1) ] - comp1 = comp1.Add(comp4) - - if comp4.Abs().Cmp(epsilon) <= 0 { - break - } - } - } else { - // Halley's Iteration. - // Calculating n-th term of formula: a_(n+1) = a_n - 2 * (exp(a_n) - z) / (exp(a_n) + z), - // until the difference between current and next term is smaller than epsilon - var prevStep Decimal - maxIters := calcPrecision*2 + 10 - - for i := int32(0); i < maxIters; i++ { - // exp(a_n) - comp3, _ = comp1.ExpTaylor(calcPrecision) - // exp(a_n) - z - comp2 = comp3.Sub(z) - // 2 * (exp(a_n) - z) - comp2 = comp2.Add(comp2) - // exp(a_n) + z - comp4 = comp3.Add(z) - // 2 * (exp(a_n) - z) / (exp(a_n) + z) - comp3 = comp2.DivRound(comp4, calcPrecision) - // comp1 = a_(n+1) = a_n - 2 * (exp(a_n) - z) / (exp(a_n) + z) - comp1 = comp1.Sub(comp3) - - if prevStep.Add(comp3).IsZero() { - // If iteration steps oscillate we should return early and prevent an infinity loop - // NOTE(mwoss): This should be quite a rare case, returning error is not necessary - break - } - - if comp3.Abs().Cmp(epsilon) <= 0 { - break - } - - prevStep = comp3 - } - } - - comp1 = comp1.Add(reduceAdjust) - - return comp1.Round(precision), nil -} - // NumDigits returns the number of digits of the decimal coefficient (d.Value) func (d Decimal) NumDigits() int { - if d.value == nil { - return 1 - } - - if d.value.IsInt64() { - i64 := d.value.Int64() - // restrict fast path to integers with exact conversion to float64 - if i64 <= (1<<53) && i64 >= -(1<<53) { - if i64 == 0 { - return 1 - } - return int(math.Log10(math.Abs(float64(i64)))) + 1 + v := d.getValue() + if v.IsInt64() { + u := uint64(v.Int64()) + if u == 0 { + return 1 } + if v.Sign() < 0 { + u = -u + } + // Not math.Log10, which rounds an exact 1eN like 1e15 down (see #420). + // bits.Len64(u)*1233>>12 is floor(log10(2^bits)), the digit count or one less. + n := bits.Len64(u) * 1233 >> 12 + if u >= pow10Uint64[n] { + n++ + } + return n } - estimatedNumDigits := int(float64(d.value.BitLen()) / math.Log2(10)) + estimatedNumDigits := int(float64(v.BitLen()) / math.Log2(10)) // estimatedNumDigits (lg10) may be off by 1, need to verify - digitsBigInt := big.NewInt(int64(estimatedNumDigits)) - errorCorrectionUnit := digitsBigInt.Exp(tenInt, digitsBigInt, nil) - - if d.value.CmpAbs(errorCorrectionUnit) >= 0 { + if v.CmpAbs(pow10(int64(estimatedNumDigits))) >= 0 { return estimatedNumDigits + 1 } @@ -1271,13 +896,20 @@ func (d Decimal) IsInteger() bool { if d.exp >= 0 { return true } + v := d.getValue() + if v.Sign() == 0 { + return true + } + if d.exp >= -18 && v.IsInt64() { + return v.Int64()%int64(pow10Uint64[-d.exp]) == 0 + } // When the exponent is negative we have to check every number after the decimal place // If all of them are zeroes, we are sure that given decimal can be represented as an integer var r big.Int - q := new(big.Int).Set(d.value) + q := new(big.Int).Set(v) for z := abs(d.exp); z > 0; z-- { q.QuoRem(q, tenInt, &r) - if r.Cmp(zeroInt) != 0 { + if r.Sign() != 0 { return false } } @@ -1298,16 +930,23 @@ func abs(n int32) int32 { // 0 if d == d2 // +1 if d > d2 func (d Decimal) Cmp(d2 Decimal) int { - d.ensureInitialized() - d2.ensureInitialized() - if d.exp == d2.exp { - return d.value.Cmp(d2.value) + return d.getValue().Cmp(d2.getValue()) + } + if s, s2 := d.Sign(), d2.Sign(); s != s2 { + if s > s2 { + return 1 + } + return -1 + } else if s == 0 { + return 0 } - rd, rd2 := RescalePair(d, d2) - - return rd.value.Cmp(rd2.value) + var scaled big.Int + if d.exp < d2.exp { + return d.getValue().Cmp(scaled.Mul(d2.getValue(), pow10(int64(d2.exp)-int64(d.exp)))) + } + return scaled.Mul(d.getValue(), pow10(int64(d.exp)-int64(d2.exp))).Cmp(d2.getValue()) } // Compare compares the numbers represented by d and d2 and returns: @@ -1324,6 +963,8 @@ func (d Decimal) Equal(d2 Decimal) bool { return d.Cmp(d2) == 0 } +// Equals returns whether the numbers represented by d and d2 are equal. +// // Deprecated: Equals is deprecated, please use Equal method instead. func (d Decimal) Equals(d2 Decimal) bool { return d.Equal(d2) @@ -1357,10 +998,7 @@ func (d Decimal) LessThanOrEqual(d2 Decimal) bool { // 0 if d == 0 // +1 if d > 0 func (d Decimal) Sign() int { - if d.value == nil { - return 0 - } - return d.value.Sign() + return d.getValue().Sign() } // IsPositive return @@ -1397,28 +1035,29 @@ func (d Decimal) Exponent() int32 { // Coefficient returns the coefficient of the decimal. It is scaled by 10^Exponent() func (d Decimal) Coefficient() *big.Int { - d.ensureInitialized() // we copy the coefficient so that mutating the result does not mutate the Decimal. - return new(big.Int).Set(d.value) + return new(big.Int).Set(d.getValue()) } // CoefficientInt64 returns the coefficient of the decimal as int64. It is scaled by 10^Exponent() // If coefficient cannot be represented in an int64, the result will be undefined. func (d Decimal) CoefficientInt64() int64 { - d.ensureInitialized() - return d.value.Int64() + return d.getValue().Int64() } // IntPart returns the integer component of the decimal. func (d Decimal) IntPart() int64 { + if v := d.getValue(); d.exp <= 0 && d.exp >= -18 && v.IsInt64() { + return v.Int64() / int64(pow10Uint64[-d.exp]) + } scaledD := d.rescale(0) - return scaledD.value.Int64() + return scaledD.getValue().Int64() } // BigInt returns integer component of the decimal as a BigInt. func (d Decimal) BigInt() *big.Int { scaledD := d.rescale(0) - return scaledD.value + return scaledD.getValue() } // BigFloat returns decimal as BigFloat. @@ -1431,15 +1070,12 @@ func (d Decimal) BigFloat() *big.Float { // Rat returns a rational number representation of the decimal. func (d Decimal) Rat() *big.Rat { - d.ensureInitialized() if d.exp <= 0 { // NOTE(vadim): must negate after casting to prevent int32 overflow - denom := new(big.Int).Exp(tenInt, big.NewInt(-int64(d.exp)), nil) - return new(big.Rat).SetFrac(d.value, denom) + return new(big.Rat).SetFrac(d.getValue(), pow10(-int64(d.exp))) } - mul := new(big.Int).Exp(tenInt, big.NewInt(int64(d.exp)), nil) - num := new(big.Int).Mul(d.value, mul) + num := new(big.Int).Mul(d.getValue(), pow10(int64(d.exp))) return new(big.Rat).SetFrac(num, oneInt) } @@ -1447,6 +1083,30 @@ func (d Decimal) Rat() *big.Rat { // whether f represents d exactly. // For more details, see the documentation for big.Rat.Float64 func (d Decimal) Float64() (f float64, exact bool) { + // Rat() materializes 10^|exp|, which takes forever for huge exponents. + // Values that far outside the float64 range don't need it: they always + // round to zero or overflow to infinity. + sign := d.Sign() + if sign == 0 { + return 0, true + } + // Both operands are exact here, so the division is correctly rounded like Rat().Float64(), + // and the result is exact when the 5^k part of 10^k divides the coefficient. + if v := d.getValue(); d.exp <= 0 && d.exp >= -22 && v.IsInt64() { + if i := v.Int64(); i >= -1<<53 && i <= 1<<53 { + return float64(i) / float64Pow10[-d.exp], i%pow5Int64[-d.exp] == 0 + } + } + // |d| lies in [10^(magnitude-1), 10^magnitude). float64 spans roughly + // 1e-324..1e308, so ±400 is safely outside it with margin to spare. + magnitude := int64(d.NumDigits()) + int64(d.exp) + if magnitude < -400 { + return math.Copysign(0, float64(sign)), false + } + if magnitude > 400 { + return math.Inf(sign), false + } + return d.Rat().Float64() } @@ -1469,7 +1129,7 @@ func (d Decimal) InexactFloat64() float64 { // // -12.345 func (d Decimal) String() string { - return d.string(true) + return d.string(TrimTrailingZeros, UseScientificNotation) } // StringFixed returns a rounded fixed-point string with places digits after @@ -1484,9 +1144,11 @@ func (d Decimal) String() string { // NewFromFloat(5.45).StringFixed(2) // output: "5.45" // NewFromFloat(5.45).StringFixed(3) // output: "5.450" // NewFromFloat(545).StringFixed(-1) // output: "550" +// +// Regardless of the UseScientificNotation option, the returned string will never be in scientific notation. func (d Decimal) StringFixed(places int32) string { rounded := d.Round(places) - return rounded.string(false) + return rounded.string(false, false) } // StringFixedBank returns a banker rounded fixed-point string with places digits @@ -1501,16 +1163,120 @@ func (d Decimal) StringFixed(places int32) string { // NewFromFloat(5.45).StringFixedBank(2) // output: "5.45" // NewFromFloat(5.45).StringFixedBank(3) // output: "5.450" // NewFromFloat(545).StringFixedBank(-1) // output: "540" +// +// Regardless of the UseScientificNotation option, the returned string will never be in scientific notation. func (d Decimal) StringFixedBank(places int32) string { rounded := d.RoundBank(places) - return rounded.string(false) + return rounded.string(false, false) } // StringFixedCash returns a Swedish/Cash rounded fixed-point string. For // more details see the documentation at function RoundCash. +// +// Regardless of the UseScientificNotation option, the returned string will never be in scientific notation. func (d Decimal) StringFixedCash(interval uint8) string { rounded := d.RoundCash(interval) - return rounded.string(false) + return rounded.string(false, false) +} + +// Format implements fmt.Formatter, so decimals work with the fmt verbs: +// +// - %v and %s print String(), and %q, %x and %X quote it or print it in hex, as for any fmt.Stringer. +// - %f and %F print fixed-point notation. With a precision, as in %.2f, the decimal is rounded like +// StringFixed. Without one, it's printed exactly, like String() but never in scientific notation. +// Width and the '-', '+', ' ' and '0' flags work as they do for floats. +// - %#v prints Go syntax that recreates the decimal exactly, such as decimal.New(314, -2). +// +// Other verbs print fmt's usual error, such as %!d(decimal.Decimal=3.14). +func (d Decimal) Format(f fmt.State, verb rune) { + switch verb { + case 'v', 's', 'q', 'x', 'X': + if verb == 'v' && f.Flag('#') { + formatString(f, 's', d.goSyntax()) + } else { + formatString(f, verb, d.String()) + } + case 'f', 'F': + d.formatFixed(f) + default: + _, _ = fmt.Fprintf(f, "%%!%c(decimal.Decimal=%s)", verb, d.String()) + } +} + +// formatString formats s as fmt would for the directive that called Format, with verb in place of its own. +func formatString(f fmt.State, verb rune, s string) { + // Println and plain %v are the common case, and rebuilding the directive costs 3 allocations. + _, hasWidth := f.Width() + _, hasPrec := f.Precision() + if (verb == 'v' || verb == 's') && !hasWidth && !hasPrec { + _, _ = io.WriteString(f, s) + return + } + _, _ = fmt.Fprintf(f, fmtDirective(f, verb), s) +} + +// fmtDirective rebuilds the directive that called Format, as fmt.FormatString does from Go 1.20. +func fmtDirective(f fmt.State, verb rune) string { + b := []byte{'%'} + for _, flag := range "+-# 0" { + if f.Flag(int(flag)) { + b = append(b, byte(flag)) + } + } + if width, ok := f.Width(); ok { + b = strconv.AppendInt(b, int64(width), 10) + } + if prec, ok := f.Precision(); ok { + b = strconv.AppendInt(append(b, '.'), int64(prec), 10) + } + return string(append(b, string(verb)...)) +} + +func (d Decimal) formatFixed(f fmt.State) { + var s string + if prec, ok := f.Precision(); ok { + s = d.StringFixed(int32(prec)) + } else { + s = d.string(TrimTrailingZeros, false) + } + + sign := "" + switch { + case s[0] == '-': + sign, s = "-", s[1:] + case f.Flag('+'): + sign = "+" + case f.Flag(' '): + sign = " " + } + // Before Go 1.23, fmt keeps the width of an earlier directive when ok is false. + pad := 0 + if width, ok := f.Width(); ok { + pad = width - len(sign) - len(s) + } + switch { + case pad <= 0: + s = sign + s + case f.Flag('-'): + s = sign + s + strings.Repeat(" ", pad) + case f.Flag('0'): + s = sign + strings.Repeat("0", pad) + s + default: + s = strings.Repeat(" ", pad) + sign + s + } + _, _ = io.WriteString(f, s) +} + +func (d Decimal) goSyntax() string { + switch { + case d.value == nil: + return "decimal.Decimal{}" + case d.value.IsInt64(): + return fmt.Sprintf("decimal.New(%d, %d)", d.value.Int64(), d.exp) + default: + // Shift instead of an exponent in the string, which RequireFromString caps at MaxDecodeExponent. + return fmt.Sprintf("decimal.RequireFromString(%q).Shift(%d)", d.value.String(), d.exp) + } } // Round rounds the decimal to places decimal places. @@ -1519,7 +1285,7 @@ func (d Decimal) StringFixedCash(interval uint8) string { // Example: // // NewFromFloat(5.45).Round(1).String() // output: "5.5" -// NewFromFloat(545).Round(-1).String() // output: "550" +// NewFromFloat(545).Round(-1).String() // output: "550" (with UseScientificNotation false, "5.5E2" if true) func (d Decimal) Round(places int32) Decimal { if d.exp == -places { return d @@ -1534,12 +1300,9 @@ func (d Decimal) Round(places int32) Decimal { ret.value.Add(ret.value, fiveInt) } - // floor for positive numbers, ceil for negative numbers - _, m := ret.value.DivMod(ret.value, tenInt, new(big.Int)) + // truncate towards zero + ret.value.Quo(ret.value, tenInt) ret.exp++ - if ret.value.Sign() < 0 && m.Cmp(zeroInt) != 0 { - ret.value.Add(ret.value, oneInt) - } return ret } @@ -1554,19 +1317,22 @@ func (d Decimal) Round(places int32) Decimal { // NewFromFloat(-1.454).RoundCeil(1).String() // output: "-1.4" func (d Decimal) RoundCeil(places int32) Decimal { if d.exp >= -places { - return d + return d.rescale(-places) } - rescaled := d.rescale(-places) - if d.Equal(rescaled) { - return d + // q is d truncated to places, r is not 0 when digits were dropped and has the sign of d + var r big.Int + q := new(big.Int) + q.QuoRem(d.getValue(), pow10(-int64(places)-int64(d.exp)), &r) + if r.Sign() > 0 { + q.Add(q, oneInt) + } else if q.Sign() == 0 && r.Sign() != 0 { + // reflect.DeepEqual tells an empty word slice from nil, keep the empty one that + // rounding a nonzero value to zero used to leave + q.SetBits([]big.Word{}) } - if d.value.Sign() > 0 { - rescaled.value.Add(rescaled.value, oneInt) - } - - return rescaled + return Decimal{value: q, exp: -places} } // RoundFloor rounds the decimal towards -infinity. @@ -1579,19 +1345,22 @@ func (d Decimal) RoundCeil(places int32) Decimal { // NewFromFloat(-1.454).RoundFloor(1).String() // output: "-1.5" func (d Decimal) RoundFloor(places int32) Decimal { if d.exp >= -places { - return d + return d.rescale(-places) } - rescaled := d.rescale(-places) - if d.Equal(rescaled) { - return d + // q is d truncated to places, r is not 0 when digits were dropped and has the sign of d + var r big.Int + q := new(big.Int) + q.QuoRem(d.getValue(), pow10(-int64(places)-int64(d.exp)), &r) + if r.Sign() < 0 { + q.Sub(q, oneInt) + } else if q.Sign() == 0 && r.Sign() != 0 { + // reflect.DeepEqual tells an empty word slice from nil, keep the empty one that + // rounding a nonzero value to zero used to leave + q.SetBits([]big.Word{}) } - if d.value.Sign() < 0 { - rescaled.value.Sub(rescaled.value, oneInt) - } - - return rescaled + return Decimal{value: q, exp: -places} } // RoundUp rounds the decimal away from zero. @@ -1604,21 +1373,20 @@ func (d Decimal) RoundFloor(places int32) Decimal { // NewFromFloat(-1.454).RoundUp(1).String() // output: "-1.5" func (d Decimal) RoundUp(places int32) Decimal { if d.exp >= -places { - return d + return d.rescale(-places) } - rescaled := d.rescale(-places) - if d.Equal(rescaled) { - return d + // q is d truncated to places, r is not 0 when digits were dropped and has the sign of d + var r big.Int + q := new(big.Int) + q.QuoRem(d.getValue(), pow10(-int64(places)-int64(d.exp)), &r) + if r.Sign() > 0 { + q.Add(q, oneInt) + } else if r.Sign() < 0 { + q.Sub(q, oneInt) } - if d.value.Sign() > 0 { - rescaled.value.Add(rescaled.value, oneInt) - } else if d.value.Sign() < 0 { - rescaled.value.Sub(rescaled.value, oneInt) - } - - return rescaled + return Decimal{value: q, exp: -places} } // RoundDown rounds the decimal towards zero. @@ -1630,15 +1398,7 @@ func (d Decimal) RoundUp(places int32) Decimal { // NewFromFloat(1.1001).RoundDown(2).String() // output: "1.1" // NewFromFloat(-1.454).RoundDown(1).String() // output: "-1.4" func (d Decimal) RoundDown(places int32) Decimal { - if d.exp >= -places { - return d - } - - rescaled := d.rescale(-places) - if d.Equal(rescaled) { - return d - } - return rescaled + return d.rescale(-places) } // RoundBank rounds the decimal to places decimal places. @@ -1658,14 +1418,17 @@ func (d Decimal) RoundDown(places int32) Decimal { func (d Decimal) RoundBank(places int32) Decimal { round := d.Round(places) - remainder := d.Sub(round).Abs() - half := New(5, -places-1) - if remainder.Cmp(half) == 0 && round.value.Bit(0) != 0 { - if round.value.Sign() < 0 { - round.value.Add(round.value, oneInt) - } else { - round.value.Sub(round.value, oneInt) + // it is a tie when twice the k dropped digits equal 10^k + if k := -int64(places) - int64(d.exp); k > 0 && round.getValue().Bit(0) != 0 { + var dropped big.Int + dropped.Rem(d.getValue(), pow10(k)) + if dropped.Abs(&dropped).Lsh(&dropped, 1).Cmp(pow10(k)) == 0 { + if round.getValue().Sign() < 0 { + round.value = new(big.Int).Add(round.getValue(), oneInt) + } else { + round.value = new(big.Int).Sub(round.getValue(), oneInt) + } } } @@ -1704,42 +1467,29 @@ func (d Decimal) RoundCash(interval uint8) Decimal { value: iVal, } - // TODO: optimize those calculations to reduce the high allocations (~29 allocs). - return d.Mul(dVal).Round(0).Div(dVal).Truncate(2) + return d.Mul(dVal).Round(0).DivRound(dVal, 2) } // Floor returns the nearest integer value less than or equal to d. func (d Decimal) Floor() Decimal { - d.ensureInitialized() - if d.exp >= 0 { return d } - exp := big.NewInt(10) - // NOTE(vadim): must negate after casting to prevent int32 overflow - exp.Exp(exp, big.NewInt(-int64(d.exp)), nil) - - z := new(big.Int).Div(d.value, exp) + z := new(big.Int).Div(d.getValue(), pow10(-int64(d.exp))) return Decimal{value: z, exp: 0} } // Ceil returns the nearest integer value greater than or equal to d. func (d Decimal) Ceil() Decimal { - d.ensureInitialized() - if d.exp >= 0 { return d } - exp := big.NewInt(10) - // NOTE(vadim): must negate after casting to prevent int32 overflow - exp.Exp(exp, big.NewInt(-int64(d.exp)), nil) - - z, m := new(big.Int).DivMod(d.value, exp, new(big.Int)) - if m.Cmp(zeroInt) != 0 { + z, m := new(big.Int).DivMod(d.getValue(), pow10(-int64(d.exp)), new(big.Int)) + if m.Sign() != 0 { z.Add(z, oneInt) } return Decimal{value: z, exp: 0} @@ -1747,157 +1497,22 @@ func (d Decimal) Ceil() Decimal { // Truncate truncates off digits from the number, without rounding. // -// NOTE: precision is the last digit that will not be truncated (must be >= 0). +// If precision >= 0, it specifies the number of decimal places to keep. +// If precision < 0, it truncates the integer part to the nearest 10^(-precision) +// towards zero. // // Example: // -// decimal.NewFromString("123.456").Truncate(2).String() // "123.45" +// decimal.RequireFromString("123.456").Truncate(2).String() // output: "123.45" +// decimal.RequireFromString("5432").Truncate(-2).String() // output: "5400" +// decimal.RequireFromString("-5432").Truncate(-2).String() // output: "-5400" func (d Decimal) Truncate(precision int32) Decimal { - d.ensureInitialized() - if precision >= 0 && -precision > d.exp { + if -precision > d.exp { return d.rescale(-precision) } return d } -// UnmarshalJSON implements the json.Unmarshaler interface. -func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error { - if string(decimalBytes) == "null" { - return nil - } - - str, err := unquoteIfQuoted(decimalBytes) - if err != nil { - return fmt.Errorf("error decoding string '%s': %s", decimalBytes, err) - } - - decimal, err := NewFromString(str) - *d = decimal - if err != nil { - return fmt.Errorf("error decoding string '%s': %s", str, err) - } - return nil -} - -// MarshalJSON implements the json.Marshaler interface. -func (d Decimal) MarshalJSON() ([]byte, error) { - var str string - if MarshalJSONWithoutQuotes { - str = d.String() - } else { - str = "\"" + d.String() + "\"" - } - return []byte(str), nil -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. As a string representation -// is already used when encoding to text, this method stores that string as []byte -func (d *Decimal) UnmarshalBinary(data []byte) error { - // Verify we have at least 4 bytes for the exponent. The GOB encoded value - // may be empty. - if len(data) < 4 { - return fmt.Errorf("error decoding binary %v: expected at least 4 bytes, got %d", data, len(data)) - } - - // Extract the exponent - d.exp = int32(binary.BigEndian.Uint32(data[:4])) - - // Extract the value - d.value = new(big.Int) - if err := d.value.GobDecode(data[4:]); err != nil { - return fmt.Errorf("error decoding binary %v: %s", data, err) - } - - return nil -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface. -func (d Decimal) MarshalBinary() (data []byte, err error) { - // exp is written first, but encode value first to know output size - var valueData []byte - if valueData, err = d.value.GobEncode(); err != nil { - return nil, err - } - - // Write the exponent in front, since it's a fixed size - expData := make([]byte, 4, len(valueData)+4) - binary.BigEndian.PutUint32(expData, uint32(d.exp)) - - // Return the byte array - return append(expData, valueData...), nil -} - -// Scan implements the sql.Scanner interface for database deserialization. -func (d *Decimal) Scan(value interface{}) error { - // first try to see if the data is stored in database as a Numeric datatype - switch v := value.(type) { - - case float32: - *d = NewFromFloat(float64(v)) - return nil - - case float64: - // numeric in sqlite3 sends us float64 - *d = NewFromFloat(v) - return nil - - case int64: - // at least in sqlite3 when the value is 0 in db, the data is sent - // to us as an int64 instead of a float64 ... - *d = New(v, 0) - return nil - - case uint64: - // while clickhouse may send 0 in db as uint64 - *d = NewFromUint64(v) - return nil - - default: - // default is trying to interpret value stored as string - str, err := unquoteIfQuoted(v) - if err != nil { - return err - } - *d, err = NewFromString(str) - return err - } -} - -// Value implements the driver.Valuer interface for database serialization. -func (d Decimal) Value() (driver.Value, error) { - return d.String(), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface for XML -// deserialization. -func (d *Decimal) UnmarshalText(text []byte) error { - str := string(text) - - dec, err := NewFromString(str) - *d = dec - if err != nil { - return fmt.Errorf("error decoding string '%s': %s", str, err) - } - - return nil -} - -// MarshalText implements the encoding.TextMarshaler interface for XML -// serialization. -func (d Decimal) MarshalText() (text []byte, err error) { - return []byte(d.String()), nil -} - -// GobEncode implements the gob.GobEncoder interface for gob serialization. -func (d Decimal) GobEncode() ([]byte, error) { - return d.MarshalBinary() -} - -// GobDecode implements the gob.GobDecoder interface for gob serialization. -func (d *Decimal) GobDecode(data []byte) error { - return d.UnmarshalBinary(data) -} - // StringScaled first scales the decimal then calls .String() on it. // // Deprecated: buggy and unintuitive. Use StringFixed instead. @@ -1905,15 +1520,24 @@ func (d Decimal) StringScaled(exp int32) string { return d.rescale(exp).String() } -func (d Decimal) string(trimTrailingZeros bool) string { +func (d Decimal) string(trimTrailingZeros, useScientificNotation bool) string { + if d.exp == 0 { + return d.getValue().String() + } if d.exp >= 0 { + if useScientificNotation { + return d.ScientificNotationString() + } return d.rescale(0).value.String() } - abs := new(big.Int).Abs(d.value) - str := abs.String() + str := d.getValue().String() + sign := "" + if str[0] == '-' { + sign, str = "-", str[1:] + } - var intPart, fractionalPart string + var intPart, leadingZeros, fractionalPart string // NOTE(vadim): this cast to int will cause bugs if d.exp == INT_MIN // and you are on a 32-bit machine. Won't fix this super-edge case. @@ -1925,7 +1549,12 @@ func (d Decimal) string(trimTrailingZeros bool) string { intPart = "0" num0s := -dExpInt - len(str) - fractionalPart = strings.Repeat("0", num0s) + str + if num0s <= len(zeros) { + leadingZeros = zeros[:num0s] + } else { + leadingZeros = strings.Repeat("0", num0s) + } + fractionalPart = str } if trimTrailingZeros { @@ -1936,26 +1565,42 @@ func (d Decimal) string(trimTrailingZeros bool) string { } } fractionalPart = fractionalPart[:i+1] + if fractionalPart == "" { + leadingZeros = "" + } } - number := intPart - if len(fractionalPart) > 0 { - number += "." + fractionalPart + if len(leadingZeros)+len(fractionalPart) > 0 { + return sign + intPart + "." + leadingZeros + fractionalPart } + return sign + intPart +} +// ScientificNotationString serializes the decimal into standard scientific notation. +// +// The notation is normalized to have one non-zero digit followed by a decimal point and +// the remaining significant digits followed by "E" and the base-10 exponent. +// +// A zero, which has no significant digits, is simply serialized to "0". +func (d Decimal) ScientificNotationString() string { + exp := int(d.exp) + intStr := new(big.Int).Abs(d.getValue()).String() + if intStr == "0" { + return intStr + } + first := intStr[0] + var remaining string + if len(intStr) > 1 { + remaining = "." + intStr[1:] + exp = exp + len(intStr) - 1 + } + number := string(first) + remaining + "E" + strconv.Itoa(exp) if d.value.Sign() < 0 { return "-" + number } - return number } -func (d *Decimal) ensureInitialized() { - if d.value == nil { - d.value = new(big.Int) - } -} - // Min returns the smallest Decimal that was passed in the arguments. // // To call this function with an array, you must do: @@ -1992,12 +1637,31 @@ func Max(first Decimal, rest ...Decimal) Decimal { // Sum returns the combined total of the provided first and rest Decimals func Sum(first Decimal, rest ...Decimal) Decimal { - total := first - for _, item := range rest { - total = total.Add(item) + if len(rest) == 0 { + return first } - return total + // Add returns a new value, so the following items can be added to it in place + total := first.Add(rest[0]) + last := len(rest) - 1 + if last == 0 { + return total + } + var scaled big.Int + for _, item := range rest[1:last] { + switch { + case item.exp < total.exp: + total = total.rescale(item.exp) + total.value.Add(total.value, item.getValue()) + case item.exp > total.exp: + total.value.Add(total.value, scaled.Mul(item.getValue(), pow10(int64(item.exp)-int64(total.exp)))) + default: + total.value.Add(total.value, item.getValue()) + } + } + + // the last Add builds the result the same way as adding one item at a time does + return total.Add(rest[last]) } // Avg returns the average value of the provided first and rest Decimals @@ -2009,9 +1673,6 @@ func Avg(first Decimal, rest ...Decimal) Decimal { // RescalePair rescales two decimals to common exponential value (minimal exp of both decimals) func RescalePair(d1 Decimal, d2 Decimal) (Decimal, Decimal) { - d1.ensureInitialized() - d2.ensureInitialized() - if d1.exp < d2.exp { return d1, d2.rescale(d1.exp) } else if d1.exp > d2.exp { @@ -2020,320 +1681,3 @@ func RescalePair(d1 Decimal, d2 Decimal) (Decimal, Decimal) { return d1, d2 } - -func unquoteIfQuoted(value interface{}) (string, error) { - var bytes []byte - - switch v := value.(type) { - case string: - bytes = []byte(v) - case []byte: - bytes = v - default: - return "", fmt.Errorf("could not convert value '%+v' to byte array of type '%T'", value, value) - } - - // If the amount is quoted, strip the quotes - if len(bytes) > 2 && bytes[0] == '"' && bytes[len(bytes)-1] == '"' { - bytes = bytes[1 : len(bytes)-1] - } - return string(bytes), nil -} - -// NullDecimal represents a nullable decimal with compatibility for -// scanning null values from the database. -type NullDecimal struct { - Decimal Decimal - Valid bool -} - -func NewNullDecimal(d Decimal) NullDecimal { - return NullDecimal{ - Decimal: d, - Valid: true, - } -} - -// Scan implements the sql.Scanner interface for database deserialization. -func (d *NullDecimal) Scan(value interface{}) error { - if value == nil { - d.Valid = false - return nil - } - d.Valid = true - return d.Decimal.Scan(value) -} - -// Value implements the driver.Valuer interface for database serialization. -func (d NullDecimal) Value() (driver.Value, error) { - if !d.Valid { - return nil, nil - } - return d.Decimal.Value() -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (d *NullDecimal) UnmarshalJSON(decimalBytes []byte) error { - if string(decimalBytes) == "null" { - d.Valid = false - return nil - } - d.Valid = true - return d.Decimal.UnmarshalJSON(decimalBytes) -} - -// MarshalJSON implements the json.Marshaler interface. -func (d NullDecimal) MarshalJSON() ([]byte, error) { - if !d.Valid { - return []byte("null"), nil - } - return d.Decimal.MarshalJSON() -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface for XML -// deserialization -func (d *NullDecimal) UnmarshalText(text []byte) error { - str := string(text) - - // check for empty XML or XML without body e.g., - if str == "" { - d.Valid = false - return nil - } - if err := d.Decimal.UnmarshalText(text); err != nil { - d.Valid = false - return err - } - d.Valid = true - return nil -} - -// MarshalText implements the encoding.TextMarshaler interface for XML -// serialization. -func (d NullDecimal) MarshalText() (text []byte, err error) { - if !d.Valid { - return []byte{}, nil - } - return d.Decimal.MarshalText() -} - -// Trig functions - -// Atan returns the arctangent, in radians, of x. -func (d Decimal) Atan() Decimal { - if d.Equal(NewFromFloat(0.0)) { - return d - } - if d.GreaterThan(NewFromFloat(0.0)) { - return d.satan() - } - return d.Neg().satan().Neg() -} - -func (d Decimal) xatan() Decimal { - P0 := NewFromFloat(-8.750608600031904122785e-01) - P1 := NewFromFloat(-1.615753718733365076637e+01) - P2 := NewFromFloat(-7.500855792314704667340e+01) - P3 := NewFromFloat(-1.228866684490136173410e+02) - P4 := NewFromFloat(-6.485021904942025371773e+01) - Q0 := NewFromFloat(2.485846490142306297962e+01) - Q1 := NewFromFloat(1.650270098316988542046e+02) - Q2 := NewFromFloat(4.328810604912902668951e+02) - Q3 := NewFromFloat(4.853903996359136964868e+02) - Q4 := NewFromFloat(1.945506571482613964425e+02) - z := d.Mul(d) - b1 := P0.Mul(z).Add(P1).Mul(z).Add(P2).Mul(z).Add(P3).Mul(z).Add(P4).Mul(z) - b2 := z.Add(Q0).Mul(z).Add(Q1).Mul(z).Add(Q2).Mul(z).Add(Q3).Mul(z).Add(Q4) - z = b1.Div(b2) - z = d.Mul(z).Add(d) - return z -} - -// satan reduces its argument (known to be positive) -// to the range [0, 0.66] and calls xatan. -func (d Decimal) satan() Decimal { - Morebits := NewFromFloat(6.123233995736765886130e-17) // pi/2 = PIO2 + Morebits - Tan3pio8 := NewFromFloat(2.41421356237309504880) // tan(3*pi/8) - pi := NewFromFloat(3.14159265358979323846264338327950288419716939937510582097494459) - - if d.LessThanOrEqual(NewFromFloat(0.66)) { - return d.xatan() - } - if d.GreaterThan(Tan3pio8) { - return pi.Div(NewFromFloat(2.0)).Sub(NewFromFloat(1.0).Div(d).xatan()).Add(Morebits) - } - return pi.Div(NewFromFloat(4.0)).Add((d.Sub(NewFromFloat(1.0)).Div(d.Add(NewFromFloat(1.0)))).xatan()).Add(NewFromFloat(0.5).Mul(Morebits)) -} - -// sin coefficients -var _sin = [...]Decimal{ - NewFromFloat(1.58962301576546568060e-10), // 0x3de5d8fd1fd19ccd - NewFromFloat(-2.50507477628578072866e-8), // 0xbe5ae5e5a9291f5d - NewFromFloat(2.75573136213857245213e-6), // 0x3ec71de3567d48a1 - NewFromFloat(-1.98412698295895385996e-4), // 0xbf2a01a019bfdf03 - NewFromFloat(8.33333333332211858878e-3), // 0x3f8111111110f7d0 - NewFromFloat(-1.66666666666666307295e-1), // 0xbfc5555555555548 -} - -// Sin returns the sine of the radian argument x. -func (d Decimal) Sin() Decimal { - PI4A := NewFromFloat(7.85398125648498535156e-1) // 0x3fe921fb40000000, Pi/4 split into three parts - PI4B := NewFromFloat(3.77489470793079817668e-8) // 0x3e64442d00000000, - PI4C := NewFromFloat(2.69515142907905952645e-15) // 0x3ce8469898cc5170, - M4PI := NewFromFloat(1.273239544735162542821171882678754627704620361328125) // 4/pi - - if d.Equal(NewFromFloat(0.0)) { - return d - } - // make argument positive but save the sign - sign := false - if d.LessThan(NewFromFloat(0.0)) { - d = d.Neg() - sign = true - } - - j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle - y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float - - // map zeros to origin - if j&1 == 1 { - j++ - y = y.Add(NewFromFloat(1.0)) - } - j &= 7 // octant modulo 2Pi radians (360 degrees) - // reflect in x axis - if j > 3 { - sign = !sign - j -= 4 - } - z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic - zz := z.Mul(z) - - if j == 1 || j == 2 { - w := zz.Mul(zz).Mul(_cos[0].Mul(zz).Add(_cos[1]).Mul(zz).Add(_cos[2]).Mul(zz).Add(_cos[3]).Mul(zz).Add(_cos[4]).Mul(zz).Add(_cos[5])) - y = NewFromFloat(1.0).Sub(NewFromFloat(0.5).Mul(zz)).Add(w) - } else { - y = z.Add(z.Mul(zz).Mul(_sin[0].Mul(zz).Add(_sin[1]).Mul(zz).Add(_sin[2]).Mul(zz).Add(_sin[3]).Mul(zz).Add(_sin[4]).Mul(zz).Add(_sin[5]))) - } - if sign { - y = y.Neg() - } - return y -} - -// cos coefficients -var _cos = [...]Decimal{ - NewFromFloat(-1.13585365213876817300e-11), // 0xbda8fa49a0861a9b - NewFromFloat(2.08757008419747316778e-9), // 0x3e21ee9d7b4e3f05 - NewFromFloat(-2.75573141792967388112e-7), // 0xbe927e4f7eac4bc6 - NewFromFloat(2.48015872888517045348e-5), // 0x3efa01a019c844f5 - NewFromFloat(-1.38888888888730564116e-3), // 0xbf56c16c16c14f91 - NewFromFloat(4.16666666666665929218e-2), // 0x3fa555555555554b -} - -// Cos returns the cosine of the radian argument x. -func (d Decimal) Cos() Decimal { - - PI4A := NewFromFloat(7.85398125648498535156e-1) // 0x3fe921fb40000000, Pi/4 split into three parts - PI4B := NewFromFloat(3.77489470793079817668e-8) // 0x3e64442d00000000, - PI4C := NewFromFloat(2.69515142907905952645e-15) // 0x3ce8469898cc5170, - M4PI := NewFromFloat(1.273239544735162542821171882678754627704620361328125) // 4/pi - - // make argument positive - sign := false - if d.LessThan(NewFromFloat(0.0)) { - d = d.Neg() - } - - j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle - y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float - - // map zeros to origin - if j&1 == 1 { - j++ - y = y.Add(NewFromFloat(1.0)) - } - j &= 7 // octant modulo 2Pi radians (360 degrees) - // reflect in x axis - if j > 3 { - sign = !sign - j -= 4 - } - if j > 1 { - sign = !sign - } - - z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic - zz := z.Mul(z) - - if j == 1 || j == 2 { - y = z.Add(z.Mul(zz).Mul(_sin[0].Mul(zz).Add(_sin[1]).Mul(zz).Add(_sin[2]).Mul(zz).Add(_sin[3]).Mul(zz).Add(_sin[4]).Mul(zz).Add(_sin[5]))) - } else { - w := zz.Mul(zz).Mul(_cos[0].Mul(zz).Add(_cos[1]).Mul(zz).Add(_cos[2]).Mul(zz).Add(_cos[3]).Mul(zz).Add(_cos[4]).Mul(zz).Add(_cos[5])) - y = NewFromFloat(1.0).Sub(NewFromFloat(0.5).Mul(zz)).Add(w) - } - if sign { - y = y.Neg() - } - return y -} - -var _tanP = [...]Decimal{ - NewFromFloat(-1.30936939181383777646e+4), // 0xc0c992d8d24f3f38 - NewFromFloat(1.15351664838587416140e+6), // 0x413199eca5fc9ddd - NewFromFloat(-1.79565251976484877988e+7), // 0xc1711fead3299176 -} -var _tanQ = [...]Decimal{ - NewFromFloat(1.00000000000000000000e+0), - NewFromFloat(1.36812963470692954678e+4), //0x40cab8a5eeb36572 - NewFromFloat(-1.32089234440210967447e+6), //0xc13427bc582abc96 - NewFromFloat(2.50083801823357915839e+7), //0x4177d98fc2ead8ef - NewFromFloat(-5.38695755929454629881e+7), //0xc189afe03cbe5a31 -} - -// Tan returns the tangent of the radian argument x. -func (d Decimal) Tan() Decimal { - - PI4A := NewFromFloat(7.85398125648498535156e-1) // 0x3fe921fb40000000, Pi/4 split into three parts - PI4B := NewFromFloat(3.77489470793079817668e-8) // 0x3e64442d00000000, - PI4C := NewFromFloat(2.69515142907905952645e-15) // 0x3ce8469898cc5170, - M4PI := NewFromFloat(1.273239544735162542821171882678754627704620361328125) // 4/pi - - if d.Equal(NewFromFloat(0.0)) { - return d - } - - // make argument positive but save the sign - sign := false - if d.LessThan(NewFromFloat(0.0)) { - d = d.Neg() - sign = true - } - - j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle - y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float - - // map zeros to origin - if j&1 == 1 { - j++ - y = y.Add(NewFromFloat(1.0)) - } - - z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic - zz := z.Mul(z) - - if zz.GreaterThan(NewFromFloat(1e-14)) { - w := zz.Mul(_tanP[0].Mul(zz).Add(_tanP[1]).Mul(zz).Add(_tanP[2])) - x := zz.Add(_tanQ[1]).Mul(zz).Add(_tanQ[2]).Mul(zz).Add(_tanQ[3]).Mul(zz).Add(_tanQ[4]) - y = z.Add(z.Mul(w.Div(x))) - } else { - y = z - } - if j&2 == 2 { - y = NewFromFloat(-1.0).Div(y) - } - if sign { - y = y.Neg() - } - return y -} diff --git a/decimal_bench_test.go b/decimal_bench_test.go index 34e038f..06f0bb9 100644 --- a/decimal_bench_test.go +++ b/decimal_bench_test.go @@ -23,10 +23,9 @@ func BenchmarkNewFromFloatWithExponent(b *testing.B) { in[i] = rng.NormFloat64() * 10e20 } b.ReportAllocs() - b.StartTimer() + b.ResetTimer() for i := 0; i < b.N; i++ { - in := rng.NormFloat64() * 10e20 - _ = NewFromFloatWithExponent(in, math.MinInt32) + _ = NewFromFloatWithExponent(in[i], math.MinInt32) } } @@ -37,7 +36,7 @@ func BenchmarkNewFromFloat(b *testing.B) { in[i] = rng.NormFloat64() * 10e20 } b.ReportAllocs() - b.StartTimer() + b.ResetTimer() for i := 0; i < b.N; i++ { _ = NewFromFloat(in[i]) } @@ -45,15 +44,14 @@ func BenchmarkNewFromFloat(b *testing.B) { func BenchmarkNewFromStringFloat(b *testing.B) { rng := rand.New(rand.NewSource(0xdead1337)) - in := make([]float64, b.N) + in := make([]string, b.N) for i := range in { - in[i] = rng.NormFloat64() * 10e20 + in[i] = strconv.FormatFloat(rng.NormFloat64()*10e20, 'f', -1, 64) } b.ReportAllocs() - b.StartTimer() + b.ResetTimer() for i := 0; i < b.N; i++ { - in := strconv.FormatFloat(in[i], 'f', -1, 64) - _, _ = NewFromString(in) + _, _ = NewFromString(in[i]) } } @@ -200,7 +198,7 @@ func BenchmarkDecimal_Sub_same_precision(b *testing.B) { b.ReportAllocs() b.StartTimer() for i := 0; i < b.N; i++ { - d1.Add(d2) + d1.Sub(d2) } } @@ -249,6 +247,15 @@ func BenchmarkDecimal_PowBigInt(b *testing.B) { } } +func BenchmarkDecimal_PowNegativeExponent(b *testing.B) { + d1 := RequireFromString("1.0041666666666667") + d2 := RequireFromString("-360") + + for i := 0; i < b.N; i++ { + d1.Pow(d2) + } +} + func BenchmarkDecimal_NewFromString(b *testing.B) { count := 72 prices := make([]string, 0, count) @@ -312,3 +319,55 @@ func BenchmarkDecimal_ExpTaylor(b *testing.B) { _, _ = d.ExpTaylor(10) } } + +func BenchmarkDecimal_Normalize(b *testing.B) { + for _, c := range []struct { + name string + d Decimal + }{ + {"NoZeros", RequireFromString("123.456")}, + {"OneZero", RequireFromString("12.50")}, + {"Div", NewFromInt(5).Div(NewFromInt(2))}, + {"Big", RequireFromString("123456789012345678901234567890.5000")}, + {"ManyZeros", RequireFromString("1.5").Round(10000)}, + } { + b.Run(c.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + c.d.Normalize() + } + }) + } +} + +func BenchmarkDecimal_Sqrt(b *testing.B) { + for _, c := range []struct { + name string + d Decimal + places int32 + }{ + {"Small", RequireFromString("0.002342"), 16}, + {"Int", NewFromInt(2), 16}, + {"Big", RequireFromString("123456789012345678901234567890.123"), 16}, + {"Places1000", NewFromInt(2), 1000}, + } { + b.Run(c.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = c.d.SqrtRound(c.places) + } + }) + } +} + +func BenchmarkDecimal_UnmarshalJSON(b *testing.B) { + b.ResetTimer() + + bstr := []byte("1234.56789") + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = (&Decimal{}).UnmarshalJSON(bstr) + } +} diff --git a/decimal_go113_test.go b/decimal_go113_test.go new file mode 100644 index 0000000..b6132be --- /dev/null +++ b/decimal_go113_test.go @@ -0,0 +1,122 @@ +//go:build go1.13 +// +build go1.13 + +package decimal + +import ( + "math" + "math/rand" + "strconv" + "testing" +) + +// Before Go 1.13, strconv could return a shortest form that wasn't the nearest one for these (golang/go#29491). +func init() { + testTable = append(testTable, + &testEnt{498484681984085570, "498484681984085570", "", ""}, + &testEnt{5.8339553793802237e+23, "583395537938022370000000", "", ""}, + ) +} + +// TestNewFromFloatShortestRoundTrip pins the contract of NewFromFloat and +// NewFromFloat32: the result is the shortest decimal that parses back to the +// original float. It covers the exponent and significand shapes a +// shortest-representation conversion has to get right, namely subnormals, +// three-digit exponents, single-digit significands and the range extremes. +// +// It is gated on Go 1.13 because before that strconv itself could return a +// shortest form that was not the nearest one (golang/go#29491), which would make +// the reference value below wrong rather than the conversion under test. +func TestNewFromFloatShortestRoundTrip(t *testing.T) { + f64 := []float64{ + 1, -1, 0.1, -0.1, 0.5, 2.5, 100, 123.456, + 1e15, 1e16, 1e17, 1e21, 1e22, 1e23, 9007199254740992, + 1e-100, 1e100, 1e-300, 1e300, + math.MaxFloat64, -math.MaxFloat64, + math.SmallestNonzeroFloat64, -math.SmallestNonzeroFloat64, + math.Pi, math.E, + } + // Every power of two, which sweeps the whole exponent range including the + // subnormals, plus both neighbours of every value collected so far. + for e := -1074; e <= 1023; e++ { + f64 = append(f64, math.Ldexp(1, e)) + } + for _, f := range append([]float64{}, f64...) { + f64 = append(f64, math.Nextafter(f, math.Inf(1)), math.Nextafter(f, math.Inf(-1))) + } + + for _, f := range f64 { + if f == 0 || math.IsInf(f, 0) { + continue + } + want, err := NewFromString(strconv.FormatFloat(f, 'f', -1, 64)) + if err != nil { + t.Fatalf("NewFromString(%v): %v", f, err) + } + got := NewFromFloat(f) + if !got.Equal(want) { + t.Errorf("NewFromFloat(%v) = %s (%s, %d), want %s (%s, %d)", + f, got, got.value, got.exp, want, want.value, want.exp) + } + if back, _ := got.Float64(); back != f { + t.Errorf("NewFromFloat(%v).Float64() = %v, does not round-trip", f, back) + } + } + + f32 := []float32{ + 1, -1, 0.1, -0.1, 100, 123.456, + math.MaxFloat32, -math.MaxFloat32, + math.SmallestNonzeroFloat32, -math.SmallestNonzeroFloat32, + } + for e := -149; e <= 127; e++ { + f32 = append(f32, float32(math.Ldexp(1, e))) + } + for _, f := range f32 { + if f == 0 { + continue + } + want, err := NewFromString(strconv.FormatFloat(float64(f), 'f', -1, 32)) + if err != nil { + t.Fatalf("NewFromString(%v): %v", f, err) + } + got := NewFromFloat32(f) + if !got.Equal(want) { + t.Errorf("NewFromFloat32(%v) = %s (%s, %d), want %s (%s, %d)", + f, got, got.value, got.exp, want, want.value, want.exp) + } + } +} + +// TestNewFromFloatRandomRoundTrip sweeps random bit patterns, which puts a +// meaningful share of subnormals and extreme exponents through the conversion. +func TestNewFromFloatRandomRoundTrip(t *testing.T) { + rng := rand.New(rand.NewSource(0xdead1337)) + for i := 0; i < 200000; i++ { + f := math.Float64frombits(rng.Uint64()) + if f == 0 || math.IsNaN(f) || math.IsInf(f, 0) { + continue + } + want, err := NewFromString(strconv.FormatFloat(f, 'f', -1, 64)) + if err != nil { + t.Fatalf("NewFromString(%v): %v", f, err) + } + if got := NewFromFloat(f); !got.Equal(want) { + t.Fatalf("NewFromFloat(%v) = %s (%s, %d), want %s (%s, %d)", + f, got, got.value, got.exp, want, want.value, want.exp) + } + } + for i := 0; i < 200000; i++ { + f := math.Float32frombits(rng.Uint32()) + if f == 0 || math.IsNaN(float64(f)) || math.IsInf(float64(f), 0) { + continue + } + want, err := NewFromString(strconv.FormatFloat(float64(f), 'f', -1, 32)) + if err != nil { + t.Fatalf("NewFromString(%v): %v", f, err) + } + if got := NewFromFloat32(f); !got.Equal(want) { + t.Fatalf("NewFromFloat32(%v) = %s (%s, %d), want %s (%s, %d)", + f, got, got.value, got.exp, want, want.value, want.exp) + } + } +} diff --git a/decimal_go124_test.go b/decimal_go124_test.go new file mode 100644 index 0000000..13ee324 --- /dev/null +++ b/decimal_go124_test.go @@ -0,0 +1,58 @@ +//go:build go1.24 +// +build go1.24 + +package decimal + +import ( + "encoding/json" + "testing" +) + +// `omitzero` is supported by encoding/json starting in Go 1.24. +// Keep this test in a go1.24-gated file so older CI jobs skip it. +func TestJSONOmitZeroTag(t *testing.T) { + type Nested struct { + Amount Decimal `json:"amount,omitzero"` + } + + type Parent struct { + Nested Nested `json:"nested,omitzero"` + } + + tests := []struct { + name string + parent Parent + expected string + }{ + { + name: "Decimal{} empty value", + parent: Parent{ + Nested: Nested{ + Amount: Decimal{}, + }, + }, + expected: "{}", + }, + { + name: "Zero constant", + parent: Parent{ + Nested: Nested{ + Amount: Zero, + }, + }, + expected: "{}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := json.Marshal(tt.parent) + if err != nil { + t.Fatal(err) + } + if string(b) != tt.expected { + t.Errorf("expected %s, got %s", tt.expected, string(b)) + } + }) + } +} diff --git a/decimal_test.go b/decimal_test.go index d398f2d..73d43c8 100644 --- a/decimal_test.go +++ b/decimal_test.go @@ -2,6 +2,7 @@ package decimal import ( "database/sql/driver" + "encoding/hex" "encoding/json" "encoding/xml" "fmt" @@ -12,6 +13,7 @@ import ( "regexp" "strconv" "strings" + "sync" "testing" "testing/quick" "time" @@ -58,9 +60,6 @@ var testTable = []*testEnt{ {2e250, "20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "", ""}, {math.MaxInt64, strconv.FormatFloat(float64(math.MaxInt64), 'f', -1, 64), "", strconv.FormatInt(math.MaxInt64, 10)}, {1.29067116156722e-309, "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000129067116156722", "", "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001290671161567218558822290567835270536800098852722416870074139002112543896676308448335063375297788379444685193974290737962187240854947838776604607190387984577130572928111657710645015086812756013489109884753559084166516937690932698276436869274093950997935137476803610007959500457935217950764794724766740819156974617155861568214427828145972181876775307023388139991104942469299524961281641158436752347582767153796914843896176260096039358494077706152272661453132497761307744086665088096215425146090058519888494342944692629602847826300550628670375451325582843627504604013541465361435761965354140678551369499812124085312128659002910905639984075064968459581691226705666561364681985266583563078466180095375402399087817404368974165082030458595596655868575908243656158447265625000000000000000000000000000000000000004440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, - // go Issue 29491. - {498484681984085570, "498484681984085570", "", ""}, - {5.8339553793802237e+23, "583395537938022370000000", "", ""}, } var testTableScientificNotation = map[string]string{ @@ -80,6 +79,11 @@ var testTableScientificNotation = map[string]string{ "123.456e10": "1234560000000", } +var testMalformedDecimalStrings = map[string]error{ + "1ee10": fmt.Errorf("can't convert %s to decimal: multiple 'E' characters found", "1ee10"), + "123.45.66": fmt.Errorf("can't convert %s to decimal: too many .s", "123.45.66"), +} + func init() { for _, s := range testTable { s.exact = strconv.FormatFloat(s.float, 'f', 1500, 64) @@ -239,6 +243,15 @@ func TestNewFromString(t *testing.T) { d.value.String(), d.exp) } } + + for s, e := range testMalformedDecimalStrings { + _, err := NewFromString(s) + if err == nil { + t.Errorf("expected an error, got nil %s", s) + } else if err.Error() != e.Error() { + t.Errorf("expected %v error, got %v", e, err) + } + } } func TestNewFromFormattedString(t *testing.T) { @@ -248,11 +261,11 @@ func TestNewFromFormattedString(t *testing.T) { ReplRegex *regexp.Regexp }{ {"$10.99", "10.99", regexp.MustCompile("[$]")}, - {"$ 12.1", "12.1", regexp.MustCompile("[$\\s]")}, + {"$ 12.1", "12.1", regexp.MustCompile(`[$\s]`)}, {"$61,690.99", "61690.99", regexp.MustCompile("[$,]")}, {"1_000_000.00", "1000000.00", regexp.MustCompile("[_]")}, {"41,410.00", "41410.00", regexp.MustCompile("[,]")}, - {"5200 USD", "5200", regexp.MustCompile("[USD\\s]")}, + {"5200 USD", "5200", regexp.MustCompile(`[USD\s]`)}, } { dFormatted, err := NewFromFormattedString(testCase.Formatted, testCase.ReplRegex) if err != nil { @@ -292,6 +305,76 @@ func TestFloat64(t *testing.T) { } } +func TestFloat64OutOfRange(t *testing.T) { + type tc struct { + d Decimal + f float64 + exact bool + } + tests := []tc{ + // near the float64 limits, still converted the normal way + {New(1, 308), 1e308, false}, + {New(-1, 308), -1e308, false}, + {New(1, 309), math.Inf(1), false}, + {New(-1, 309), math.Inf(-1), false}, + {New(5, -324), 5e-324, false}, + {New(1, -325), 0, false}, + {New(-1, -325), math.Copysign(0, -1), false}, + {New(123456789, -340), 1.23456789e-332, false}, + {New(0, -1<<30), 0, true}, + {New(0, 1<<30), 0, true}, + // far outside the float64 range (issue #226) + {New(100, -1<<30), 0, false}, + {New(-100, -1<<30), math.Copysign(0, -1), false}, + {New(100, 1<<30), math.Inf(1), false}, + {New(-100, 1<<30), math.Inf(-1), false}, + {New(1, math.MinInt32), 0, false}, + {New(1, math.MaxInt32), math.Inf(1), false}, + {NewFromBigInt(new(big.Int).Exp(tenInt, big.NewInt(400), nil), -1<<30), 0, false}, + } + + for _, test := range tests { + done := make(chan struct{}) + var f float64 + var exact bool + go func() { + f, exact = test.d.Float64() + close(done) + }() + + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatalf("Float64() of %de%d did not return within 5s", test.d.Coefficient(), test.d.Exponent()) + } + + if math.Float64bits(f) != math.Float64bits(test.f) || exact != test.exact { + t.Errorf("%de%d: expected (%v, %v), got (%v, %v)", + test.d.Coefficient(), test.d.Exponent(), test.f, test.exact, f, exact) + } + } +} + +func TestNewFromStringUnicodeMinus(t *testing.T) { + cases := []struct { + in, want string + }{ + {"\u2212123.45", "-123.45"}, + {"\u2212.5", "-0.5"}, + {"1e\u22122", "0.01"}, + } + for _, tc := range cases { + d, err := NewFromString(tc.in) + if err != nil { + t.Errorf("NewFromString(%q) unexpected error: %v", tc.in, err) + continue + } + if d.String() != tc.want { + t.Errorf("NewFromString(%q) = %q, want %q", tc.in, d.String(), tc.want) + } + } +} + func TestNewFromStringErrs(t *testing.T) { tests := []string{ "", @@ -329,6 +412,9 @@ func TestNewFromStringErrs(t *testing.T) { "51,850.00", "20_000_000.00", "$20_000_000.00", + ".-5", + ".+5", + ".-1234567890123456789012", } for _, s := range tests { @@ -340,6 +426,67 @@ func TestNewFromStringErrs(t *testing.T) { } } +func TestParseInt64SkipIndex(t *testing.T) { + // every string of up to 5 bytes from this alphabet, with the decimal point at each position + const alphabet = "059-+.a " + var check func(s string) + check = func(s string) { + for skip := -1; skip < len(s); skip++ { + if skip >= 0 && s[skip] != '.' { + continue + } + if skip == 0 && len(s) > 1 && (s[1] == '-' || s[1] == '+') { + continue // NewFromString rejects a sign after the point before parsing + } + withoutPoint := s + if skip >= 0 { + withoutPoint = s[:skip] + s[skip+1:] + } + want, err := strconv.ParseInt(withoutPoint, 10, 64) + got, ok := parseInt64SkipIndex(s, skip) + if ok != (err == nil) || got != want { + t.Fatalf("parseInt64SkipIndex(%q, %d) = %d, %v; ParseInt(%q) = %d, %v", s, skip, got, ok, withoutPoint, want, err) + } + } + if len(s) < 5 { + for i := range alphabet { + check(s + alphabet[i:i+1]) + } + } + } + check("") +} + +func TestMaxDecodeExponent(t *testing.T) { + limit := int32(MaxDecodeExponent) + for _, c := range []struct { + exp int32 + ok bool + }{ + {limit, true}, + {-limit, true}, + {limit + 1, false}, + {-limit - 1, false}, + {math.MaxInt32, false}, + {math.MinInt32, false}, + } { + if _, err := NewFromString(fmt.Sprintf("1e%d", c.exp)); (err == nil) != c.ok { + t.Errorf("NewFromString(1e%d): got error %v, want ok=%v", c.exp, err, c.ok) + } + + b, _ := New(1, c.exp).MarshalBinary() + d := New(7, 0) + err := d.UnmarshalBinary(b) + if (err == nil) != c.ok || (!c.ok && d.Exponent() != 0) { + t.Errorf("UnmarshalBinary(exp %d): got error %v and exp %d, want ok=%v", c.exp, err, d.Exponent(), c.ok) + } + } + + if _, err := NewFromString(fmt.Sprintf("0.1e%d", -limit)); err == nil { + t.Errorf("NewFromString(0.1e%d) accepted exponent %d", -limit, -limit-1) + } +} + func TestNewFromStringDeepEquals(t *testing.T) { type StrCmp struct { str1 string @@ -388,16 +535,15 @@ func TestRequireFromString(t *testing.T) { func TestRequireFromStringErrs(t *testing.T) { s := "qwert" - var d Decimal var err interface{} - func(d Decimal) { + func() { defer func() { err = recover() }() RequireFromString(s) - }(d) + }() if err == nil { t.Errorf("panic expected when parsing %s", s) @@ -562,7 +708,7 @@ func TestNewFromBigIntWithExponent(t *testing.T) { // add negatives for p, s := range tests { - if p.val.Cmp(Zero.value) > 0 { + if p.val.Cmp(zeroInt) > 0 { tests[Inp{p.val.Neg(p.val), p.exp}] = "-" + s } } @@ -589,17 +735,18 @@ func TestNewFromBigRat(t *testing.T) { } tests := map[Inp]string{ - Inp{big.NewRat(0, 1), 16}: "0", - Inp{big.NewRat(4, 5), 16}: "0.8", - Inp{big.NewRat(10, 2), 16}: "5", - Inp{big.NewRat(1023427554493, 43432632), 16}: "23563.5628642767953828", // rounded - Inp{big.NewRat(1, 434324545566634), 16}: "0.0000000000000023", - Inp{big.NewRat(1, 3), 16}: "0.3333333333333333", - Inp{big.NewRat(2, 3), 2}: "0.67", // rounded - Inp{big.NewRat(2, 3), 16}: "0.6666666666666667", // rounded - Inp{big.NewRat(10000, 3), 16}: "3333.3333333333333333", - Inp{mustParseRat("30702832066636633479"), 16}: "30702832066636633479", - Inp{mustParseRat("487028320159896636679.1827512895753"), 16}: "487028320159896636679.1827512895753", + Inp{big.NewRat(0, 1), 16}: "0", + Inp{big.NewRat(4, 5), 16}: "0.8", + Inp{big.NewRat(10, 2), 16}: "5", + Inp{big.NewRat(1023427554493, 43432632), 16}: "23563.5628642767953828", // rounded + Inp{big.NewRat(1, 434324545566634), 16}: "0.0000000000000023", + Inp{big.NewRat(1, 3), 16}: "0.3333333333333333", + Inp{big.NewRat(2, 3), 2}: "0.67", // rounded + Inp{big.NewRat(2, 3), 16}: "0.6666666666666667", // rounded + Inp{big.NewRat(10000, 3), 16}: "3333.3333333333333333", + Inp{mustParseRat("30702832066636633479"), 16}: "30702832066636633479", + Inp{mustParseRat("487028320159896636679.1827512895753"), 16}: "487028320159896636679.1827512895753", + Inp{mustParseRat("127028320612589896636633479.173582751289575278357832"), -2}: "127028320612589896636633500", // rounded Inp{mustParseRat("127028320612589896636633479.173582751289575278357832"), 16}: "127028320612589896636633479.1735827512895753", // rounded Inp{mustParseRat("127028320612589896636633479.173582751289575278357832"), 32}: "127028320612589896636633479.173582751289575278357832", @@ -622,6 +769,88 @@ func TestNewFromBigRat(t *testing.T) { } } +func TestNewFromBigFloat(t *testing.T) { + mustParseFloat := func(s string, prec uint) *big.Float { + f, _, err := big.ParseFloat(s, 10, prec, big.ToNearestEven) + if err != nil { + t.Fatalf("failed to parse float %s: %v", s, err) + } + return f + } + + testCases := []struct { + name string + input *big.Float + expected string + }{ + {"zero", new(big.Float).SetInt64(0), "0"}, + {"uninitialized", new(big.Float), "0"}, + {"negative zero", new(big.Float).Neg(new(big.Float)), "0"}, + {"integer positive", new(big.Float).SetInt64(12345), "12345"}, + {"integer negative", new(big.Float).SetInt64(-12345), "-12345"}, + {"simple float positive", mustParseFloat("12.1", 64), "12.1"}, + {"simple float negative", mustParseFloat("-12.1", 64), "-12.1"}, + {"fractional positive", mustParseFloat("0.01", 64), "0.01"}, + {"fractional negative", mustParseFloat("-0.01", 64), "-0.01"}, + {"decimal 123.456", mustParseFloat("123.456", 64), "123.456"}, + {"decimal -123.456", mustParseFloat("-123.456", 64), "-123.456"}, + {"from float64 exact", new(big.Float).SetFloat64(1.25), "1.25"}, + {"from float64 0.5", new(big.Float).SetFloat64(0.5), "0.5"}, + {"from rat half", new(big.Float).SetRat(big.NewRat(1, 2)), "0.5"}, + {"high precision pi", mustParseFloat("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 512), "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"}, + {"high precision large", mustParseFloat("1234567890123456789012345678901234567890.1234567890123456789", 512), "1234567890123456789012345678901234567890.1234567890123456789"}, + {"large exponent", mustParseFloat("1e25", 256), "10000000000000000000000000"}, + {"small exponent", mustParseFloat("1e-25", 256), "0.0000000000000000000000001"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + d, err := NewFromBigFloat(tc.input) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if d.String() != tc.expected { + t.Errorf("expected %s, got %s", tc.expected, d.String()) + } + }) + } +} + +func TestNewFromBigFloat_Error(t *testing.T) { + t.Run("nil", func(t *testing.T) { + _, err := NewFromBigFloat(nil) + if err == nil { + t.Fatal("expected error for nil, got nil") + } + }) + + t.Run("positive inf", func(t *testing.T) { + f := new(big.Float).SetInf(false) + _, err := NewFromBigFloat(f) + if err == nil { + t.Fatal("expected error for +Inf, got nil") + } + }) + + t.Run("negative inf", func(t *testing.T) { + f := new(big.Float).SetInf(true) + _, err := NewFromBigFloat(f) + if err == nil { + t.Fatal("expected error for -Inf, got nil") + } + }) + + t.Run("exponent overflow", func(t *testing.T) { + f, _, err := big.ParseFloat("1e15000", 10, 64, big.ToNearestEven) + if err == nil { + _, err = NewFromBigFloat(f) + if err == nil { + t.Fatal("expected error for exponent exceeding MaxDecodeExponent, got nil") + } + } + }) +} + func TestCopy(t *testing.T) { origin := New(1, 0) cpy := origin.Copy() @@ -634,7 +863,7 @@ func TestCopy(t *testing.T) { t.Error("expecting copy and origin to be equals, but they are not") } - //change value + // change value cpy = cpy.Add(New(1, 0)) if cpy.Cmp(origin) == 0 { @@ -802,6 +1031,17 @@ func TestNullDecimalBadJSON(t *testing.T) { if err == nil { t.Errorf("expected error, got %+v", doc) } + if doc.Amount.Valid { + t.Errorf("%s: expected Valid to be false after an error", testCase) + } + } + + var nd NullDecimal + if err := nd.Scan("nope"); err == nil || nd.Valid { + t.Errorf("Scan(\"nope\"): expected an error and Valid false, got %v and %v", err, nd.Valid) + } + if err := nd.DecodeSpanner("nope"); err == nil || nd.Valid { + t.Errorf("DecodeSpanner(\"nope\"): expected an error and Valid false, got %v and %v", err, nd.Valid) } } @@ -1829,6 +2069,64 @@ func TestDecimal_Shift(t *testing.T) { } } +func TestDecimal_ShiftExponentOverflow(t *testing.T) { + // The sum still fits, so the exponent is unchanged by the overflow check. + got := New(5, math.MaxInt32-1).Shift(1) + if !got.Equal(New(5, math.MaxInt32)) { + t.Fatalf("shift onto MaxInt32: exp %d", got.Exponent()) + } + + if !didPanic(func() { New(5, math.MaxInt32).Shift(1) }) { + t.Fatalf("should have gotten an overflow panic") + } + if !didPanic(func() { New(5, math.MinInt32).Shift(-1) }) { + t.Fatalf("should have gotten an overflow panic") + } +} + +func TestDecimal_Normalize(t *testing.T) { + for _, c := range []struct { + d Decimal + coef string + exp int32 + }{ + {New(123, -2), "123", -2}, + {New(100, 0), "1", 2}, + {New(100, -3), "1", -1}, + {New(-1500, -3), "-15", -1}, + {RequireFromString("2.5000000000000000"), "25", -1}, + {RequireFromString("-123456789012345678901234567891000"), "-123456789012345678901234567891", 3}, + {RequireFromString("12.500000000000000000"), "125", -1}, + {RequireFromString("1" + strings.Repeat("0", 40)), "1", 40}, + {New(100, math.MaxInt32-1), "10", math.MaxInt32}, + {New(10, math.MaxInt32), "10", math.MaxInt32}, + {NewFromBigInt(pow10(40), math.MaxInt32-30), "10000000000", math.MaxInt32}, + } { + coef, exp := c.d.Coefficient().String(), c.d.Exponent() + got := c.d.Normalize() + if got.Coefficient().String() != c.coef || got.Exponent() != c.exp { + t.Errorf("Normalize(%se%d): got %se%d, want %se%d", coef, exp, got.Coefficient(), got.Exponent(), c.coef, c.exp) + } + if c.d.Coefficient().String() != coef || c.d.Exponent() != exp { + t.Errorf("Normalize(%se%d) changed its receiver to %se%d", coef, exp, c.d.Coefficient(), c.d.Exponent()) + } + } + + one := NewFromInt(1) + for _, equal := range [][]Decimal{ + {Decimal{}, Zero, New(0, 0), NewFromFloat(0), RequireFromString("0.00"), one.Sub(one), RequireFromString("0.4").Round(0), RequireFromString("0.5").RoundDown(0)}, + {New(1, 1), RequireFromString("10"), NewFromFloat(10), RequireFromString("10.00"), NewFromInt(20).Div(NewFromInt(2)), RequireFromString("2.5").Mul(NewFromInt(4)), New(10000000000, -9)}, + {New(-15, -1), RequireFromString("-1.500"), NewFromFloat(-1.5), NewFromInt(-3).Div(NewFromInt(2))}, + } { + want := equal[0] + for _, d := range equal[1:] { + if got := d.Normalize(); !reflect.DeepEqual(got, want) { + t.Errorf("Normalize(%se%d): got %#v, want %#v", d.Coefficient(), d.Exponent(), got, want) + } + } + } +} + func TestDecimal_Div(t *testing.T) { type Inp struct { a string @@ -1955,7 +2253,7 @@ func TestDecimal_QuoRem(t *testing.T) { t.Errorf("remainder too large: d=%v, d2= %v, prec=%d, q=%v, r=%v", d, d2, prec, q, r) } - if r.value.Sign()*d.value.Sign() < 0 { + if r.Sign()*d.Sign() < 0 { t.Errorf("signum of divisor and rest do not match: d=%v, d2= %v, prec=%d, q=%v, r=%v", d, d2, prec, q, r) } @@ -2020,7 +2318,7 @@ func TestDecimal_QuoRem2(t *testing.T) { d, d2, prec, q, r) } // rule 4: r and d have the same sign - if r.value.Sign()*d.value.Sign() < 0 { + if r.Sign()*d.Sign() < 0 { t.Errorf("signum of divisor and rest do not match, "+ "d=%v, d2=%v, prec=%d, q=%v, r=%v", d, d2, prec, q, r) @@ -2047,7 +2345,7 @@ func (d Decimal) DivOld(d2 Decimal, prec int) Decimal { } func sign(d Decimal) int { - return d.value.Sign() + return d.Sign() } // rules for rounded divide, rounded to integer @@ -2164,13 +2462,17 @@ func TestDecimal_RoundCash(t *testing.T) { {"3.93", 100, "4.00"}, {"393", 100, "393"}, } - for i, test := range tests { - d, _ := NewFromString(test.d) - haveRounded := d.RoundCash(test.interval) - result, _ := NewFromString(test.result) + defer func(p int) { DivisionPrecision = p }(DivisionPrecision) + for _, precision := range []int{16, 0} { + DivisionPrecision = precision + for i, test := range tests { + d, _ := NewFromString(test.d) + haveRounded := d.RoundCash(test.interval) + result, _ := NewFromString(test.result) - if !haveRounded.Equal(result) { - t.Errorf("Index %d: Cash rounding for %q interval %d want %q, have %q", i, test.d, test.interval, test.result, haveRounded) + if !haveRounded.Equal(result) { + t.Errorf("Index %d, DivisionPrecision %d: Cash rounding for %q interval %d want %q, have %q", i, precision, test.d, test.interval, test.result, haveRounded) + } } } } @@ -2240,6 +2542,16 @@ func TestDecimal_Overflow(t *testing.T) { if !didPanic(func() { New(1, math.MaxInt32).Mul(New(1, math.MaxInt32)) }) { t.Fatalf("should have gotten an overflow panic") } + // -math.MinInt32 wraps back to itself as an int32. + if !didPanic(func() { New(1, math.MinInt32).QuoRem(New(1, 0), math.MinInt32) }) { + t.Fatalf("should have gotten an overflow panic") + } + if !didPanic(func() { New(1, math.MinInt32).DivRound(New(1, 0), math.MinInt32) }) { + t.Fatalf("should have gotten an overflow panic") + } + if q, _ := New(1, math.MaxInt32).QuoRem(New(1, 0), math.MinInt32+1); !q.Equal(New(1, math.MaxInt32)) { + t.Fatalf("precision math.MinInt32+1 should still work, got %s", q) + } } func TestDecimal_ExtremeValues(t *testing.T) { @@ -2361,6 +2673,36 @@ func TestBigFloat(t *testing.T) { } } +func TestBigFloatRoundTrip(t *testing.T) { + testCases := []string{ + "0", + "1", + "-1", + "0.5", + "-0.5", + "0.25", + "12.1", + "-12.1", + "123.456", + "-123.456", + } + + for _, s := range testCases { + d, err := NewFromString(s) + if err != nil { + t.Fatal(err) + } + bf := d.BigFloat() + roundTripD, err := NewFromBigFloat(bf) + if err != nil { + t.Fatalf("NewFromBigFloat failed for %s: %v", s, err) + } + if !d.Equal(roundTripD) { + t.Errorf("round trip failed: expected %s, got %s", d, roundTripD) + } + } +} + func TestDecimal_Min(t *testing.T) { // the first element in the array is the expected answer, rest are inputs testCases := [][]float64{ @@ -2440,7 +2782,7 @@ func TestDecimal_Scan(t *testing.T) { // apparently MySQL 5.7.16 and returns these as float32 so we need // to handle these as well dbvalueFloat32 := float32(54.33) - expected = NewFromFloat(float64(dbvalueFloat32)) + expected = RequireFromString("54.33") scanHelper(t, dbvalueFloat32, expected) // at least SQLite returns an int64 when 0 is stored in the db @@ -2471,6 +2813,54 @@ func TestDecimal_Scan(t *testing.T) { if err == nil { t.Errorf("a.Scan(Foo{}) should have thrown an error but did not") } + + if err := a.Scan(nil); err == nil || !strings.Contains(err.Error(), "NullDecimal") { + t.Errorf("a.Scan(nil): got %v, want an error that points to NullDecimal", err) + } +} + +func TestDecimal_ScanTypes(t *testing.T) { + int128, _ := new(big.Int).SetString("170141183460469231731687303715884105727", 10) + for _, c := range []struct { + value interface{} + want string + }{ + {int(-3264), "-3264"}, + {int8(-7), "-7"}, + {int16(300), "300"}, + {int32(-70000), "-70000"}, + {int64(math.MinInt64), "-9223372036854775808"}, + {uint(3264), "3264"}, + {uint8(200), "200"}, + {uint16(65535), "65535"}, + {uint32(4000000000), "4000000000"}, + {uint64(math.MaxUint64), "18446744073709551615"}, + {float32(-0.1), "-0.1"}, + {float32(math.MaxFloat32), strconv.FormatFloat(math.MaxFloat32, 'f', -1, 32)}, + {float32(math.SmallestNonzeroFloat32), strconv.FormatFloat(math.SmallestNonzeroFloat32, 'f', -1, 32)}, + {*int128, "170141183460469231731687303715884105727"}, + {int128, "170141183460469231731687303715884105727"}, + {New(2, 3), "2000"}, + } { + var d Decimal + if err := d.Scan(c.value); err != nil || d.String() != c.want { + t.Errorf("Scan(%T %v): got %s, %v, want %s", c.value, c.value, d, err, c.want) + } + var n NullDecimal + if err := n.Scan(c.value); err != nil || !n.Valid || n.Decimal.String() != c.want { + t.Errorf("NullDecimal.Scan(%T %v): got %v, %v, want %s", c.value, c.value, n, err, c.want) + } + } + if int128.String() != "170141183460469231731687303715884105727" { + t.Errorf("Scan changed the scanned *big.Int to %s", int128) + } + + for _, v := range []interface{}{(*big.Int)(nil), math.NaN(), math.Inf(1), float32(math.Inf(-1))} { + var n NullDecimal + if err := n.Scan(v); err == nil || n.Valid { + t.Errorf("NullDecimal.Scan(%T %v): got %v, %v, want an error", v, v, n, err) + } + } } func TestDecimal_Value(t *testing.T) { @@ -2491,6 +2881,213 @@ func TestDecimal_Value(t *testing.T) { } } +func decodeSpannerHelper(t *testing.T, dbval interface{}, expected Decimal) { + t.Helper() + + a := Decimal{} + if err := a.DecodeSpanner(dbval); err != nil { + // DecodeSpanner failed... no need to test result value + t.Errorf("a.DecodeSpanner(%v) failed with message: %s", dbval, err) + } else if !a.Equal(expected) { + // DecodeSpanner succeeded... test resulting values + t.Errorf("%s does not equal to %s", a, expected) + } +} + +type spannerDecoder interface { + DecodeSpanner(input interface{}) error +} + +func TestDecimal_DecodeSpanner(t *testing.T) { + // test the DecodeSpanner method that implements spanner.Decoder interface + if _, ok := interface{}(new(Decimal)).(spannerDecoder); !ok { + t.Error("Decimal does not implement spanner.Decoder") + } + + dbvalue := 54.33 + expected := NewFromFloat(dbvalue) + decodeSpannerHelper(t, dbvalue, expected) + + // also test uint64 + dbvalueUint64 := uint64(2) + expected = New(2, 0) + decodeSpannerHelper(t, dbvalueUint64, expected) + + // ensure we can handle the return of either []byte or string + valueStr := "535.666" + dbvalueStr := []byte(valueStr) + expected, err := NewFromString(valueStr) + if err != nil { + t.Fatal(err) + } + decodeSpannerHelper(t, dbvalueStr, expected) + decodeSpannerHelper(t, valueStr, expected) + + type foo struct{} + a := Decimal{} + err = a.DecodeSpanner(foo{}) + if err == nil { + t.Errorf("a.DecodeSpanner(Foo{}) should have thrown an error but did not") + } +} + +type spannerEncoder interface { + EncodeSpanner() (interface{}, error) +} + +func TestDecimal_EncodeSpanner(t *testing.T) { + // Make sure this does implement the spanner.Encoder interface + if _, ok := interface{}(Decimal{}).(spannerEncoder); !ok { + t.Error("Decimal does not implement spanner.Encoder") + } + + // check that normal case is handled appropriately + a := New(1234, -2) + expected := "12.34" + value, err := a.Value() + if err != nil { + t.Errorf("Decimal(12.34).Value() failed with message: %s", err) + } else if got := value.(string); got != expected { + t.Errorf("%s does not equal to %s", a, expected) + } +} + +type bsonValueMarshaler interface { + MarshalBSONValue() (byte, []byte, error) +} + +type bsonValueUnmarshaler interface { + UnmarshalBSONValue(byte, []byte) error +} + +var ( + _ bsonValueMarshaler = Decimal{} + _ bsonValueMarshaler = NullDecimal{} + _ bsonValueUnmarshaler = (*Decimal)(nil) + _ bsonValueUnmarshaler = (*NullDecimal)(nil) +) + +// decimal128Tests are canonical values from the BSON spec's Decimal128 test corpus. +var decimal128Tests = []struct { + dec string + hex string // the 16 bytes as stored in BSON +}{ + {"0", "00000000000000000000000000004030"}, + {"1", "01000000000000000000000000004030"}, + {"-1", "010000000000000000000000000040b0"}, + {"0.1", "01000000000000000000000000003e30"}, + {"1E+3", "01000000000000000000000000004630"}, + {"1234567890123456789012345678901234", "f2af967ed05c82de3297ff6fde3c4030"}, + {"9.999999999999999999999999999999999E+6144", "ffffffff638e8d37c087adbe09edff5f"}, + {"1.000000000000000000000000000000000E+6144", "000000000a5bc138938d44c64d31fe5f"}, + {"1E-6176", "01000000000000000000000000000000"}, + {"-1E-6176", "01000000000000000000000000000080"}, + {"0E-6176", "00000000000000000000000000000000"}, + {"0E+6111", "0000000000000000000000000000fe5f"}, +} + +func mustDecodeHex(t *testing.T, s string) []byte { + t.Helper() + b, err := hex.DecodeString(s) + if err != nil { + t.Fatal(err) + } + return b +} + +func TestDecimal_MarshalBSONValue(t *testing.T) { + type test struct { + name string + d Decimal + hex string + } + tests := []test{ + {"zero value", Decimal{}, "00000000000000000000000000004030"}, + {"41 digits drop trailing zeros", RequireFromString("1" + strings.Repeat("0", 40)), "000000000a5bc138938d44c64d314e30"}, + {"exponent above 6111 gains trailing zeros", RequireFromString("1E+6144"), "000000000a5bc138938d44c64d31fe5f"}, + {"exponent below -6176 drops trailing zeros", New(1000, -6179), "01000000000000000000000000000000"}, + {"zero with the largest exponent", New(0, math.MaxInt32), "0000000000000000000000000000fe5f"}, + {"zero with the smallest exponent", New(0, math.MinInt32), "00000000000000000000000000000000"}, + } + for _, c := range decimal128Tests { + tests = append(tests, test{c.dec, RequireFromString(c.dec), c.hex}) + } + for _, c := range tests { + typ, data, err := c.d.MarshalBSONValue() + if err != nil || typ != bsonTypeDecimal128 || hex.EncodeToString(data) != c.hex { + t.Errorf("MarshalBSONValue(%s): got %#x, %x, %v, want %#x, %s", c.name, typ, data, err, bsonTypeDecimal128, c.hex) + } + } + + for _, c := range []struct { + name string + d Decimal + }{ + {"35 significant digits", RequireFromString("1.2345678901234567890123456789012345")}, + {"below the smallest Decimal128", RequireFromString("1E-6177")}, + {"above the largest Decimal128", RequireFromString("1E+6145")}, + {"smallest int32 exponent", New(1, math.MinInt32)}, + } { + if _, _, err := c.d.MarshalBSONValue(); err == nil { + t.Errorf("MarshalBSONValue(%s): got no error", c.name) + } + } +} + +func TestDecimal_UnmarshalBSONValue(t *testing.T) { + type test struct { + name string + typ byte + hex string + want string + } + tests := []test{ + {"string", bsonTypeString, "04000000312e3500", "1.5"}, + {"double", bsonTypeDouble, "0000000000000440", "2.5"}, + {"int32", bsonTypeInt32, "f9ffffff", "-7"}, + {"int64", bsonTypeInt64, "0000000000010000", "1099511627776"}, + {"null", bsonTypeNull, "", "0"}, + {"undefined", bsonTypeUndefined, "", "0"}, + {"Decimal128 with an implied coefficient above 10^34 - 1", bsonTypeDecimal128, "ffffffffffffffffffffffffffff116c", "0E+3"}, + {"Decimal128 with the coefficient 10^34", bsonTypeDecimal128, "00000000648e8d37c087adbe09ed4130", "0"}, + } + for _, c := range decimal128Tests { + tests = append(tests, test{c.dec, bsonTypeDecimal128, c.hex, c.dec}) + } + for _, c := range tests { + want := RequireFromString(c.want) + got := New(5, 0) + err := got.UnmarshalBSONValue(c.typ, mustDecodeHex(t, c.hex)) + if err != nil || !got.Equal(want) || got.Exponent() != want.Exponent() { + t.Errorf("UnmarshalBSONValue(%s): got %se%d, %v, want %se%d", c.name, got.Coefficient(), got.Exponent(), err, want.Coefficient(), want.Exponent()) + } + } + + for _, c := range []struct { + name string + typ byte + hex string + }{ + {"Decimal128 NaN", bsonTypeDecimal128, "0000000000000000000000000000007c"}, + {"Decimal128 -Infinity", bsonTypeDecimal128, "000000000000000000000000000000f8"}, + {"Decimal128 of 15 bytes", bsonTypeDecimal128, "000000000000000000000000004030"}, + {"double NaN", bsonTypeDouble, "000000000000f87f"}, + {"double +Infinity", bsonTypeDouble, "000000000000f07f"}, + {"string abc", bsonTypeString, "0400000061626300"}, + {"boolean", 0x08, "01"}, + } { + if err := new(Decimal).UnmarshalBSONValue(c.typ, mustDecodeHex(t, c.hex)); err == nil { + t.Errorf("UnmarshalBSONValue(%s): got no error", c.name) + } + } + + defer func(limit int) { MaxDecodeExponent = limit }(MaxDecodeExponent) + MaxDecodeExponent = 100 + if err := new(Decimal).UnmarshalBSONValue(bsonTypeDecimal128, mustDecodeHex(t, "01000000000000000000000000000000")); err == nil { + t.Error("UnmarshalBSONValue(1E-6176): got no error with MaxDecodeExponent 100") + } +} + // old tests after this line func TestDecimal_Scale(t *testing.T) { @@ -2612,23 +3209,44 @@ func TestDecimal_Pow(t *testing.T) { {"4.0", "2.0", "16.0"}, {"4.0", "-2.0", "0.0625"}, {"629.25", "5.0", "98654323103449.5673828125"}, - {"5.0", "5.73", "10118.08037159375"}, - {"962.0", "3.2791", "6055212360.0000044205714144"}, - {"5.69169126", "5.18515912", "8242.26344757948412597909547972726268869189399260047793106028930864"}, - {"13.1337", "3.5196719618391835", "8636.856220644773844815693636723928750940666269885"}, - {"67762386.283696923", "4.85917691669163916681738", "112761146905370140621385730157437443321.91755738117317148674362233906499698561022574811238435007575701773212242750262081945556470501"}, + {"5.0", "5.73", "10118.0803715950193171"}, + {"962.0", "3.2791", "6055212360.0000044065516031"}, + {"5.69169126", "5.18515912", "8242.263447579484126"}, + {"13.1337", "3.5196719618391835", "8636.8562206447738448"}, + {"67762386.283696923", "4.85917691669163916681738", "112761146905370140621385730157437443321.9175573811731715"}, {"-3.0", "6.0", "729"}, {"-13.757", "5.0", "-492740.983929899460557"}, {"3.0", "-6.0", "0.0013717421124829"}, {"13.757", "-5.0", "0.000002029463821"}, - {"66.12", "-7.61313", "0.000000000000013854086588876805036"}, - {"6696871.12", "-2.61313", "0.000000000000000001455988684546983"}, + {"66.12", "-7.61313", "0.0000000000000139"}, + {"6696871.12", "-2.61313", "0.000000000000000001455825273411375"}, {"-3.0", "-6.0", "0.0013717421124829"}, {"-13.757", "-5.0", "-0.000002029463821"}, + {"-2", "-3", "-0.125"}, + {"0.5", "-20", "1048576"}, + {"2", "-17", "0.0000076293945313"}, + {"10", "-18", "0.000000000000000001"}, + {"100", "-17", "0.0000000000000000000000000000000001"}, + {"12345", "-5", "0.000000000000000000003487743424943423"}, + {"1000", "-6.5", "0.00000000000000000003162277660168379"}, + {"1.0041666666666667", "-360", "0.2238265956413493"}, + {"1.0041666666666667", "360.5", "4.4770424391814548"}, + {"0.01", "-7.585", "1479108388168207.4221993463468552"}, + {"-1.5", "-1001", "-5.403183104351711e-177"}, + {"-1.5", "-1000", "8.104774656527567e-177"}, + {"1.0000001", "-1000000000", "0.00000000000000000000000000000000000000000003720094576445977"}, + {"1.0000001", "1000000000.5", "26881038356701055136223322520735843418287670.4854123820834493"}, + {"100.0000000000000000", "0.5", "10"}, + {"4", "2.5", "32"}, + {"2", "1.1", "2.1435469250725863"}, + {"2", "0.5", "1.414213562373095"}, + {"1.28", "0.0833333333333333", "1.0207847284895002"}, + {"0.0208333333333333", "0.33", "0.2787342852450195"}, + {"1", "1" + strings.Repeat("0", 400) + ".5", "1"}, } { - base, _ := NewFromString(testCase.Base) - exp, _ := NewFromString(testCase.Exponent) - expected, _ := NewFromString(testCase.Expected) + base := RequireFromString(testCase.Base) + exp := RequireFromString(testCase.Exponent) + expected := RequireFromString(testCase.Expected) result := base.Pow(exp) @@ -2636,6 +3254,11 @@ func TestDecimal_Pow(t *testing.T) { t.Errorf("expected %s, got %s, for %s^%s", testCase.Expected, result.String(), testCase.Base, testCase.Exponent) } } + + // The expected exponent is beyond MaxDecodeExponent, so it can't be parsed from a string. + if got := New(15, -1).Pow(New(-100000, 0)); got.Cmp(New(7483321982518254, -17625)) != 0 { + t.Errorf("expected 7.483321982518254e-17610, got %s, for 1.5^-100000", got) + } } func TestDecimal_PowWithPrecision(t *testing.T) { @@ -2657,19 +3280,31 @@ func TestDecimal_PowWithPrecision(t *testing.T) { {"4.0", "-2.0", 2, "0.06"}, {"4.0", "-2.0", 4, "0.0625"}, {"629.25", "5.0", 6, "98654323103449.5673828125"}, - {"5.0", "5.73", 20, "10118.080371595019317118681359884375"}, - {"962.0", "3.2791", 15, "6055212360.000004406551603058195732"}, - {"5.69169126", "5.18515912", 4, "8242.26344757948412587366859330429895955552280978668983459852256"}, - {"13.1337", "3.5196719618391835", 8, "8636.85622064477384481569363672392591908386390769375"}, - {"67762386.283696923", "4.85917691669163916681738", 10, "112761146905370140621385730157437443321.917557381173174638304347353880676293576708009282115993465286373470882947470198597518762"}, + {"5.0", "5.73", 20, "10118.08037159501931711868"}, + {"962.0", "3.2791", 15, "6055212360.000004406551603"}, + {"5.69169126", "5.18515912", 4, "8242.2634"}, + {"13.1337", "3.5196719618391835", 8, "8636.85622064"}, + {"67762386.283696923", "4.85917691669163916681738", 10, "112761146905370140621385730157437443321.9175573812"}, {"-3.0", "6.0", 2, "729"}, {"-13.757", "5.0", 4, "-492740.983929899460557"}, {"3.0", "-6.0", 10, "0.0013717421"}, {"13.757", "-5.0", 20, "0.00000202946382098037"}, - {"66.12", "-7.61313", 20, "0.00000000000001385381563049821591633907104023700216"}, - {"6696871.12", "-2.61313", 24, "0.0000000000000000014558252733872790626400278983397459207418"}, + {"66.12", "-7.61313", 20, "0.00000000000001385382"}, + {"6696871.12", "-2.61313", 24, "0.000000000000000001455825"}, {"-3.0", "-6.0", 8, "0.00137174"}, {"-13.757", "-5.0", 16, "-0.000002029463821"}, + {"10", "-18", 5, "0.000000000000000001"}, + {"5.63379", "-6.438", 2, "0.000015"}, + {"1000", "10.5", 5, "31622776601683793319988935444327.18534"}, + {"123.456", "7.89", 10, "31771028258180977.3090686597"}, + {"0.3", "-2.5", 16, "20.2860206483394857"}, + {"6.25", "0.5", 0, "3"}, + {"6.24" + strings.Repeat("9", 200), "0.5", 0, "2"}, + {"1.0041666666666667", "-360", -1, "0.2"}, + {"0.0625", "0.5", 1, "0.3"}, + {"0.00000625", "0.5", 1, "0.003"}, + {"2", "0.5", -1, "1"}, + {"123456", "0.5", -2, "400"}, } { base, _ := NewFromString(testCase.Base) exp, _ := NewFromString(testCase.Exponent) @@ -2773,9 +3408,12 @@ func TestDecimal_PowInt32(t *testing.T) { {"-13.757", 5, "-492740.983929899460557"}, {"3.0", -6, "0.0013717421124829"}, {"-13.757", -5, "-0.000002029463821"}, + {"10", -18, "0.000000000000000001"}, + {"12345", -5, "0.000000000000000000003487743424943423"}, + {"-1.5", -1001, "-5.403183104351711e-177"}, } { - base, _ := NewFromString(testCase.Decimal) - expected, _ := NewFromString(testCase.Expected) + base := RequireFromString(testCase.Decimal) + expected := RequireFromString(testCase.Expected) result, _ := base.PowInt32(testCase.Exponent) @@ -2783,6 +3421,103 @@ func TestDecimal_PowInt32(t *testing.T) { t.Errorf("expected %s, got %s, for %s**%d", testCase.Expected, result.String(), testCase.Decimal, testCase.Exponent) } } + + // Exponents beyond MaxDecodeExponent can't be parsed, and String() on them is too slow for error messages. + for _, testCase := range []struct { + Decimal Decimal + Exponent int32 + Expected Decimal + }{ + {New(15, -1), math.MinInt32, New(4189793427303781, -378153115)}, + {New(3, 50000000), -1, New(3333333333333333, -50000016)}, + } { + result, err := testCase.Decimal.PowInt32(testCase.Exponent) + if err != nil || result.Cmp(testCase.Expected) != 0 { + t.Errorf("expected %de%d, got %de%d (err %v), for %de%d**%d", + testCase.Expected.Coefficient(), testCase.Expected.Exponent(), result.Coefficient(), result.Exponent(), err, + testCase.Decimal.Coefficient(), testCase.Decimal.Exponent(), testCase.Exponent) + } + } +} + +func TestDecimal_Pow_InexactResultExponent(t *testing.T) { + for _, testCase := range []struct { + Base string + Exponent string + }{ + {"3", "-6"}, + {"2", "0.5"}, + {"5.2", "6.3"}, + } { + result := RequireFromString(testCase.Base).Pow(RequireFromString(testCase.Exponent)) + if result.Exponent() != -int32(PowPrecisionNegativeExponent) { + t.Errorf("expected exponent %d, got %d, for %s^%s", -PowPrecisionNegativeExponent, result.Exponent(), testCase.Base, testCase.Exponent) + } + } +} + +func TestDecimal_Pow_RoundedToZeroResultExponent(t *testing.T) { + for _, testCase := range []struct { + Exponent int32 + Expected int32 + }{ + {-18, -33}, + {-1018, -1033}, + } { + result, _ := New(10, 0).PowInt32(testCase.Exponent) + if result.Exponent() != testCase.Expected { + t.Errorf("expected exponent %d, got %d, for 10**%d", testCase.Expected, result.Exponent(), testCase.Exponent) + } + } +} + +func TestDecimal_PowWithPrecision_HighPrecision(t *testing.T) { + two := New(2, 0) + for _, precision := range []int32{700, 3500} { + result, _ := two.PowWithPrecision(RequireFromString("0.5"), precision) + half := New(5, -precision-1) + lo, hi := result.Sub(half), result.Add(half) + if result.Exponent() != -precision || lo.Mul(lo).Cmp(two) >= 0 || hi.Mul(hi).Cmp(two) <= 0 { + t.Errorf("2^0.5 is not correctly rounded to %d places", precision) + } + } +} + +func TestDecimal_Pow_OutOfRange(t *testing.T) { + for _, testCase := range []struct { + Base string + Exponent string + }{ + {"10", "3000000000.5"}, + {"1.5", "-1000000000000"}, + {"0.5", "-4000000000.5"}, + {"0.1", "3000000000"}, + } { + base, exp := RequireFromString(testCase.Base), RequireFromString(testCase.Exponent) + + if _, err := base.PowWithPrecision(exp, 16); err == nil { + t.Errorf("expected out of range error for %s^%s", testCase.Base, testCase.Exponent) + } + if result := base.Pow(exp); !result.IsZero() { + t.Errorf("expected zero value for %s^%s, got %s", testCase.Base, testCase.Exponent, result) + } + } + + if _, err := RequireFromString("1.5").PowBigInt(big.NewInt(-1000000000000)); err == nil { + t.Errorf("expected out of range error for 1.5**-1000000000000") + } + if _, err := New(1, -2).PowInt32(1<<30 + 1); err == nil { + t.Errorf("expected out of range error for 0.01**1073741825") + } +} + +func TestDecimal_PowInt32_ZeroNegativeExponent(t *testing.T) { + if _, err := Zero.PowInt32(-2); err == nil { + t.Errorf("expected error, cannot represent infinity value of 0 ** y, where y < 0") + } + if _, err := Zero.PowBigInt(big.NewInt(-2)); err == nil { + t.Errorf("expected error, cannot represent infinity value of 0 ** y, where y < 0") + } } func TestDecimal_PowInt32_UndefinedResult(t *testing.T) { @@ -2811,6 +3546,11 @@ func TestDecimal_PowBigInt(t *testing.T) { {"-13.757", big.NewInt(5), "-492740.983929899460557"}, {"3.0", big.NewInt(-6), "0.0013717421124829"}, {"-13.757", big.NewInt(-5), "-0.000002029463821"}, + {"10", big.NewInt(-18), "0.000000000000000001"}, + {"12345", big.NewInt(-5), "0.000000000000000000003487743424943423"}, + {"-1.5", big.NewInt(-1001), "-5.403183104351711e-177"}, + {"1.0000000000000000000000000000001", big.NewInt(-100000000000000000), "0.99999999999999"}, + {"-1", new(big.Int).Neg(new(big.Int).Exp(big.NewInt(10), big.NewInt(30), nil)), "1"}, } { base, _ := NewFromString(testCase.Decimal) expected, _ := NewFromString(testCase.Expected) @@ -2833,6 +3573,140 @@ func TestDecimal_PowBigInt_UndefinedResult(t *testing.T) { } } +func TestDecimal_SqrtRound(t *testing.T) { + for _, c := range []struct { + in string + places int32 + want string + }{ + {"0", 16, "0"}, + {"1", 16, "1"}, + {"4", 16, "2"}, + {"1.44", 16, "1.2"}, + {"3", 16, "1.7320508075688773"}, + {"4.5", 16, "2.1213203435596426"}, + {"0.002342", 16, "0.0483942145302514"}, + {"0.002342", 4, "0.0484"}, + {"0.002342", 32, "0.04839421453025144409653399922499"}, + {"3289854", 16, "1813.7954680724064484"}, + {"0.0025", 1, "0.1"}, + {"12345", -1, "110"}, + {"1e40", 0, "1e20"}, + {"1e-40", 16, "0"}, + {"1e-9000", 16, "0"}, + } { + got, err := RequireFromString(c.in).SqrtRound(c.places) + if want := RequireFromString(c.want); err != nil || !got.Equal(want) || got.Exponent() != -c.places { + t.Errorf("SqrtRound(%s, %d): got %s (exponent %d), %v, want %s", c.in, c.places, got, got.Exponent(), err, want) + } + } + + if got, err := New(1, math.MinInt32).SqrtRound(16); err != nil || !got.IsZero() { + t.Errorf("SqrtRound(1e%d, 16): got %s, %v, want 0", math.MinInt32, got, err) + } + for _, d := range []Decimal{NewFromInt(-1), RequireFromString("-0.0001")} { + if _, err := d.SqrtRound(16); err == nil { + t.Errorf("SqrtRound(%s, 16): got no error", d) + } + } + + // r is the correctly rounded root of d exactly when (r - u/2)^2 <= d < (r + u/2)^2, with u = 10^-places + r := rand.New(rand.NewSource(1)) + for i := 0; i < 2000; i++ { + d := NewFromBigInt(new(big.Int).Rand(r, pow10(int64(1+r.Intn(40)))), int32(r.Intn(80)-50)) + places := int32(r.Intn(46) - 5) + got, err := d.SqrtRound(places) + half := New(5, -places-1) + lo, hi := got.Sub(half), got.Add(half) + if err != nil || got.Sign() > 0 && lo.Mul(lo).GreaterThan(d) || hi.Mul(hi).LessThanOrEqual(d) { + t.Fatalf("SqrtRound(%s, %d): got %s, %v, which isn't the correctly rounded root", d, places, got, err) + } + } +} + +func TestDecimal_Sqrt(t *testing.T) { + if got, err := NewFromInt(2).Sqrt(); err != nil || got.String() != "1.414213562373095" || got.Exponent() != -16 { + t.Errorf("Sqrt(2): got %s (exponent %d), %v", got, got.Exponent(), err) + } + + defer func(prec int) { DivisionPrecision = prec }(DivisionPrecision) + DivisionPrecision = 4 + if got, err := NewFromInt(2).Sqrt(); err != nil || got.String() != "1.4142" { + t.Errorf("Sqrt(2) with DivisionPrecision 4: got %s, %v", got, err) + } + if _, err := NewFromInt(-4).Sqrt(); err == nil { + t.Error("Sqrt(-4): got no error") + } +} + +func TestDecimal_RootRound(t *testing.T) { + for _, c := range []struct { + in string + n int64 + places int32 + want string + }{ + {"27", 3, 16, "3"}, + {"2", 3, 16, "1.2599210498948732"}, + {"-8", 3, 16, "-2"}, + {"-2", 3, 5, "-1.25992"}, + {"16", 4, 16, "2"}, + {"32", 5, 0, "2"}, + {"0", 5, 16, "0"}, + {"1e-30", 3, 16, "0.0000000001"}, + {"0.001", 3, 2, "0.1"}, + {"0.125", 3, 0, "1"}, + {"-0.125", 3, 0, "-1"}, + {"1000000", 3, -1, "100"}, + {"2", 1, 3, "2"}, + {"5e-150", 10, 14, "0"}, + } { + got, err := RequireFromString(c.in).RootRound(c.n, c.places) + if want := RequireFromString(c.want); err != nil || !got.Equal(want) || got.Exponent() != -c.places { + t.Errorf("RootRound(%s, %d, %d): got %s (exponent %d), %v, want %s", c.in, c.n, c.places, got, got.Exponent(), err, want) + } + } + + for _, c := range []struct { + d Decimal + n int64 + }{{NewFromInt(2), 0}, {NewFromInt(2), -3}, {NewFromInt(-4), 2}, {NewFromInt(-16), 4}} { + if _, err := c.d.RootRound(c.n, 16); err == nil { + t.Errorf("RootRound(%s, %d, 16): got no error", c.d, c.n) + } + } + + // r is the correctly rounded root of d exactly when (r - u/2)^n <= d < (r + u/2)^n, with u = 10^-places + r := rand.New(rand.NewSource(1)) + for i := 0; i < 2000; i++ { + d := NewFromBigInt(new(big.Int).Rand(r, pow10(int64(1+r.Intn(30)))), int32(r.Intn(60)-40)) + n := int64(1 + r.Intn(7)) + places := int32(r.Intn(33) - 2) + got, err := d.RootRound(n, places) + half := New(5, -places-1) + lo, _ := got.Sub(half).PowInt32(int32(n)) + hi, _ := got.Add(half).PowInt32(int32(n)) + if err != nil || got.Sign() > 0 && lo.GreaterThan(d) || hi.LessThanOrEqual(d) { + t.Fatalf("RootRound(%s, %d, %d): got %s, %v, which isn't the correctly rounded root", d, n, places, got, err) + } + if sqrt, _ := d.SqrtRound(places); n == 2 && !got.Equal(sqrt) { + t.Fatalf("RootRound(%s, 2, %d) = %s, SqrtRound gives %s", d, places, got, sqrt) + } + } +} + +func TestDecimal_Root(t *testing.T) { + if got, err := NewFromInt(2).Root(3); err != nil || got.String() != "1.2599210498948732" || got.Exponent() != -16 { + t.Errorf("Root(2, 3): got %s (exponent %d), %v", got, got.Exponent(), err) + } + + defer func(prec int) { DivisionPrecision = prec }(DivisionPrecision) + DivisionPrecision = 4 + if got, err := NewFromInt(2).Root(3); err != nil || got.String() != "1.2599" { + t.Errorf("Root(2, 3) with DivisionPrecision 4: got %s, %v", got, err) + } +} + func TestDecimal_IsInteger(t *testing.T) { for _, testCase := range []struct { Dec string @@ -2867,6 +3741,15 @@ func TestDecimal_ExpHullAbrham(t *testing.T) { OverallPrecision uint32 ExpectedDec string }{ + {"0.000001", 4, "1"}, + {"-0.000001", 4, "1"}, + {"0.00000100", 4, "1"}, + {"-0.00000100", 4, "1"}, + {"0.00009", 4, "1"}, + {"0.000000000001", 8, "1"}, + {"-0.000000000001", 8, "1"}, + {"0.001", 4, "1.001"}, + {"-0.001", 4, "0.999"}, {"0", 1, "1"}, {"0.00", 5, "1"}, {"0.5", 5, "1.6487"}, @@ -2895,17 +3778,20 @@ func TestDecimal_ExpHullAbrham(t *testing.T) { {"-0.569297", 20, "0.56592314285957604443"}, {"-1.0", 1, "0.4"}, {"-1.0", 5, "0.36788"}, - {"-3.0", 1, "0"}, + {"-3.0", 1, "0.05"}, {"-3.0", 2, "0.05"}, - {"-3.0", 10, "0.0497870684"}, - {"-5.26", 2, "0.01"}, - {"-5.26", 10, "0.0051953047"}, - {"-5.2663117716", 2, "0.01"}, - {"-5.2663117716", 10, "0.0051626164"}, - {"-26.1", 2, "0"}, - {"-26.1", 15, "0.000000000004623"}, - {"-50.1591", 10, "0"}, - {"-50.1591", 30, "0.000000000000000000000164505208"}, + {"-3.0", 10, "0.04978706837"}, + {"-5.26", 2, "0.0052"}, + {"-5.26", 10, "0.005195304719"}, + {"-5.2663117716", 2, "0.0052"}, + {"-5.2663117716", 10, "0.005162616411"}, + {"-26.1", 2, "0.0000000000046"}, + {"-26.1", 15, "0.00000000000462289492466867"}, + {"-50.1591", 10, "0.0000000000000000000001645052084"}, + {"-50.1591", 30, "0.000000000000000000000164505208424152908216198963722"}, + {"23.31", 1, "10000000000"}, + {"50", 2, "5200000000000000000000"}, + {"-50", 2, "0.00000000000000000000019"}, } { d, _ := NewFromString(testCase.Dec) expected, _ := NewFromString(testCase.ExpectedDec) @@ -2922,6 +3808,43 @@ func TestDecimal_ExpHullAbrham(t *testing.T) { } } +// ExpHullAbrham is documented to return overallPrecision significant figures. +// It used to round the result to overallPrecision decimal places whenever the +// result was < 1, discarding the leading-zero positions, so every sufficiently +// negative argument lost significant figures (exp(-50).ExpHullAbrham(10) +// returned 0). Check the delivered precision against exp(x) computed to 40 +// digits with mpmath, across the negative half-line. +func TestDecimal_ExpHullAbrham_significantFigures(t *testing.T) { + trueExp := map[string]string{ + "-2.5": "8.20849986238987951695286744671598078378041e-2", + "-5": "6.73794699908546709663604842314842424884959e-3", + "-10": "4.53999297624848515355915155605506102379181e-5", + "-20": "2.06115362243855782796594038015582097637581e-9", + "-30": "9.35762296884017460491583222337870674495832e-14", + "-50": "1.92874984796391778301734281652701257475283e-22", + } + for _, x := range []string{"-2.5", "-5", "-10", "-20", "-30", "-50"} { + d := RequireFromString(x) + want := RequireFromString(trueExp[x]) + for _, prec := range []uint32{10, 20, 30} { + got, err := d.ExpHullAbrham(prec) + if err != nil { + t.Fatalf("exp(%s).ExpHullAbrham(%d): %v", x, prec, err) + } + if got.IsZero() { + t.Errorf("exp(%s).ExpHullAbrham(%d) = 0, want ~%s", x, prec, want) + continue + } + relErr := got.Sub(want).Abs().DivRound(want, 45) + tol := New(1, -int32(prec)+1) // one unit in the prec-th significant figure + if relErr.Cmp(tol) > 0 { + t.Errorf("exp(%s).ExpHullAbrham(%d) = %s: relative error %s > %s, fewer than %d significant figures", + x, prec, got, relErr, tol, prec) + } + } + } +} + func TestDecimal_ExpTaylor(t *testing.T) { for _, testCase := range []struct { Dec string @@ -2990,6 +3913,88 @@ func TestDecimal_ExpTaylor(t *testing.T) { } } +// TestDecimal_ExpTaylor_Concurrency tests the concurrency safety of the +// ExpTaylor method, particularly focusing on the shared state caching mechanism +// for factorial calculations. +func TestDecimal_ExpTaylor_Concurrency(t *testing.T) { + resetFactorials := func() { + factorialsMutex.Lock() + factorials = []Decimal{New(1, 0)} + factorialsMutex.Unlock() + } + t.Run("concurrent calculation works as expected", func(t *testing.T) { + resetFactorials() + + // Run multiple goroutines calculating exp concurrently + // This should trigger concurrent factorial calculations. + var wg sync.WaitGroup + numGoroutines := 50 + results := make([]Decimal, numGoroutines) + errors := make([]error, numGoroutines) + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + d := NewFromFloat(5.0) + result, err := d.ExpTaylor(10) + results[index] = result + errors[index] = err + }(i) + } + + wg.Wait() + + // Check all goroutines succeeded. + for i, err := range errors { + if err != nil { + t.Errorf("goroutine %d: ExpTaylor failed: %v", i, err) + } + } + + // All results should be identical. + expected := results[0] + for i, result := range results[1:] { + if !result.Equal(expected) { + t.Errorf("goroutine %d: result mismatch: expected %s, got %s", i+1, expected.String(), result.String()) + } + } + + // Verify factorials slice doesn't have duplicate Zero values + // (which would indicate the double-check locking failed). + factorialsMutex.RLock() + for i, f := range factorials { + if i > 0 && f.IsZero() { + t.Errorf("factorial at index %d is Zero, double-check locking may have failed", i) + } + } + factorialsMutex.RUnlock() + }) + + t.Run("race detection", func(t *testing.T) { + // NOTE: Factorials from previous subtests are intentionally reused here. + // This tests that concurrent access works correctly with a pre-populated cache, + // which is the real-world scenario. Run with `go test -race`. + + var wg sync.WaitGroup + numGoroutines := 100 + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(val float64) { + defer wg.Done() + d := NewFromFloat(val) + _, err := d.ExpTaylor(10) + if err != nil { + t.Errorf("ExpTaylor failed for value %f: %v", val, err) + } + }(float64(i%10) + 0.5) + } + + wg.Wait() + }) +} + func TestDecimal_Ln(t *testing.T) { for _, testCase := range []struct { Dec string @@ -3012,6 +4017,11 @@ func TestDecimal_Ln(t *testing.T) { {"3.13", 100, "1.1410330045520618486427823998884819383708962910797239760817078430268177216960996098918971117211892839"}, {"5.71", 25, "1.7422190236679188486939833"}, {"5.7185108151957193571930205", 50, "1.74370842450178929149992165925283704012576949094645"}, + {"10", 5, "2.30259"}, + // e^2.5 truncated, ln is 2.5 - 5e-45, so the first working precisions can't decide the rounding + {"12.18249396070347343807017595116796618318276779", 0, "2"}, + {"1e-28", 16, "-64.4723826038332792"}, + {"1e-34", 13, "-78.2878931617976"}, {"839101.0351", 25, "13.6400864014410013994397240"}, {"839101.0351094726488848490572028502", 50, "13.64008640145229044389152437468283605382056561604272"}, {"5023583755703750094849.03519358513093500275017501750602739169823", 25, "49.9684305274348922267409953"}, @@ -3032,6 +4042,19 @@ func TestDecimal_Ln(t *testing.T) { } } +// ln(10^k) is k*ln(10), and strLn10 has far more digits than any rounding below needs. +func TestDecimal_LnPowersOfTen(t *testing.T) { + ln10 := RequireFromString(strLn10) + for k := int32(-40); k <= 40; k++ { + for p := int32(0); p <= 30; p++ { + want := ln10.Mul(New(int64(k), 0)).Round(p) + if got, err := New(1, k).Ln(p); err != nil || !got.Equal(want) { + t.Errorf("Ln(1e%d, %d): got %s, %v, want %s", k, p, got, err, want) + } + } + } +} + func TestDecimal_LnZero(t *testing.T) { d := New(0, 0) @@ -3072,6 +4095,14 @@ func TestDecimal_NumDigits(t *testing.T) { {"-5.2663117716", 11}, {"-26.1", 3}, {"", 1}, + // Regression for #420: float64 math.Log10 rounds 1eN down to + // N-1 for some N (e.g. log10(1e15) == 14.999...), so the old + // fast path under-reported by one for exact-power coefficients. + {"1000000000000000", 16}, + {"-1000000000000000", 16}, + {"1000000000000000E20", 16}, + {"9007199254740992", 16}, // 1<<53, the old fast-path boundary + {"-9007199254740992", 16}, } { d, _ := NewFromString(testCase.Dec) @@ -3080,6 +4111,45 @@ func TestDecimal_NumDigits(t *testing.T) { t.Errorf("expected %d digits for decimal %s, got %d", testCase.ExpectedNumDigits, testCase.Dec, nums) } } + + // every power of ten and of two in int64, and their neighbours + values := []int64{math.MaxInt64, math.MinInt64} + for p := int64(1); p <= math.MaxInt64/10; p *= 10 { + values = append(values, p-1, p, p+1, -p+1, -p, -p-1) + } + for j := uint(0); j < 63; j++ { + p := int64(1) << j + values = append(values, p-1, p, p+1, -p+1, -p, -p-1) + } + for _, v := range values { + want := len(strconv.FormatInt(v, 10)) + if v < 0 { + want-- + } + if got := New(v, 0).NumDigits(); got != want { + t.Errorf("expected %d digits for %d, got %d", want, v, got) + } + } +} + +func TestDecimal_Float64MatchesRat(t *testing.T) { + // coefficients and exponents in the range of the Float64 fast path + rng := rand.New(rand.NewSource(7)) + for i := 0; i < 100000; i++ { + v := rng.Int63n(1<<54+1) - 1<<53 + if i%3 == 0 { + v /= 1e10 // shorter coefficients, so more exact results + } + d := New(v, -int32(rng.Intn(23))) + f, exact := d.Float64() + wantF, wantExact := d.Rat().Float64() + if v == 0 { + wantF, wantExact = 0, true + } + if math.Float64bits(f) != math.Float64bits(wantF) || exact != wantExact { + t.Fatalf("%s.Float64() = %v, %v; want %v, %v", d, f, exact, wantF, wantExact) + } + } } func TestDecimal_Sign(t *testing.T) { @@ -3287,6 +4357,135 @@ func TestNullDecimal_Value(t *testing.T) { } } +func TestNullDecimal_DecodeSpanner(t *testing.T) { + // test the DecodeSpanner method that implements the + // spanner.Decoder interface + if _, ok := interface{}(new(NullDecimal)).(spannerDecoder); !ok { + t.Error("NullDecimal does not implement spanner.Decoder") + } + + // Make sure handles nil value + a := NullDecimal{} + var dbvaluePtr interface{} + err := a.DecodeSpanner(dbvaluePtr) + if err != nil { + // DecodeSpanner failed... no need to test result value + t.Errorf("a.DecodeSpanner(nil) failed with message: %s", err) + } else { + if a.Valid { + t.Errorf("%s is not null", a.Decimal) + } + } + + // Make sure handles nil *string + dbvaluePtr = (*string)(nil) + if err := a.DecodeSpanner(dbvaluePtr); err != nil { + // DecodeSpanner failed... no need to test result value + t.Errorf("a.DecodeSpanner((*string)(nil)) failed with message: %s", err) + } else { + if a.Valid { + t.Errorf("%s is not null", a.Decimal) + } + } + + valueStr := "535.666" + expected, err := NewFromString(valueStr) + if err != nil { + t.Fatal(err) + } + + // Handle string + err = a.DecodeSpanner(valueStr) + if err != nil { + // DecodeSpanner failed... no need to test result value + t.Errorf("a.DecodeSpanner('535.666') failed with message: %s", err) + } else { + // DecodeSpanner succeeded... test resulting values + if !a.Valid { + t.Errorf("%s is null", a.Decimal) + } else if !a.Decimal.Equals(expected) { + t.Errorf("%v does not equal %v", a, expected) + } + } + + // handle *string + err = a.DecodeSpanner(&valueStr) + if err != nil { + // DecodeSpanner failed... no need to test result value + t.Errorf("a.DecodeSpanner('535.666') failed with message: %s", err) + } else { + // DecodeSpanner succeeded... test resulting values + if !a.Valid { + t.Errorf("%s is null", a.Decimal) + } else if !a.Decimal.Equals(expected) { + t.Errorf("%v does not equal %v", a, expected) + } + } +} + +func TestNullDecimal_EncodeSpanner(t *testing.T) { + // Make sure this does implement the spanner.Encoder interface + var nullDecimal NullDecimal + if _, ok := interface{}(nullDecimal).(spannerEncoder); !ok { + t.Error("NullDecimal does not implement spanner.Encoder") + } + + // check that null is handled appropriately + value, err := nullDecimal.EncodeSpanner() + if err != nil { + t.Errorf("NullDecimal{}.Valid() failed with message: %s", err) + } else if value != nil { + t.Errorf("%v is not nil", value) + } + + // check that normal case is handled appropriately + a := NullDecimal{Decimal: New(1234, -2), Valid: true} + expected := "12.34" + value, err = a.EncodeSpanner() + if err != nil { + t.Errorf("NullDecimal(12.34).EncodeSpanner() failed with message: %s", err) + } else if value.(string) != expected { + t.Errorf("%v does not equal %v", a, expected) + } +} + +func TestNullDecimal_MarshalBSONValue(t *testing.T) { + for _, c := range []struct { + d NullDecimal + typ byte + hex string + }{ + {NullDecimal{}, bsonTypeNull, ""}, + {NewNullDecimal(New(1, 3)), bsonTypeDecimal128, "01000000000000000000000000004630"}, + } { + typ, data, err := c.d.MarshalBSONValue() + if err != nil || typ != c.typ || hex.EncodeToString(data) != c.hex { + t.Errorf("MarshalBSONValue(%v): got %#x, %x, %v, want %#x, %s", c.d, typ, data, err, c.typ, c.hex) + } + } +} + +func TestNullDecimal_UnmarshalBSONValue(t *testing.T) { + for _, c := range []struct { + name string + typ byte + hex string + want string // "" if the result is invalid + wantErr bool + }{ + {"Decimal128", bsonTypeDecimal128, "01000000000000000000000000004630", "1000", false}, + {"null", bsonTypeNull, "", "", false}, + {"undefined", bsonTypeUndefined, "", "", false}, + {"boolean", 0x08, "01", "", true}, + } { + got := NewNullDecimal(New(5, 0)) + err := got.UnmarshalBSONValue(c.typ, mustDecodeHex(t, c.hex)) + if (err != nil) != c.wantErr || got.Valid != (c.want != "") || got.Valid && got.Decimal.String() != c.want { + t.Errorf("UnmarshalBSONValue(%s): got %v, %v, want %q and error=%v", c.name, got, err, c.want, c.wantErr) + } + } +} + func TestBinary(t *testing.T) { for _, y := range testTable { x := y.float @@ -3417,6 +4616,17 @@ func TestSum(t *testing.T) { if !sum.Equal(New(45, 0)) { t.Errorf("Failed to calculate sum, expected %s got %s", New(45, 0), sum) } + + // reflect.DeepEqual, used by testify's assert.Equal, also compares the big.Int internals + if s := Sum(New(5, -1), New(-5, -1), Zero); !reflect.DeepEqual(s, New(0, -1)) { + t.Errorf("Sum(0.5, -0.5, 0) = %#v, want the same representation as New(0, -1)", s) + } + if s := Zero.Shift(2); !reflect.DeepEqual(s, New(0, 2)) { + t.Errorf("Zero.Shift(2) = %#v, want the same representation as New(0, 2)", s) + } + if s := New(7, 0).Sub(New(7, 0)).Shift(2); !reflect.DeepEqual(s, New(0, 2)) { + t.Errorf("(7 - 7).Shift(2) = %#v, want the same representation as New(0, 2)", s) + } } func TestAvg(t *testing.T) { @@ -3628,22 +4838,391 @@ func TestNewNullDecimal(t *testing.T) { } } +func TestDecimal_String(t *testing.T) { + type testData struct { + input string + expected string + } + + tests := []testData{ + {"1.22", "1.22"}, + {"1.00", "1"}, + {"153.192", "153.192"}, + {"999.999", "999.999"}, + {"0.0000000001", "0.0000000001"}, + {"0.0000000000", "0"}, + } + + for _, test := range tests { + d, err := NewFromString(test.input) + if err != nil { + t.Fatal(err) + } else if d.String() != test.expected { + t.Errorf("expected %s, got %s", test.expected, d.String()) + } + } +} + +func TestDecimal_StringWithTrailing(t *testing.T) { + type testData struct { + input string + expected string + } + + defer func() { + TrimTrailingZeros = true + }() + + TrimTrailingZeros = false + tests := []testData{ + {"1.00", "1.00"}, + {"0.00", "0.00"}, + {"129.123000", "129.123000"}, + {"1.0000E3", "1000.0"}, // 1000 to the nearest tenth + {"10000E-1", "1000.0"}, // 1000 to the nearest tenth + } + + for _, test := range tests { + d, err := NewFromString(test.input) + if err != nil { + t.Fatal(err) + } else if d.String() != test.expected { + x := d.String() + fmt.Println(x) + t.Errorf("expected %s, got %s", test.expected, d.String()) + } + } +} + +func TestDecimal_StringWithScientificNotationWhenNeeded(t *testing.T) { + type testData struct { + input string + expected string + } + + defer func() { + UseScientificNotation = false + }() + UseScientificNotation = true + + tests := []testData{ + {"1.0E3", "1.0E3"}, // 1000 to the nearest hundred + {"1.00E3", "1.00E3"}, // 1000 to the nearest ten + {"1.000E3", "1000"}, // 1000 to the nearest one + {"1E3", "1E3"}, // 1000 to the nearest thousand + {"-1E3", "-1E3"}, // -1000 to the nearest thousand + } + + for _, test := range tests { + d, err := NewFromString(test.input) + if err != nil { + t.Fatal(err) + } else if d.String() != test.expected { + x := d.String() + fmt.Println(x) + t.Errorf("expected %s, got %s", test.expected, d.String()) + } + } +} + +func TestDecimal_Format(t *testing.T) { + pi := RequireFromString("3.14159") + for i, c := range []struct { + format string + v interface{} + want string + }{ + {"%v", pi, "3.14159"}, + {"%s", pi, "3.14159"}, + {"%v", Decimal{}, "0"}, + {"%10v", pi, " 3.14159"}, + {"%-10s|", pi, "3.14159 |"}, + {"%.3s", pi, "3.1"}, + {"%q", pi, `"3.14159"`}, + {"%x", pi, "332e3134313539"}, + {"%v", &pi, "3.14159"}, + {"%v", (*Decimal)(nil), ""}, + {"%v", []Decimal{pi, pi.Neg()}, "[3.14159 -3.14159]"}, + {"%+v", NewNullDecimal(pi), "{Decimal:3.14159 Valid:true}"}, + + {"%f", RequireFromString("1.23456789"), "1.23456789"}, + {"%F", pi, "3.14159"}, + {"%f", New(150, -2), "1.5"}, + {"%f", New(12, 3), "12000"}, + {"%.2f", pi, "3.14"}, + {"%.0f", pi, "3"}, + {"%.7f", pi, "3.1415900"}, + {"%.1f", RequireFromString("2.25"), "2.3"}, + {"%.1f", RequireFromString("-2.25"), "-2.3"}, + {"%.2f", RequireFromString("-0.001"), "0.00"}, + {"%2.2f", pi, "3.14"}, + {"%8.2f", pi, " 3.14"}, + {"%-8.2f|", pi, "3.14 |"}, + {"%08.2f", pi, "00003.14"}, + {"%08.2f", pi.Neg(), "-0003.14"}, + {"%-08.2f|", pi, "3.14 |"}, + {"%+.2f", pi, "+3.14"}, + {"%+.2f", pi.Neg(), "-3.14"}, + {"%+08.2f", pi, "+0003.14"}, + {"% .2f", pi, " 3.14"}, + + {"%#v", New(314, -2), "decimal.New(314, -2)"}, + {"%#v", New(-1500, -3), "decimal.New(-1500, -3)"}, + {"%#v", Decimal{}, "decimal.Decimal{}"}, + {"%#v", New(1, math.MaxInt32), "decimal.New(1, 2147483647)"}, + {"%#v", RequireFromString("-12345678901234567890123456789012").Shift(-2), `decimal.RequireFromString("-12345678901234567890123456789012").Shift(-2)`}, + {"%#v", RequireFromString("12345678901234567890").Shift(20000), `decimal.RequireFromString("12345678901234567890").Shift(20000)`}, + {"%#v", NewNullDecimal(New(314, -2)), "decimal.NullDecimal{Decimal:decimal.New(314, -2), Valid:true}"}, + + {"%d", pi, "%!d(decimal.Decimal=3.14159)"}, + {"%e", pi, "%!e(decimal.Decimal=3.14159)"}, + } { + if got := fmt.Sprintf(c.format, c.v); got != c.want { + t.Errorf("Sprintf(%q) of case %d: got %q, want %q", c.format, i, got, c.want) + } + } + + for _, c := range []struct { + got, want string + }{ + {fmt.Sprintln(pi, pi.Neg()), "3.14159 -3.14159\n"}, + {fmt.Sprintf("%8d|%f|%.2f|", 1, pi, pi), " 1|3.14159|3.14|"}, + } { + if c.got != c.want { + t.Errorf("got %q, want %q", c.got, c.want) + } + } +} + +func TestDecimal_FormatWithStringSettings(t *testing.T) { + defer func() { + TrimTrailingZeros = true + UseScientificNotation = false + }() + TrimTrailingZeros = false + UseScientificNotation = true + + for _, c := range []struct { + format string + d Decimal + want string + }{ + {"%v", New(150, -2), "1.50"}, + {"%f", New(150, -2), "1.50"}, + {"%v", New(1, 3), "1E3"}, + {"%f", New(1, 3), "1000"}, + } { + if got := fmt.Sprintf(c.format, c.d); got != c.want { + t.Errorf("Sprintf(%q, %se%d): got %q, want %q", c.format, c.d.Coefficient(), c.d.Exponent(), got, c.want) + } + } +} + +func TestDecimal_ScientificNotation(t *testing.T) { + type testData struct { + input string + expected string + } + + tests := []testData{ + {"1", "1E0"}, + {"1.0", "1.0E0"}, + {"10", "1.0E1"}, + {"123", "1.23E2"}, + {"1234", "1.234E3"}, + {"-1", "-1E0"}, + {"-10", "-1.0E1"}, + {"-123", "-1.23E2"}, + {"-1234", "-1.234E3"}, + {"0.1", "1E-1"}, + {"0.01", "1E-2"}, + {"0.123", "1.23E-1"}, + {"1.23", "1.23E0"}, + {"-0.1", "-1E-1"}, + {"-0.01", "-1E-2"}, + {"-0.010", "-1.0E-2"}, + {"-0.123", "-1.23E-1"}, + {"-1.23", "-1.23E0"}, + {"1E6", "1E6"}, + {"1e6", "1E6"}, + {"1.23E6", "1.23E6"}, + {"-1E6", "-1E6"}, + {"1E-6", "1E-6"}, + {"1.23E-6", "1.23E-6"}, + {"-1E-6", "-1E-6"}, + {"-1.0E-6", "-1.0E-6"}, + {"12345600", "1.2345600E7"}, + {"123456E2", "1.23456E7"}, + {"0", "0"}, + {"0E1", "0"}, + {"-0", "0"}, + {"-0.000", "0"}, + } + + for _, test := range tests { + d, err := NewFromString(test.input) + if err != nil { + t.Fatal(err) + } else if d.ScientificNotationString() != test.expected { + t.Errorf("expected %s, got %s", test.expected, d.ScientificNotationString()) + } + } +} + func ExampleNewFromFloat32() { fmt.Println(NewFromFloat32(123.123123123123).String()) fmt.Println(NewFromFloat32(.123123123123123).String()) fmt.Println(NewFromFloat32(-1e13).String()) // OUTPUT: - //123.12312 - //0.123123124 - //-10000000000000 + // 123.12312 + // 0.123123124 + // -10000000000000 } func ExampleNewFromFloat() { fmt.Println(NewFromFloat(123.123123123123).String()) fmt.Println(NewFromFloat(.123123123123123).String()) fmt.Println(NewFromFloat(-1e13).String()) + fmt.Println(NewFromFloat(1 << 62).String()) + fmt.Println(NewFromFloatWithExponent(1<<62, 0).String()) // OUTPUT: - //123.123123123123 - //0.123123123123123 - //-10000000000000 + // 123.123123123123 + // 0.123123123123123 + // -10000000000000 + // 4611686018427388000 + // 4611686018427387904 +} + +// TestTruncateNegativePrecision verifies that Truncate correctly handles +// negative precision values, truncating the integer part towards zero. +func TestTruncateNegativePrecision(t *testing.T) { + type testCase struct { + input string + places int32 + want string + wantExp int32 + } + tests := []testCase{ + // negative precision: truncate integer part + {"5432", -2, "5400", 2}, + {"-5432", -2, "-5400", 2}, + {"5499", -2, "5400", 2}, + {"5500", -2, "5500", 2}, + {"5501", -2, "5500", 2}, + {"999", -3, "0", 3}, + {"1000", -3, "1000", 3}, + {"1001", -3, "1000", 3}, + {"-999", -3, "0", 3}, + {"-1000", -3, "-1000", 3}, + // positive precision still works as before + {"123.456", 2, "123.45", -2}, + {"123.456", 0, "123", 0}, + {"123.456", 5, "123.456", -3}, + } + + for _, tc := range tests { + d := RequireFromString(tc.input) + got := d.Truncate(tc.places) + if got.String() != tc.want { + t.Errorf("(%s).Truncate(%d): got %s, want %s", tc.input, tc.places, got.String(), tc.want) + } + if got.exp != tc.wantExp { + t.Errorf("(%s).Truncate(%d): got exponent %d, want %d", tc.input, tc.places, got.exp, tc.wantExp) + } + } +} + +func TestRescaleToZeroRepresentation(t *testing.T) { + // reflect.DeepEqual, used by testify's assert.Equal, also compares the big.Int internals. + // Rounding a nonzero value to zero keeps the same form for any number of dropped digits. + want := New(7, 0).Sub(New(7, 0)) + for _, k := range []int32{1, 19, 20, 40} { + d := New(5, -k) + if got := d.RoundDown(0); !reflect.DeepEqual(got, want) { + t.Errorf("5e-%d RoundDown(0) = %#v, want the same representation as %#v", k, got, want) + } + if got := d.Truncate(0); !reflect.DeepEqual(got, want) { + t.Errorf("5e-%d Truncate(0) = %#v, want the same representation as %#v", k, got, want) + } + } +} + +// TestRoundingExponentNormalization verifies that RoundUp, RoundDown, +// RoundCeil, and RoundFloor return a result with the exponent normalized +// to the requested number of places, even when the value is already exact. +func TestRoundCeilFloorToZeroRepresentation(t *testing.T) { + // reflect.DeepEqual, used by testify's assert.Equal, also compares the big.Int internals. + // Rounding a nonzero value to zero keeps the same form for any number of dropped digits. + want := New(7, 0).Sub(New(7, 0)) + for _, k := range []int32{1, 19, 20, 40} { + if got := New(-5, -k).RoundCeil(0); !reflect.DeepEqual(got, want) { + t.Errorf("-5e-%d RoundCeil(0) = %#v, want the same representation as %#v", k, got, want) + } + if got := New(5, -k).RoundFloor(0); !reflect.DeepEqual(got, want) { + t.Errorf("5e-%d RoundFloor(0) = %#v, want the same representation as %#v", k, got, want) + } + } +} + +func TestRoundingExponentNormalization(t *testing.T) { + type testCase struct { + fn string + input string + places int32 + wantStr string + wantExp int32 + } + + tests := []testCase{ + // RoundUp + {"RoundUp", "100.0", 0, "100", 0}, + {"RoundUp", "100.00", 0, "100", 0}, + {"RoundUp", "3.14", 2, "3.14", -2}, + {"RoundUp", "500", -2, "500", 2}, + {"RoundUp", "100", 2, "100", -2}, + {"RoundUp", "5E3", -2, "5000", 2}, + {"RoundUp", "0.01230000000000000000000000000000", 4, "0.0123", -4}, + // RoundDown + {"RoundDown", "100.0", 0, "100", 0}, + {"RoundDown", "100.00", 0, "100", 0}, + {"RoundDown", "3.14", 2, "3.14", -2}, + {"RoundDown", "500", -2, "500", 2}, + {"RoundDown", "100", 2, "100", -2}, + {"RoundDown", "5E3", -2, "5000", 2}, + // RoundFloor + {"RoundFloor", "100.0", 0, "100", 0}, + {"RoundFloor", "100.00", 0, "100", 0}, + {"RoundFloor", "-100.0", 0, "-100", 0}, + {"RoundFloor", "500", -2, "500", 2}, + {"RoundFloor", "-100", 2, "-100", -2}, + {"RoundFloor", "5E3", -2, "5000", 2}, + // RoundCeil + {"RoundCeil", "100.0", 0, "100", 0}, + {"RoundCeil", "100.00", 0, "100", 0}, + {"RoundCeil", "-100.0", 0, "-100", 0}, + {"RoundCeil", "500", -2, "500", 2}, + {"RoundCeil", "-100", 2, "-100", -2}, + {"RoundCeil", "5E3", -2, "5000", 2}, + } + + for _, tc := range tests { + d := RequireFromString(tc.input) + var got Decimal + switch tc.fn { + case "RoundUp": + got = d.RoundUp(tc.places) + case "RoundDown": + got = d.RoundDown(tc.places) + case "RoundFloor": + got = d.RoundFloor(tc.places) + case "RoundCeil": + got = d.RoundCeil(tc.places) + } + if got.String() != tc.wantStr { + t.Errorf("(%s).%s(%d): got %s, want %s", tc.input, tc.fn, tc.places, got.String(), tc.wantStr) + } + if got.exp != tc.wantExp { + t.Errorf("(%s).%s(%d): got exponent %d, want %d", tc.input, tc.fn, tc.places, got.exp, tc.wantExp) + } + } } diff --git a/encoding.go b/encoding.go new file mode 100644 index 0000000..6801f99 --- /dev/null +++ b/encoding.go @@ -0,0 +1,465 @@ +package decimal + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + "math" + "math/big" + "reflect" +) + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error { + if string(decimalBytes) == "null" { + return nil + } + + decimal, err := NewFromString(unquoteIfQuoted(string(decimalBytes))) + *d = decimal + if err != nil { + return fmt.Errorf("error decoding string '%s': %s", string(decimalBytes), err) + } + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (d Decimal) MarshalJSON() ([]byte, error) { + str := d.String() + if MarshalJSONWithoutQuotes { + return []byte(str), nil + } + b := make([]byte, 0, len(str)+2) + b = append(b, '"') + b = append(b, str...) + return append(b, '"'), nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. As a string representation +// is already used when encoding to text, this method stores that string as []byte +func (d *Decimal) UnmarshalBinary(data []byte) error { + // Verify we have at least 4 bytes for the exponent. The GOB encoded value + // may be empty. + if len(data) < 4 { + return fmt.Errorf("error decoding binary %v: expected at least 4 bytes, got %d", data, len(data)) + } + + // Extract the exponent + exp := int32(binary.BigEndian.Uint32(data[:4])) + if int64(exp) > int64(MaxDecodeExponent) || int64(exp) < -int64(MaxDecodeExponent) { + return fmt.Errorf("error decoding binary: exponent %d exceeds MaxDecodeExponent (%d)", exp, MaxDecodeExponent) + } + d.exp = exp + + // Extract the value + d.value = new(big.Int) + if err := d.value.GobDecode(data[4:]); err != nil { + return fmt.Errorf("error decoding binary %v: %s", data, err) + } + + return nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (d Decimal) MarshalBinary() (data []byte, err error) { + // exp is written first, but encode value first to know output size + var valueData []byte + if valueData, err = d.getValue().GobEncode(); err != nil { + return nil, err + } + + // Write the exponent in front, since it's a fixed size + expData := make([]byte, 4, len(valueData)+4) + binary.BigEndian.PutUint32(expData, uint32(d.exp)) + + // Return the byte array + return append(expData, valueData...), nil +} + +// Scan implements the sql.Scanner interface for database deserialization. +func (d *Decimal) Scan(value interface{}) error { + // first try to see if the data is stored in database as a Numeric datatype + switch v := value.(type) { + + case nil: + return fmt.Errorf("can't scan NULL into a Decimal, use NullDecimal or *Decimal") + + case float32: + if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) { + return fmt.Errorf("could not convert value '%v' to decimal", v) + } + *d = NewFromFloat32(v) + return nil + + case float64: + // numeric in sqlite3 sends us float64 + if math.IsNaN(v) || math.IsInf(v, 0) { + return fmt.Errorf("could not convert value '%v' to decimal", v) + } + *d = NewFromFloat(v) + return nil + + case int64: + // at least in sqlite3 when the value is 0 in db, the data is sent + // to us as an int64 instead of a float64 ... + *d = New(v, 0) + return nil + + case uint64: + // while clickhouse may send 0 in db as uint64 + *d = NewFromUint64(v) + return nil + + case int, int8, int16, int32: + // clickhouse and test mocks send the other integer sizes + *d = New(reflect.ValueOf(v).Int(), 0) + return nil + + case uint, uint8, uint16, uint32: + *d = NewFromUint64(reflect.ValueOf(v).Uint()) + return nil + + case big.Int: + // clickhouse sends Int128, UInt128 and Int256 through database/sql as big.Int + *d = NewFromBigInt(&v, 0) + return nil + + case *big.Int: + if v != nil { + *d = NewFromBigInt(v, 0) + return nil + } + + case Decimal: + // clickhouse sends Decimal columns through its native API as Decimal + *d = v + return nil + + case string: + var err error + *d, err = NewFromString(unquoteIfQuoted(v)) + return err + + case []byte: + var err error + *d, err = NewFromString(unquoteIfQuoted(string(v))) + return err + } + return fmt.Errorf("could not convert value '%+v' to any known type", value) +} + +// Value implements the driver.Valuer interface for database serialization. +func (d Decimal) Value() (driver.Value, error) { + return d.String(), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for XML +// deserialization. +func (d *Decimal) UnmarshalText(text []byte) error { + str := string(text) + + dec, err := NewFromString(str) + *d = dec + if err != nil { + return fmt.Errorf("error decoding string '%s': %s", str, err) + } + + return nil +} + +// MarshalText implements the encoding.TextMarshaler interface for XML +// serialization. +func (d Decimal) MarshalText() (text []byte, err error) { + return []byte(d.String()), nil +} + +// GobEncode implements the gob.GobEncoder interface for gob serialization. +func (d Decimal) GobEncode() ([]byte, error) { + return d.MarshalBinary() +} + +// GobDecode implements the gob.GobDecoder interface for gob serialization. +func (d *Decimal) GobDecode(data []byte) error { + return d.UnmarshalBinary(data) +} + +// DecodeSpanner decodes a Spanner value into a Decimal +func (d *Decimal) DecodeSpanner(val interface{}) error { + return d.Scan(val) +} + +// EncodeSpanner encodes a Decimal into a Spanner value +func (d Decimal) EncodeSpanner() (interface{}, error) { + return d.String(), nil +} + +// BSON type numbers, see https://bsonspec.org/spec.html +const ( + bsonTypeDouble byte = 0x01 + bsonTypeString byte = 0x02 + bsonTypeUndefined byte = 0x06 + bsonTypeNull byte = 0x0A + bsonTypeInt32 byte = 0x10 + bsonTypeInt64 byte = 0x12 + bsonTypeDecimal128 byte = 0x13 +) + +// MarshalBSONValue implements the bson.ValueMarshaler interface of the MongoDB Go driver v2. It stores +// the decimal as a BSON Decimal128, MongoDB's decimal type, which holds 34 significant digits and +// exponents from -6176 to 6111. Trailing zeros move between the coefficient and the exponent to fit, so +// Exponent() can change in a round trip but the value can't. Decimals that still don't fit return an +// error instead of being rounded. +// +// The v1 driver declares this interface with its own bsontype.Type, so it doesn't call this method. +func (d Decimal) MarshalBSONValue() (byte, []byte, error) { + coef := new(big.Int).Abs(d.getValue()) + exp := d.exp + + // Zero fits at any exponent, and the loops below could take 2^31 steps to get there. + if coef.Sign() == 0 { + if exp < decimal128MinExp { + exp = decimal128MinExp + } else if exp > decimal128MaxExp { + exp = decimal128MaxExp + } + } + + quo, rem := new(big.Int), new(big.Int) + for (coef.Cmp(decimal128MaxCoef) > 0 || exp < decimal128MinExp) && exp < decimal128MaxExp { + if quo.QuoRem(coef, tenInt, rem); rem.Sign() != 0 { + break + } + coef, quo = quo, coef + exp++ + } + for exp > decimal128MaxExp && coef.Cmp(decimal128MaxCoef) <= 0 { + coef.Mul(coef, tenInt) + exp-- + } + if coef.Cmp(decimal128MaxCoef) > 0 || exp < decimal128MinExp { + return 0, nil, fmt.Errorf("can't encode %se%d as BSON Decimal128 without rounding: it holds 34 significant digits and exponents from %d to %d", + d.getValue(), d.exp, decimal128MinExp, decimal128MaxExp) + } + return bsonTypeDecimal128, encodeDecimal128(d.Sign() < 0, coef, exp), nil +} + +// UnmarshalBSONValue implements the bson.ValueUnmarshaler interface of the MongoDB Go driver v2. +// It decodes BSON Decimal128, string, double, int32 and int64 values. BSON null and undefined decode +// to zero, as they do for the driver's own number types. +func (d *Decimal) UnmarshalBSONValue(typ byte, data []byte) error { + var err error + switch { + case typ == bsonTypeNull || typ == bsonTypeUndefined: + *d = Decimal{} + case typ == bsonTypeDecimal128 && len(data) == 16: + *d, err = decodeDecimal128(data) + case typ == bsonTypeString && len(data) >= 5: + // int32 length, the string, then a NUL byte + *d, err = NewFromString(string(data[4 : len(data)-1])) + case typ == bsonTypeDouble && len(data) == 8: + f := math.Float64frombits(binary.LittleEndian.Uint64(data)) + if math.IsNaN(f) || math.IsInf(f, 0) { + return fmt.Errorf("can't convert BSON double %v to decimal", f) + } + *d = NewFromFloat(f) + case typ == bsonTypeInt32 && len(data) == 4: + *d = New(int64(int32(binary.LittleEndian.Uint32(data))), 0) + case typ == bsonTypeInt64 && len(data) == 8: + *d = New(int64(binary.LittleEndian.Uint64(data)), 0) + default: + return fmt.Errorf("can't convert BSON value of type 0x%02x and length %d to decimal", typ, len(data)) + } + return err +} + +// BSON stores a Decimal128 as a little-endian IEEE 754-2008 decimal128 in the binary integer decimal +// encoding: from the top bit down, a sign bit, a 14-bit exponent biased by 6176 and a 113-bit coefficient. +const ( + decimal128MinExp = -6176 + decimal128MaxExp = 6111 +) + +// decimal128MaxCoef is 10^34 - 1, the largest Decimal128 coefficient. +var decimal128MaxCoef = new(big.Int).Sub(new(big.Int).Exp(tenInt, big.NewInt(34), nil), oneInt) + +// encodeDecimal128 expects a coefficient and exponent that are already in range. +func encodeDecimal128(neg bool, coef *big.Int, exp int32) []byte { + var buf [16]byte + b := coef.Bytes() + copy(buf[16-len(b):], b) + hi := binary.BigEndian.Uint64(buf[:8]) | uint64(exp-decimal128MinExp)<<49 + if neg { + hi |= 1 << 63 + } + data := make([]byte, 16) + binary.LittleEndian.PutUint64(data, binary.BigEndian.Uint64(buf[8:])) + binary.LittleEndian.PutUint64(data[8:], hi) + return data +} + +func decodeDecimal128(data []byte) (Decimal, error) { + lo := binary.LittleEndian.Uint64(data) + hi := binary.LittleEndian.Uint64(data[8:]) + neg := hi>>63 == 1 + + var exp int32 + switch { + case hi>>59&0xF == 0xF: + return Decimal{}, fmt.Errorf("can't convert BSON Decimal128 NaN or Infinity to decimal") + case hi>>61&3 == 3: + // The exponent starts 2 bits lower, and the coefficient is always above 10^34 - 1. + exp, hi, lo = int32(hi>>47&0x3FFF), 0, 0 + default: + exp, hi = int32(hi>>49&0x3FFF), hi&(1<<49-1) + } + exp += decimal128MinExp + if int64(exp) > int64(MaxDecodeExponent) || int64(exp) < -int64(MaxDecodeExponent) { + return Decimal{}, fmt.Errorf("can't convert BSON Decimal128 to decimal: exponent %d exceeds MaxDecodeExponent (%d)", exp, MaxDecodeExponent) + } + + var buf [16]byte + binary.BigEndian.PutUint64(buf[:8], hi) + binary.BigEndian.PutUint64(buf[8:], lo) + value := new(big.Int).SetBytes(buf[:]) + // IEEE 754 reads coefficients above 10^34 - 1 as zero. + if value.Cmp(decimal128MaxCoef) > 0 { + value.SetInt64(0) + } + if neg { + value.Neg(value) + } + return Decimal{value: value, exp: exp}, nil +} + +func unquoteIfQuoted(value string) string { + // If the amount is quoted, strip the quotes + if len(value) > 2 && value[0] == '"' && value[len(value)-1] == '"' { + return value[1 : len(value)-1] + } + + return value +} + +// NullDecimal represents a nullable decimal with compatibility for +// scanning null values from the database. +type NullDecimal struct { + Decimal Decimal + Valid bool +} + +// NewNullDecimal returns a valid NullDecimal holding d. +func NewNullDecimal(d Decimal) NullDecimal { + return NullDecimal{ + Decimal: d, + Valid: true, + } +} + +// Scan implements the sql.Scanner interface for database deserialization. +func (d *NullDecimal) Scan(value interface{}) error { + if value == nil { + d.Valid = false + return nil + } + err := d.Decimal.Scan(value) + d.Valid = err == nil + return err +} + +// Value implements the driver.Valuer interface for database serialization. +func (d NullDecimal) Value() (driver.Value, error) { + if !d.Valid { + return nil, nil + } + return d.Decimal.Value() +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (d *NullDecimal) UnmarshalJSON(decimalBytes []byte) error { + if string(decimalBytes) == "null" { + d.Valid = false + return nil + } + err := d.Decimal.UnmarshalJSON(decimalBytes) + d.Valid = err == nil + return err +} + +// MarshalJSON implements the json.Marshaler interface. +func (d NullDecimal) MarshalJSON() ([]byte, error) { + if !d.Valid { + return []byte("null"), nil + } + return d.Decimal.MarshalJSON() +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for XML +// deserialization +func (d *NullDecimal) UnmarshalText(text []byte) error { + str := string(text) + + // check for empty XML or XML without body e.g., + if str == "" { + d.Valid = false + return nil + } + if err := d.Decimal.UnmarshalText(text); err != nil { + d.Valid = false + return err + } + d.Valid = true + return nil +} + +// MarshalText implements the encoding.TextMarshaler interface for XML +// serialization. +func (d NullDecimal) MarshalText() (text []byte, err error) { + if !d.Valid { + return []byte{}, nil + } + return d.Decimal.MarshalText() +} + +// DecodeSpanner decodes a Spanner value into a Decimal +func (d *NullDecimal) DecodeSpanner(value interface{}) error { + switch t := value.(type) { + case nil: + d.Valid = false + return nil + case *string: + if t == nil { + d.Valid = false + return nil + } + value = *t + } + + err := d.Decimal.Scan(value) + d.Valid = err == nil + return err +} + +// EncodeSpanner encodes a Decimal into a Spanner value +func (d NullDecimal) EncodeSpanner() (interface{}, error) { + if !d.Valid { + return nil, nil + } + return d.Decimal.String(), nil +} + +// MarshalBSONValue implements the bson.ValueMarshaler interface of the MongoDB Go driver v2. +// An invalid NullDecimal is stored as BSON null, see Decimal.MarshalBSONValue for the rest. +func (d NullDecimal) MarshalBSONValue() (byte, []byte, error) { + if !d.Valid { + return bsonTypeNull, nil, nil + } + return d.Decimal.MarshalBSONValue() +} + +// UnmarshalBSONValue implements the bson.ValueUnmarshaler interface of the MongoDB Go driver v2. +// BSON null and undefined make the NullDecimal invalid, see Decimal.UnmarshalBSONValue for the rest. +func (d *NullDecimal) UnmarshalBSONValue(typ byte, data []byte) error { + err := d.Decimal.UnmarshalBSONValue(typ, data) + d.Valid = err == nil && typ != bsonTypeNull && typ != bsonTypeUndefined + return err +} diff --git a/math.go b/math.go new file mode 100644 index 0000000..e5c7d5c --- /dev/null +++ b/math.go @@ -0,0 +1,1143 @@ +package decimal + +import ( + "fmt" + "math" + "math/big" + "math/bits" + "sync" +) + +var factorials = []Decimal{New(1, 0)} +var factorialsMutex sync.RWMutex + +// Pow returns d to the power of d2. +// The result is exact for non-negative integer exponents. For negative or non-integer exponents it is +// rounded half away from zero to PowPrecisionNegativeExponent places after the decimal point. A result +// that would round to 0 keeps PowPrecisionNegativeExponent significant digits instead (at least one). +// +// Pow returns 0 (zero-value of Decimal) instead of error for power operation edge cases, to handle those edge cases use PowWithPrecision. +// Edge cases not handled by Pow: +// - 0 ** 0 => undefined value +// - 0 ** y, where y < 0 => infinity +// - x ** y, where x < 0 and y is non-integer decimal => imaginary value +// - x ** y, where the magnitude of the result or the precision is too large to represent +// +// Example: +// +// d1 := decimal.NewFromFloat(4.0) +// d2 := decimal.NewFromFloat(4.0) +// res1 := d1.Pow(d2) +// res1.String() // output: "256" +// +// d3 := decimal.NewFromFloat(5.0) +// d4 := decimal.NewFromFloat(5.73) +// res2 := d3.Pow(d4) +// res2.String() // output: "10118.0803715950193171" +// +// d5 := decimal.NewFromInt(10) +// d6 := decimal.NewFromInt(-18) +// res3 := d5.Pow(d6) +// res3.String() // output: "0.000000000000000001" +func (d Decimal) Pow(d2 Decimal) Decimal { + res, err := d.PowWithPrecision(d2, int32(PowPrecisionNegativeExponent)) + if err != nil { + return Decimal{} + } + return res +} + +// PowWithPrecision returns d to the power of d2. +// The result is exact for non-negative integer exponents. For negative or non-integer exponents it is +// rounded half away from zero to precision places after the decimal point. A result that would round +// to 0 keeps precision significant digits instead (at least one). +// +// PowWithPrecision returns error when: +// - 0 ** 0 => undefined value +// - 0 ** y, where y < 0 => infinity +// - x ** y, where x < 0 and y is non-integer decimal => imaginary value +// - x ** y, where the magnitude of the result or the precision is too large to represent +// +// Example: +// +// d1 := decimal.NewFromFloat(4.0) +// d2 := decimal.NewFromFloat(4.0) +// res1, err := d1.PowWithPrecision(d2, 2) +// res1.String() // output: "256" +// +// d3 := decimal.NewFromFloat(5.0) +// d4 := decimal.NewFromFloat(5.73) +// res2, err := d3.PowWithPrecision(d4, 5) +// res2.String() // output: "10118.08037" +// +// d5 := decimal.NewFromFloat(-3.0) +// d6 := decimal.NewFromFloat(-6.0) +// res3, err := d5.PowWithPrecision(d6, 10) +// res3.String() // output: "0.0013717421" +func (d Decimal) PowWithPrecision(d2 Decimal, precision int32) (Decimal, error) { + if d.IsZero() { + if d2.IsZero() { + return Decimal{}, fmt.Errorf("cannot represent undefined value of 0**0") + } + if d2.Sign() < 0 { + return Decimal{}, fmt.Errorf("cannot represent infinity value of 0 ** y, where y < 0") + } + return Decimal{zeroInt, 0}, nil + } + + if d2.IsInteger() { + return d.powBigIntWithPrecision(d2.BigInt(), precision) + } + + if d.Sign() < 0 { + return Decimal{}, fmt.Errorf("cannot represent imaginary value of x ** y, where x < 0 and y is non-integer decimal") + } + + return d.powFrac(d2, precision) +} + +// PowInt32 returns d to the power of exp, where exp is int32. +// Returns error for 0 ** 0, 0 ** exp where exp < 0, and results whose magnitude is too large to represent. +// +// For negative exponents the result is rounded half away from zero to PowPrecisionNegativeExponent places +// after the decimal point. A result that would round to 0 keeps PowPrecisionNegativeExponent significant +// digits instead (at least one). +// +// Example: +// +// d1, err := decimal.NewFromFloat(4.0).PowInt32(4) +// d1.String() // output: "256" +// +// d2, err := decimal.NewFromFloat(3.13).PowInt32(5) +// d2.String() // output: "300.4150512793" +func (d Decimal) PowInt32(exp int32) (Decimal, error) { + return d.PowBigInt(big.NewInt(int64(exp))) +} + +// PowBigInt returns d to the power of exp, where exp is big.Int. +// Returns error for 0 ** 0, 0 ** exp where exp < 0, and results whose magnitude is too large to represent. +// +// For negative exponents the result is rounded half away from zero to PowPrecisionNegativeExponent places +// after the decimal point. A result that would round to 0 keeps PowPrecisionNegativeExponent significant +// digits instead (at least one). +// +// Example: +// +// d1, err := decimal.NewFromFloat(3.0).PowBigInt(big.NewInt(3)) +// d1.String() // output: "27" +// +// d2, err := decimal.NewFromFloat(629.25).PowBigInt(big.NewInt(5)) +// d2.String() // output: "98654323103449.5673828125" +func (d Decimal) PowBigInt(exp *big.Int) (Decimal, error) { + return d.powBigIntWithPrecision(exp, int32(PowPrecisionNegativeExponent)) +} + +var errPowOutOfRange = fmt.Errorf("cannot represent result of power operation, its magnitude or precision is too large") + +// powExactBits is the largest estimated cost of computing d^n exactly for negative n. +// Bigger powers are approximated with truncated intermediates, which is cheaper. +const powExactBits = 3000 + +func (d Decimal) powBigIntWithPrecision(exp *big.Int, precision int32) (Decimal, error) { + switch { + case exp.Sign() > 0: + return d.powExact(exp) + case d.IsZero() && exp.Sign() == 0: + return Decimal{}, fmt.Errorf("cannot represent undefined value of 0**0") + case exp.Sign() == 0: + return Decimal{oneInt, 0}, nil + case d.IsZero(): + return Decimal{}, fmt.Errorf("cannot represent infinity value of 0 ** y, where y < 0") + } + return d.powNegInt(new(big.Int).Neg(exp), precision) +} + +// powExact returns d^n for n > 0. +func (d Decimal) powExact(n *big.Int) (Decimal, error) { + exp := new(big.Int).Mul(n, big.NewInt(int64(d.exp))) + if !exp.IsInt64() || exp.Int64() > math.MaxInt32 || exp.Int64() < math.MinInt32 { + return Decimal{}, errPowOutOfRange + } + return Decimal{new(big.Int).Exp(d.getValue(), n, nil), int32(exp.Int64())}, nil +} + +// powNegInt returns d^-n for n > 0, rounded as described in PowWithPrecision. +func (d Decimal) powNegInt(n *big.Int, precision int32) (Decimal, error) { + if d.absIsOne() { + // only the parity of n matters + n = big.NewInt(2 - int64(n.Bit(0))) + } + + // small powers are cheap to compute exactly, and |log10(d^-n)| <= cost keeps them in range + if n.IsInt64() && n.Int64() <= powExactBits { + cost := n.Int64() * (int64(d.getValue().BitLen()) + abs64(int64(d.exp))) + if cost <= powExactBits && powInRange(float64(cost), precision) { + return d.powNegIntExact(n, precision) + } + } + + nf, _ := new(big.Float).SetInt(n).Float64() + lg := -nf * d.log10Abs() + if !powInRange(lg, precision) { + return Decimal{}, errPowOutOfRange + } + + // the digit shifts of the truncated c^n must fit into int64 + if nd := int64(d.NumDigits()); n.IsInt64() && n.Int64() <= math.MaxInt64/(4*nd) { + c := new(big.Int).Abs(d.getValue()) + w := powSigDigits(lg, precision) + int64(len(n.String())) + 6 + // each try adds 20 digits, failing 5 times means an exact tie or a value extremely close to one + for i := 0; i < 5; i, w = i+1, w+20 { + if res, ok := powNegIntApprox(c, int64(d.exp), n, w, precision); ok { + if d.Sign() < 0 && n.Bit(0) == 1 { + res.value.Neg(res.value) + } + return res, nil + } + } + } + + // same as for non-integer exponents, which handles any n and exact ties + res, err := d.Abs().powFrac(Decimal{value: new(big.Int).Neg(n)}, precision) + if err == nil && d.Sign() < 0 && n.Bit(0) == 1 { + res.value.Neg(res.value) + } + return res, err +} + +// powNegIntExact returns d^-n for n > 0, dividing by the exact d^n. +func (d Decimal) powNegIntExact(n *big.Int, precision int32) (Decimal, error) { + x, err := d.powExact(n) + if err != nil { + return Decimal{}, err + } + res := New(1, 0).DivRound(x, precision) + if res.IsZero() { + // first significant digit of 1/x is at place NumDigits+exp, one earlier when x is a power of ten + nd := int32(x.NumDigits()) + places := int32(powMinSig(precision)) - 1 + nd + x.exp + if x.getValue().CmpAbs(pow10(int64(nd)-1)) == 0 { + places-- + } + res = New(1, 0).DivRound(x, places) + } + return res, nil +} + +// powNegIntApprox rounds |1 / (c^n * 10^(e*n))| as described in PowWithPrecision. c^n is approximated +// from below by m*10^s, truncating m to w digits after every multiplication. Each truncation shrinks +// the result by a factor of at most 1-10^(1-w), and the errors add up to less than 5n*10^(1-w), +// lo below allows ten times that. +func powNegIntApprox(c *big.Int, e int64, n *big.Int, w int64, precision int32) (Decimal, bool) { + cw := new(big.Int).Set(c) + cs, exact := truncDigits(cw, w) + + m, s := big.NewInt(1), int64(0) + for i := n.BitLen() - 1; i >= 0; i-- { + m.Mul(m, m) + ds, ex := truncDigits(m, w) + s, exact = 2*s+ds, exact && ex + if n.Bit(i) == 1 { + m.Mul(m, cw) + ds, ex = truncDigits(m, w) + s, exact = s+cs+ds, exact && ex + } + } + + // 10^scale / (m * 10^(s+e*n)) = 10^z / m + z := int64(Decimal{value: m}.NumDigits()) + w + 2 + scale := z + s + e*n.Int64() + q, rem := new(big.Int).QuoRem(pow10(z), m, new(big.Int)) + lo, hi := q, q + if !exact || rem.Sign() != 0 { + hi = new(big.Int).Add(q, oneInt) + } + if !exact { + lo = new(big.Int).Mul(q, big.NewInt(5)) + lo.Mul(lo, n).Quo(lo, pow10(w-2)) + lo.Sub(q, lo).Sub(lo, oneInt) + } + return roundBounds(lo, hi, scale, precision) +} + +// truncDigits truncates m > 0 to at least w digits, returning the number of dropped digits +// and whether they were all zeros. +func truncDigits(m *big.Int, w int64) (int64, bool) { + // m >= 2^(BitLen-1), so m has at least floor((BitLen-1)*log10(2))+1 digits + drop := int64(float64(m.BitLen()-1)*math.Log10(2)-1e-9) + 1 - w + if drop <= 0 { + return 0, true + } + var r big.Int + m.QuoRem(m, pow10(drop), &r) + return drop, r.Sign() == 0 +} + +// powFrac returns d^y for d > 0 and non-integer (or negative integer) y, rounded as described in +// PowWithPrecision. It evaluates d^y = 10^q * e^s, where y*ln(d) = q*ln(10) + s and 0 <= s < ln(10), +// in binary fixed-point arithmetic, adding working digits until the error bounds decide the rounding. +func (d Decimal) powFrac(y Decimal, precision int32) (Decimal, error) { + if d.absIsOne() { + // 1^y = 1, also for y beyond float64 range + if !powInRange(0, precision) { + return Decimal{}, errPowOutOfRange + } + k := int64(abs(precision)) + 2 + res, _ := roundBounds(pow10(k), pow10(k), k, precision) + return res, nil + } + + yf := y.InexactFloat64() + lg := yf * d.log10Abs() + if !powInRange(lg, precision) { + return Decimal{}, errPowOutOfRange + } + + k := int64(d.NumDigits()) - 1 + int64(d.exp) + // covers the working error amplified by |y| and |k|, see powFracFixed + extra := int64(math.Log10(math.Abs(yf)*(math.Abs(float64(k))+64)+math.Abs(lg)+1)) + 4 + // 9 guard digits make a retry unlikely, each retry adds 20 more + f := powSigDigits(lg, precision) + 9 + exactTie := false + for i := 0; ; i, f = i+1, f+20 { + if res, ok := powFracFixed(d, y, k, f, extra, precision, exactTie); ok { + return res, nil + } + // only a terminating result can sit exactly on a rounding boundary, which no working + // precision resolves, then the upper bound rounds it away from zero + if i == 8 { + exactTie = powIsTerminating(d, y) + } + } +} + +// powIsTerminating reports whether d^y is a terminating decimal, for d > 0. +func powIsTerminating(d, y Decimal) bool { + p, q := powRatio(y) + num, den := powRatio(d) + if !q.IsInt64() { + return false + } + // d^(p/q) is rational only when num and den are perfect q-th powers + rn, ok := iroot(num, q.Int64()) + if !ok { + return false + } + rd, ok := iroot(den, q.Int64()) + if !ok { + return false + } + // (rn/rd)^p terminates when its denominator has no prime factors other than 2 and 5 + if p.Sign() < 0 { + rd = rn + } + rd = new(big.Int).Set(rd) + for _, f := range []*big.Int{twoInt, fiveInt} { + for r := new(big.Int); rd.Cmp(oneInt) > 0 && r.Rem(rd, f).Sign() == 0; { + rd.Quo(rd, f) + } + } + return rd.Cmp(oneInt) == 0 +} + +// powRatio returns d as a fraction p/q in lowest terms, q > 0. +func powRatio(d Decimal) (*big.Int, *big.Int) { + if d.exp >= 0 { + return new(big.Int).Mul(d.getValue(), pow10(int64(d.exp))), big.NewInt(1) + } + p, q := new(big.Int).Set(d.getValue()), new(big.Int).Set(pow10(-int64(d.exp))) + g := new(big.Int).GCD(nil, nil, new(big.Int).Abs(p), q) + return p.Quo(p, g), q.Quo(q, g) +} + +// iroot returns the integer k-th root of x >= 1, rounded down, and whether it is exact. +func iroot(x *big.Int, k int64) (*big.Int, bool) { + if k == 1 || x.Cmp(oneInt) == 0 { + return x, true + } + if int64(x.BitLen()) <= k { + // 1 < x < 2^k + return big.NewInt(1), false + } + // Newton's method from above converges to the floor of the root + kb, km1 := big.NewInt(k), big.NewInt(k-1) + r := new(big.Int).Lsh(oneInt, uint((int64(x.BitLen())+k-1)/k)) + t, u := new(big.Int), new(big.Int) + for { + t.Quo(x, t.Exp(r, km1, nil)) + t.Add(t, u.Mul(r, km1)).Quo(t, kb) + if t.Cmp(r) >= 0 { + break + } + r.Set(t) + } + return r, t.Exp(r, kb, nil).Cmp(x) == 0 +} + +// powFracFixed computes d^y (see powFrac) as an interval of e^s * 10^f with f+extra working digits +// and rounds it. With force the upper bound is used as the exact value. +func powFracFixed(d, y Decimal, k, f, extra int64, precision int32, force bool) (Decimal, bool) { + b := uint(float64(f+extra)*math.Log2(10)) + 1 + + // d = m * 10^k, 1 <= m < 10 + m := new(big.Int).Lsh(d.getValue(), b) + m.Quo(m, pow10(int64(d.NumDigits())-1)) + t, lnErr := lnFixed(m, b) + ln10, ln10Err := ln10Fixed(b) + t.Add(t, new(big.Int).Mul(big.NewInt(k), ln10)) + tErr := big.NewInt(lnErr + 1 + abs64(k)*ln10Err) + + // t = y * ln(d) + t.Mul(t, y.getValue()) + if y.exp >= 0 { + t.Mul(t, pow10(int64(y.exp))) + } else { + t.Quo(t, pow10(-int64(y.exp))) + } + tErr.Mul(tErr, y.Abs().Ceil().getValue()).Add(tErr, oneInt) + + q, s := new(big.Int).DivMod(t, ln10, new(big.Int)) + sErr := new(big.Int).Abs(q) + sErr.Mul(sErr, big.NewInt(ln10Err)).Add(sErr, tErr) + + // e^s < 11, so an error in s grows at most 11 times + r, rErr := expFixed(s, b) + sErr.Mul(sErr, big.NewInt(11)).Add(sErr, big.NewInt(rErr+1)) + + // v * 10^(f-q) = e^s * 10^f + p := pow10(f) + hi := new(big.Int).Add(r, sErr) + hi.Mul(hi, p).Rsh(hi, b).Add(hi, oneInt) + lo := hi + if !force { + lo = new(big.Int).Sub(r, sErr) + lo.Mul(lo, p).Rsh(lo, b) + if lo.Sign() < 0 { + lo.SetInt64(0) + } + } + return roundBounds(lo, hi, f-q.Int64(), precision) +} + +// lnFixed returns ln(m / 2^b) * 2^b for m / 2^b in [1, 10], with its error bound in ulps. +func lnFixed(m *big.Int, b uint) (*big.Int, int64) { + // a = math.Log(m) is accurate to ~1e-15, so ln(m) = a + ln(1+z), where z = m*e^-a - 1 is tiny + // and the series ln(1+z) = z - z^2/2 + z^3/3 - ... gains ~50 bits per term. + top, sh := new(big.Int), m.BitLen()-60 + if sh > 0 { + top.Rsh(m, uint(sh)) + } else { + top.Lsh(m, uint(-sh)) + } + a := big.NewInt(int64(math.Ldexp(math.Log(math.Ldexp(float64(top.Uint64()), sh-int(b))), 52))) + if b >= 52 { + a.Lsh(a, b-52) + } else { + a.Rsh(a, 52-b) + } + + e, eErr := expFixed(new(big.Int).Neg(a), b) + z := e.Mul(e, m).Rsh(e, b) + z.Sub(z, new(big.Int).Lsh(oneInt, b)) + + sum, p, term, iv := new(big.Int).Set(z), new(big.Int).Set(z), new(big.Int), new(big.Int) + terms := int64(1) + for i := int64(2); ; i++ { + p.Mul(p, z).Rsh(p, b) + if p.Sign() == 0 { + break + } + term.Quo(p, iv.SetInt64(i)) + if i%2 == 0 { + sum.Sub(sum, term) + } else { + sum.Add(sum, term) + } + terms++ + } + // m <= 10 amplifies the error of e^-a at most 10 times + return sum.Add(sum, a), 10*eErr + 2*terms + 3 +} + +// expFixed returns e^(x / 2^b) * 2^b for |x / 2^b| <= 2.4, with its error bound in ulps. +func expFixed(x *big.Int, b uint) (*big.Int, int64) { + // e^x = (e^(x/2^j))^(2^j): Taylor series of the reduced argument, then j squarings. + // Guard bits cover the error of up to ~g Taylor terms, amplified up to 11*2^j times by squaring. + j := uint(math.Sqrt(float64(b))) + 1 + g := b + j + uint(bits.Len(33*(b+j+64)+363)) + 2 + + xr := new(big.Int).Lsh(x, g-b) + xr.Rsh(xr, j) + sum, term, iv := new(big.Int).Lsh(oneInt, g), new(big.Int).Lsh(oneInt, g), new(big.Int) + for i := int64(1); ; i++ { + term.Mul(term, xr).Rsh(term, g) + term.Quo(term, iv.SetInt64(i)) + if term.Sign() == 0 { + break + } + sum.Add(sum, term) + } + for ; j > 0; j-- { + sum.Mul(sum, sum).Rsh(sum, g) + } + return sum.Rsh(sum, g-b), 2 +} + +var ln10Cache struct { + once sync.Once + v *big.Int +} + +const ln10CacheBits = 2048 + +// ln10Fixed returns ln(10) * 2^b, with its error bound in ulps. +func ln10Fixed(b uint) (*big.Int, int64) { + if b <= ln10CacheBits { + ln10Cache.once.Do(func() { ln10Cache.v, _ = ln10FromDigits(ln10CacheBits) }) + return new(big.Int).Rsh(ln10Cache.v, ln10CacheBits-b), 3 + } + if v, ok := ln10FromDigits(b); ok { + return v, 2 + } + return lnFixed(new(big.Int).Lsh(big.NewInt(10), b), b) +} + +// ln10FromDigits returns ln(10) * 2^b computed from strLn10, if it has enough digits. +func ln10FromDigits(b uint) (*big.Int, bool) { + digits := int(float64(b)*math.Log10(2)) + 3 + if digits+2 > len(strLn10) { + return nil, false + } + v, _ := new(big.Int).SetString(strLn10[:1]+strLn10[2:digits+2], 10) + v.Lsh(v, b) + return v.Quo(v, pow10(int64(digits))), true +} + +// roundBounds rounds v, known only by lo <= v*10^scale <= hi (lo >= 0), half away from zero to +// precision places after the decimal point, or to max(precision, 1) significant digits when that +// would be 0. It reports false when lo and hi do not round to the same value. +func roundBounds(lo, hi *big.Int, scale int64, precision int32) (Decimal, bool) { + g := scale - int64(precision) + if g < 1 { + return Decimal{}, false + } + dhi := int64(Decimal{value: hi}.NumDigits()) + // with fewer than g digits hi rounds to 0, skip building 10^g for tiny results + if g <= dhi { + if q := roundHalfUp(hi, g); q.Sign() != 0 { + return Decimal{q, -precision}, q.Cmp(roundHalfUp(lo, g)) == 0 + } + } + + sig := powMinSig(precision) + g = dhi - sig + if g < 2 { + return Decimal{}, false + } + q := roundHalfUp(hi, g) + res := Decimal{q, int32(g - scale)} + if int64(Decimal{value: lo}.NumDigits()) == dhi { + return res, q.Cmp(roundHalfUp(lo, g)) == 0 + } + // lo < 10^(dhi-1) <= hi, decided only when both round to that power of ten + return res, q.Cmp(pow10(sig-1)) == 0 && roundHalfUp(lo, g-1).Cmp(pow10(sig)) == 0 +} + +// roundHalfUp returns x / 10^g rounded half away from zero, for x >= 0 and g >= 1. +func roundHalfUp(x *big.Int, g int64) *big.Int { + p := pow10(g) + q, r := new(big.Int).QuoRem(x, p, new(big.Int)) + if r.Lsh(r, 1).Cmp(p) >= 0 { + q.Add(q, oneInt) + } + return q +} + +// powSigDigits returns the number of significant digits of a result of magnitude 10^lg rounded to +// precision places, at least powMinSig(precision). +func powSigDigits(lg float64, precision int32) int64 { + if n := int64(math.Floor(lg)) + 1 + int64(precision); n > powMinSig(precision) { + return n + } + return powMinSig(precision) +} + +// powMinSig returns the significant digits kept by results that would round to zero. +func powMinSig(precision int32) int64 { + if precision < 1 { + return 1 + } + return int64(precision) +} + +// powInRange reports whether a result of magnitude 10^lg rounded to precision places has an exponent +// that fits into int32. +func powInRange(lg float64, precision int32) bool { + return math.Abs(lg)+math.Abs(float64(precision)) < math.MaxInt32/2 +} + +// absIsOne reports whether |d| = 1. +func (d Decimal) absIsOne() bool { + nd := int64(d.NumDigits()) + return nd-1+int64(d.exp) == 0 && d.getValue().CmpAbs(pow10(nd-1)) == 0 +} + +// log10Abs approximates log10(|d|) for d != 0. +func (d Decimal) log10Abs() float64 { + var m big.Float + e2 := new(big.Float).SetInt(d.getValue()).MantExp(&m) + mf, _ := m.Float64() + return math.Log10(math.Abs(mf)) + float64(e2)*math.Log10(2) + float64(d.exp) +} + +func abs64(n int64) int64 { + if n < 0 { + return -n + } + return n +} + +// Sqrt returns the square root of d with DivisionPrecision digits after the decimal point, like Div. +// Use SqrtRound to choose the precision per call. It returns an error for negative d. +// +// Example: +// +// d, err := NewFromInt(2).Sqrt() +// d.String() // output: "1.414213562373095" +func (d Decimal) Sqrt() (Decimal, error) { + return d.SqrtRound(int32(DivisionPrecision)) +} + +// SqrtRound returns the square root of d rounded half away from zero to places digits after the +// decimal point, like DivRound. The result is exact up to that rounding. It returns an error for +// negative d. +// +// Example: +// +// d, err := NewFromFloat(0.002342).SqrtRound(4) +// d.String() // output: "0.0484" +func (d Decimal) SqrtRound(places int32) (Decimal, error) { + if d.Sign() < 0 { + return Decimal{}, fmt.Errorf("cannot represent imaginary value of sqrt(x), where x < 0") + } + + // floor(sqrt(d) * 10^(places+1)) is the integer square root of d * 10^(2*places+2), and its + // last digit decides the rounding. Below 1 that product truncates to an integer first, which + // doesn't change the floor, and when it's below 1 the result is 0. + k := int64(d.exp) + 2*int64(places) + 2 + n := new(big.Int) + if k >= 0 { + n.Mul(d.getValue(), pow10(k)) + } else if -k <= int64(d.NumDigits()) { + n.Quo(d.getValue(), pow10(-k)) + } + n.Sqrt(n) + n.Add(n, fiveInt).Quo(n, tenInt) + return Decimal{value: n, exp: -places}, nil +} + +// Root returns the n-th root of d with DivisionPrecision digits after the decimal point, like Div. +// Use RootRound to choose the precision per call. Odd roots of negative numbers are negative. +// It returns an error when n < 1, and for negative d when n is even. +// +// Example: +// +// d, err := NewFromInt(2).Root(3) +// d.String() // output: "1.2599210498948732" +func (d Decimal) Root(n int64) (Decimal, error) { + return d.RootRound(n, int32(DivisionPrecision)) +} + +// RootRound returns the n-th root of d rounded half away from zero to places digits after the +// decimal point, like DivRound. The result is exact up to that rounding. Odd roots of negative +// numbers are negative. It returns an error when n < 1, and for negative d when n is even. +// The work grows with n times places, so very large roots such as the millionth are slow. +// +// Example: +// +// d, err := NewFromFloat(-0.125).RootRound(3, 2) +// d.String() // output: "-0.5" +func (d Decimal) RootRound(n int64, places int32) (Decimal, error) { + if n < 1 { + return Decimal{}, fmt.Errorf("cannot calculate the %d-th root, n must be at least 1", n) + } + if d.Sign() < 0 && n%2 == 0 { + return Decimal{}, fmt.Errorf("cannot represent imaginary value of an even root of x, where x < 0") + } + + // floor(root(|d|) * 10^(places+1)) is the integer n-th root of |d| * 10^(n*(places+1)), and its + // last digit decides the rounding, as in SqrtRound. + k := int64(d.exp) + n*(int64(places)+1) + x := new(big.Int).Abs(d.getValue()) + if k >= 0 { + x.Mul(x, pow10(k)) + } else if -k <= int64(d.NumDigits()) { + x.Quo(x, pow10(-k)) + } else { + x.SetInt64(0) + } + r := new(big.Int) + if x.Sign() > 0 { + root, _ := iroot(x, n) + r.Set(root) + } + r.Add(r, fiveInt).Quo(r, tenInt) + if d.Sign() < 0 { + r.Neg(r) + } + return Decimal{value: r, exp: -places}, nil +} + +// ExpHullAbrham calculates the natural exponent of decimal (e to the power of d) using Hull-Abraham algorithm. +// OverallPrecision argument specifies the overall precision of the result (integer part + decimal part). +// +// ExpHullAbrham is faster than ExpTaylor for small precision values, but it is much slower for large precision values. +// +// Example: +// +// d1, err := NewFromFloat(26.1).ExpHullAbrham(2) +// d1.String() // output: "220000000000" +// +// d2, err := NewFromFloat(26.1).ExpHullAbrham(20) +// d2.String() // output: "216314672147.05767284" +func (d Decimal) ExpHullAbrham(overallPrecision uint32) (Decimal, error) { + // Algorithm based on Variable precision exponential function. + // ACM Transactions on Mathematical Software by T. E. Hull & A. Abrham. + if d.IsZero() { + return Decimal{oneInt, 0}, nil + } + + currentPrecision := overallPrecision + + // Algorithm does not work if currentPrecision * 23 < |x|. + // Precision is automatically increased in such cases, so the value can be calculated precisely. + // If newly calculated precision is higher than ExpMaxIterations the currentPrecision will not be changed. + f := d.Abs().InexactFloat64() + if ncp := f / 23; ncp > float64(currentPrecision) && ncp < float64(ExpMaxIterations) { + currentPrecision = uint32(math.Ceil(ncp)) + } + + // fail if abs(d) beyond an over/underflow threshold + overflowThreshold := New(23*int64(currentPrecision), 0) + if d.Abs().Cmp(overflowThreshold) > 0 { + return Decimal{}, fmt.Errorf("over/underflow threshold, exp(x) cannot be calculated precisely") + } + + // Return 1 if abs(d) small enough; this also avoids later over/underflow + overflowThreshold2 := New(9, -int32(currentPrecision)-1) + if d.Abs().Cmp(overflowThreshold2) <= 0 { + return Decimal{oneInt, 0}, nil + } + + // t is the smallest integer >= 0 such that the corresponding abs(d/k) < 1 + t := d.exp + int32(d.NumDigits()) // Add d.NumDigits because the paper assumes that d.value [0.1, 1) + + if t < 0 { + t = 0 + } + + k := New(1, t) // reduction factor + r := Decimal{new(big.Int).Set(d.getValue()), d.exp - t} // reduced argument + p := int32(currentPrecision) + t + 2 // precision for calculating the sum + + // Determine n, the number of therms for calculating sum + // use first Newton step (1.435p - 1.182) / log10(p/abs(r)) + // for solving appropriate equation, along with directed + // roundings and simple rational bound for log10(p/abs(r)) + rf := r.Abs().InexactFloat64() + pf := float64(p) + nf := math.Ceil((1.453*pf - 1.182) / math.Log10(pf/rf)) + if nf > float64(ExpMaxIterations) || math.IsNaN(nf) { + return Decimal{}, fmt.Errorf("exact value cannot be calculated in <=ExpMaxIterations iterations") + } + n := int64(nf) + + tmp := New(0, 0) + sum := New(1, 0) + one := New(1, 0) + for i := n - 1; i > 0; i-- { + tmp.value.SetInt64(i) + sum = sum.Mul(r.DivRound(tmp, p)) + sum = sum.Add(one) + } + + // res = sum^ki, the same value ki repeated multiplications would give + ki := k.IntPart() + expInt64 := int64(sum.exp) * ki + if expInt64 > math.MaxInt32 || expInt64 < math.MinInt32 { + panic(fmt.Sprintf("exponent %v overflows an int32!", expInt64)) + } + res := Decimal{new(big.Int).Exp(sum.getValue(), big.NewInt(ki), nil), int32(expInt64)} + + // Round to overallPrecision significant figures, at least 1. A raised currentPrecision only keeps + // the calculation precise, it doesn't add digits to the result. + sig := int32(overallPrecision) + if sig == 0 { + sig = 1 + } + roundDigits := sig - int32(res.NumDigits()) - res.exp + + res = res.Round(roundDigits) + + return res, nil +} + +// ExpTaylor calculates the natural exponent of decimal (e to the power of d) using Taylor series expansion. +// Precision argument specifies how precise the result must be (number of digits after decimal point). +// Negative precision is allowed. +// +// ExpTaylor is much faster for large precision values than ExpHullAbrham. +// +// Example: +// +// d1, err := NewFromFloat(26.1).ExpTaylor(2) +// d1.String() // output: "216314672147.06" +// +// d2, err := NewFromFloat(26.1).ExpTaylor(20) +// d2.String() // output: "216314672147.05767284062928674083" +// +// d3, err := NewFromFloat(26.1).ExpTaylor(-10) +// d3.String() // output: "220000000000" +func (d Decimal) ExpTaylor(precision int32) (Decimal, error) { + // Note(mwoss): Implementation can be optimized by exclusively using big.Int API only + if d.IsZero() { + return Decimal{oneInt, 0}.Round(precision), nil + } + + var epsilon Decimal + var divPrecision int32 + if precision < 0 { + epsilon = New(1, -1) + divPrecision = 8 + } else { + epsilon = New(1, -precision-1) + divPrecision = precision + 1 + } + + decAbs := d.Abs() + pow := d.Abs() + factorial := New(1, 0) + + result := New(1, 0) + + for i := int64(1); ; { + step := pow.DivRound(factorial, divPrecision) + result = result.Add(step) + + // Stop Taylor series when current step is smaller than epsilon + if step.Cmp(epsilon) < 0 { + break + } + + pow = pow.Mul(decAbs) + + i++ + + // Calculate next factorial number or retrieve cached value + factorialsMutex.RLock() + if len(factorials) >= int(i) && !factorials[i-1].IsZero() { + factorial = factorials[i-1] + factorialsMutex.RUnlock() + } else { + prevFactorial := factorials[i-2] + factorialsMutex.RUnlock() + factorial = prevFactorial.Mul(New(i, 0)) + factorialsMutex.Lock() + // Check again in case another goroutine already added it. + if len(factorials) < int(i) || factorials[i-1].IsZero() { + factorials = append(factorials, Zero) + factorials[i-1] = factorial + } + factorialsMutex.Unlock() + } + } + + if d.Sign() < 0 { + result = New(1, 0).DivRound(result, precision+1) + } + + result = result.Round(precision) + return result, nil +} + +// Ln calculates natural logarithm of d. +// Precision argument specifies how precise the result must be (number of digits after decimal point). +// Negative precision is allowed. The result is correctly rounded half away from zero. +// +// Example: +// +// d1, err := NewFromFloat(13.3).Ln(2) +// d1.String() // output: "2.59" +// +// d2, err := NewFromFloat(579.161).Ln(10) +// d2.String() // output: "6.3615805046" +func (d Decimal) Ln(precision int32) (Decimal, error) { + if d.IsNegative() { + return Decimal{}, fmt.Errorf("cannot calculate natural logarithm for negative decimals") + } + if d.IsZero() { + return Decimal{}, fmt.Errorf("cannot represent natural logarithm of 0, result: -infinity") + } + + // d = m * 10^k with 1 <= m < 10, so ln(d) = ln(m) + k*ln(10). Both are computed with error bounds, + // and the working precision grows until the whole interval rounds to the same value. That ends, + // because ln(d) is irrational for every d other than 1. + k := int64(d.NumDigits()) - 1 + int64(d.exp) + f := int64(precision) + 10 + if f < 10 { + f = 10 + } + for ; ; f += 20 { + b := uint(float64(f)*math.Log2(10)) + uint(bits.Len64(uint64(abs64(k)))) + 8 + m := new(big.Int).Lsh(d.getValue(), b) + m.Quo(m, pow10(int64(d.NumDigits())-1)) + t, lnErr := lnFixed(m, b) + ln10, ln10Err := ln10Fixed(b) + t.Add(t, new(big.Int).Mul(big.NewInt(k), ln10)) + tErr := big.NewInt(lnErr + 1 + abs64(k)*ln10Err) + + neg := t.Sign() < 0 + t.Abs(t) + p := pow10(f) + hi := new(big.Int).Add(t, tErr) + hi.Mul(hi, p).Rsh(hi, b).Add(hi, oneInt) + lo := t.Sub(t, tErr) + if lo.Sign() < 0 { + lo.SetInt64(0) + } + lo.Mul(lo, p).Rsh(lo, b) + + g := f - int64(precision) + if q := roundHalfUp(hi, g); q.Cmp(roundHalfUp(lo, g)) == 0 { + if neg { + q.Neg(q) + } + return Decimal{value: q, exp: -precision}, nil + } + } +} + +// Trig functions + +var ( + // Pi/4 split into three parts + pi4A = NewFromFloat(7.85398125648498535156e-1) // 0x3fe921fb40000000 + pi4B = NewFromFloat(3.77489470793079817668e-8) // 0x3e64442d00000000 + pi4C = NewFromFloat(2.69515142907905952645e-15) // 0x3ce8469898cc5170 + m4PI = NewFromFloat(1.273239544735162542821171882678754627704620361328125) // 4/pi + + atanP0 = NewFromFloat(-8.750608600031904122785e-01) + atanP1 = NewFromFloat(-1.615753718733365076637e+01) + atanP2 = NewFromFloat(-7.500855792314704667340e+01) + atanP3 = NewFromFloat(-1.228866684490136173410e+02) + atanP4 = NewFromFloat(-6.485021904942025371773e+01) + atanQ0 = NewFromFloat(2.485846490142306297962e+01) + atanQ1 = NewFromFloat(1.650270098316988542046e+02) + atanQ2 = NewFromFloat(4.328810604912902668951e+02) + atanQ3 = NewFromFloat(4.853903996359136964868e+02) + atanQ4 = NewFromFloat(1.945506571482613964425e+02) + + atanMorebits = NewFromFloat(6.123233995736765886130e-17) // pi/2 = PIO2 + Morebits + tan3pio8 = NewFromFloat(2.41421356237309504880) // tan(3*pi/8) + piFloat = NewFromFloat(3.14159265358979323846264338327950288419716939937510582097494459) +) + +// Atan returns the arctangent, in radians, of x. +func (d Decimal) Atan() Decimal { + if d.IsZero() { + return d + } + if d.IsPositive() { + return d.satan() + } + return d.Neg().satan().Neg() +} + +func (d Decimal) xatan() Decimal { + P0, P1, P2, P3, P4 := atanP0, atanP1, atanP2, atanP3, atanP4 + Q0, Q1, Q2, Q3, Q4 := atanQ0, atanQ1, atanQ2, atanQ3, atanQ4 + z := d.Mul(d) + b1 := P0.Mul(z).Add(P1).Mul(z).Add(P2).Mul(z).Add(P3).Mul(z).Add(P4).Mul(z) + b2 := z.Add(Q0).Mul(z).Add(Q1).Mul(z).Add(Q2).Mul(z).Add(Q3).Mul(z).Add(Q4) + z = b1.Div(b2) + z = d.Mul(z).Add(d) + return z +} + +// satan reduces its argument (known to be positive) +// to the range [0, 0.66] and calls xatan. +func (d Decimal) satan() Decimal { + Morebits, Tan3pio8, pi := atanMorebits, tan3pio8, piFloat + + if d.LessThanOrEqual(New(66, -2)) { + return d.xatan() + } + if d.GreaterThan(Tan3pio8) { + return pi.Div(New(2, 0)).Sub(New(1, 0).Div(d).xatan()).Add(Morebits) + } + return pi.Div(New(4, 0)).Add((d.Sub(New(1, 0)).Div(d.Add(New(1, 0)))).xatan()).Add(New(5, -1).Mul(Morebits)) +} + +// sin coefficients +var _sin = [...]Decimal{ + NewFromFloat(1.58962301576546568060e-10), // 0x3de5d8fd1fd19ccd + NewFromFloat(-2.50507477628578072866e-8), // 0xbe5ae5e5a9291f5d + NewFromFloat(2.75573136213857245213e-6), // 0x3ec71de3567d48a1 + NewFromFloat(-1.98412698295895385996e-4), // 0xbf2a01a019bfdf03 + NewFromFloat(8.33333333332211858878e-3), // 0x3f8111111110f7d0 + NewFromFloat(-1.66666666666666307295e-1), // 0xbfc5555555555548 +} + +// Sin returns the sine of the radian argument x. +func (d Decimal) Sin() Decimal { + PI4A, PI4B, PI4C, M4PI := pi4A, pi4B, pi4C, m4PI + + if d.IsZero() { + return d + } + // make argument positive but save the sign + sign := false + if d.IsNegative() { + d = d.Neg() + sign = true + } + + j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle + y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float + + // map zeros to origin + if j&1 == 1 { + j++ + y = y.Add(New(1, 0)) + } + j &= 7 // octant modulo 2Pi radians (360 degrees) + // reflect in x axis + if j > 3 { + sign = !sign + j -= 4 + } + z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic + zz := z.Mul(z) + + if j == 1 || j == 2 { + w := zz.Mul(zz).Mul(_cos[0].Mul(zz).Add(_cos[1]).Mul(zz).Add(_cos[2]).Mul(zz).Add(_cos[3]).Mul(zz).Add(_cos[4]).Mul(zz).Add(_cos[5])) + y = New(1, 0).Sub(New(5, -1).Mul(zz)).Add(w) + } else { + y = z.Add(z.Mul(zz).Mul(_sin[0].Mul(zz).Add(_sin[1]).Mul(zz).Add(_sin[2]).Mul(zz).Add(_sin[3]).Mul(zz).Add(_sin[4]).Mul(zz).Add(_sin[5]))) + } + if sign { + y = y.Neg() + } + return y +} + +// cos coefficients +var _cos = [...]Decimal{ + NewFromFloat(-1.13585365213876817300e-11), // 0xbda8fa49a0861a9b + NewFromFloat(2.08757008419747316778e-9), // 0x3e21ee9d7b4e3f05 + NewFromFloat(-2.75573141792967388112e-7), // 0xbe927e4f7eac4bc6 + NewFromFloat(2.48015872888517045348e-5), // 0x3efa01a019c844f5 + NewFromFloat(-1.38888888888730564116e-3), // 0xbf56c16c16c14f91 + NewFromFloat(4.16666666666665929218e-2), // 0x3fa555555555554b +} + +// Cos returns the cosine of the radian argument x. +func (d Decimal) Cos() Decimal { + + PI4A, PI4B, PI4C, M4PI := pi4A, pi4B, pi4C, m4PI + + // make argument positive + sign := false + if d.IsNegative() { + d = d.Neg() + } + + j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle + y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float + + // map zeros to origin + if j&1 == 1 { + j++ + y = y.Add(New(1, 0)) + } + j &= 7 // octant modulo 2Pi radians (360 degrees) + // reflect in x axis + if j > 3 { + sign = !sign + j -= 4 + } + if j > 1 { + sign = !sign + } + + z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic + zz := z.Mul(z) + + if j == 1 || j == 2 { + y = z.Add(z.Mul(zz).Mul(_sin[0].Mul(zz).Add(_sin[1]).Mul(zz).Add(_sin[2]).Mul(zz).Add(_sin[3]).Mul(zz).Add(_sin[4]).Mul(zz).Add(_sin[5]))) + } else { + w := zz.Mul(zz).Mul(_cos[0].Mul(zz).Add(_cos[1]).Mul(zz).Add(_cos[2]).Mul(zz).Add(_cos[3]).Mul(zz).Add(_cos[4]).Mul(zz).Add(_cos[5])) + y = New(1, 0).Sub(New(5, -1).Mul(zz)).Add(w) + } + if sign { + y = y.Neg() + } + return y +} + +var _tanP = [...]Decimal{ + NewFromFloat(-1.30936939181383777646e+4), // 0xc0c992d8d24f3f38 + NewFromFloat(1.15351664838587416140e+6), // 0x413199eca5fc9ddd + NewFromFloat(-1.79565251976484877988e+7), // 0xc1711fead3299176 +} +var _tanQ = [...]Decimal{ + NewFromFloat(1.00000000000000000000e+0), + NewFromFloat(1.36812963470692954678e+4), // 0x40cab8a5eeb36572 + NewFromFloat(-1.32089234440210967447e+6), // 0xc13427bc582abc96 + NewFromFloat(2.50083801823357915839e+7), // 0x4177d98fc2ead8ef + NewFromFloat(-5.38695755929454629881e+7), // 0xc189afe03cbe5a31 +} + +// Tan returns the tangent of the radian argument x. +func (d Decimal) Tan() Decimal { + + PI4A, PI4B, PI4C, M4PI := pi4A, pi4B, pi4C, m4PI + + if d.IsZero() { + return d + } + + // make argument positive but save the sign + sign := false + if d.IsNegative() { + d = d.Neg() + sign = true + } + + j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle + y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float + + // map zeros to origin + if j&1 == 1 { + j++ + y = y.Add(New(1, 0)) + } + + z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic + zz := z.Mul(z) + + if zz.GreaterThan(New(1, -14)) { + w := zz.Mul(_tanP[0].Mul(zz).Add(_tanP[1]).Mul(zz).Add(_tanP[2])) + x := zz.Add(_tanQ[1]).Mul(zz).Add(_tanQ[2]).Mul(zz).Add(_tanQ[3]).Mul(zz).Add(_tanQ[4]) + y = z.Add(z.Mul(w.Div(x))) + } else { + y = z + } + if j&2 == 2 { + y = New(-1, 0).Div(y) + } + if sign { + y = y.Neg() + } + return y +} diff --git a/rounding.go b/rounding.go deleted file mode 100644 index d4b0cd0..0000000 --- a/rounding.go +++ /dev/null @@ -1,160 +0,0 @@ -// 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. - -// Multiprecision decimal numbers. -// For floating-point formatting only; not general purpose. -// Only operations are assign and (binary) left/right shift. -// Can do binary floating point in multiprecision decimal precisely -// because 2 divides 10; cannot do decimal floating point -// in multiprecision binary precisely. - -package decimal - -type floatInfo struct { - mantbits uint - expbits uint - bias int -} - -var float32info = floatInfo{23, 8, -127} -var float64info = floatInfo{52, 11, -1023} - -// roundShortest rounds d (= mant * 2^exp) to the shortest number of digits -// that will let the original floating point value be precisely reconstructed. -func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) { - // If mantissa is zero, the number is zero; stop now. - if mant == 0 { - d.nd = 0 - return - } - - // Compute upper and lower such that any decimal number - // between upper and lower (possibly inclusive) - // will round to the original floating point number. - - // We may see at once that the number is already shortest. - // - // Suppose d is not denormal, so that 2^exp <= d < 10^dp. - // The closest shorter number is at least 10^(dp-nd) away. - // The lower/upper bounds computed below are at distance - // at most 2^(exp-mantbits). - // - // So the number is already shortest if 10^(dp-nd) > 2^(exp-mantbits), - // or equivalently log2(10)*(dp-nd) > exp-mantbits. - // It is true if 332/100*(dp-nd) >= exp-mantbits (log2(10) > 3.32). - minexp := flt.bias + 1 // minimum possible exponent - if exp > minexp && 332*(d.dp-d.nd) >= 100*(exp-int(flt.mantbits)) { - // The number is already shortest. - return - } - - // d = mant << (exp - mantbits) - // Next highest floating point number is mant+1 << exp-mantbits. - // Our upper bound is halfway between, mant*2+1 << exp-mantbits-1. - upper := new(decimal) - upper.Assign(mant*2 + 1) - upper.Shift(exp - int(flt.mantbits) - 1) - - // d = mant << (exp - mantbits) - // Next lowest floating point number is mant-1 << exp-mantbits, - // unless mant-1 drops the significant bit and exp is not the minimum exp, - // in which case the next lowest is mant*2-1 << exp-mantbits-1. - // Either way, call it mantlo << explo-mantbits. - // Our lower bound is halfway between, mantlo*2+1 << explo-mantbits-1. - var mantlo uint64 - var explo int - if mant > 1<= d.nd { - break - } - li := ui - upper.dp + lower.dp - l := byte('0') // lower digit - if li >= 0 && li < lower.nd { - l = lower.d[li] - } - m := byte('0') // middle digit - if mi >= 0 { - m = d.d[mi] - } - u := byte('0') // upper digit - if ui < upper.nd { - u = upper.d[ui] - } - - // Okay to round down (truncate) if lower has a different digit - // or if lower is inclusive and is exactly the result of rounding - // down (i.e., and we have reached the final digit of lower). - okdown := l != m || inclusive && li+1 == lower.nd - - switch { - case upperdelta == 0 && m+1 < u: - // Example: - // m = 12345xxx - // u = 12347xxx - upperdelta = 2 - case upperdelta == 0 && m != u: - // Example: - // m = 12345xxx - // u = 12346xxx - upperdelta = 1 - case upperdelta == 1 && (m != '9' || u != '0'): - // Example: - // m = 1234598x - // u = 1234600x - upperdelta = 2 - } - // Okay to round up if upper has a different digit and either upper - // is inclusive or upper is bigger than the result of rounding up. - okup := upperdelta > 0 && (inclusive || upperdelta > 1 || ui+1 < upper.nd) - - // If it's okay to do either, then round to the nearest one. - // If it's okay to do only one, do it. - switch { - case okdown && okup: - d.Round(mi + 1) - return - case okdown: - d.RoundDown(mi + 1) - return - case okup: - d.RoundUp(mi + 1) - return - } - } -}