diff options
| author | 2025-11-03 13:55:04 +0100 | |
|---|---|---|
| committer | 2025-11-17 14:12:09 +0100 | |
| commit | 81e3cdda44a2aed1ad0805fa738429c891b6209d (patch) | |
| tree | d9c3c95eb721e1dc1c613ee7370eaad9ec8796f7 /vendor/github.com/shopspring/decimal | |
| parent | [chore] add a 'nos3' build tag to support compiling without S3 storage suppor... (diff) | |
| download | gotosocial-81e3cdda44a2aed1ad0805fa738429c891b6209d.tar.xz | |
[chore] update dependencies (#4539)
- github.com/KimMachineGun/automemlimit: v0.7.4 -> v0.7.5
- github.com/tdewolff/minify/v2: v2.24.4 -> v2.24.5
- modernc.org/sqlite: v1.39.1 -> v1.40.0 w/ concurrency workaround
- github.com/go-swagger/go-swagger: v0.32.3 -> v0.33.1 (and drops use of our custom fork now the fix is available upstream)
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4539
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
Diffstat (limited to 'vendor/github.com/shopspring/decimal')
| -rw-r--r-- | vendor/github.com/shopspring/decimal/.travis.yml | 19 | ||||
| -rw-r--r-- | vendor/github.com/shopspring/decimal/CHANGELOG.md | 27 | ||||
| -rw-r--r-- | vendor/github.com/shopspring/decimal/README.md | 25 | ||||
| -rw-r--r-- | vendor/github.com/shopspring/decimal/const.go | 63 | ||||
| -rw-r--r-- | vendor/github.com/shopspring/decimal/decimal.go | 775 |
5 files changed, 712 insertions, 197 deletions
diff --git a/vendor/github.com/shopspring/decimal/.travis.yml b/vendor/github.com/shopspring/decimal/.travis.yml deleted file mode 100644 index 6326d40f0..000000000 --- a/vendor/github.com/shopspring/decimal/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: go - -arch: - - amd64 - - ppc64le - -go: - - 1.7.x - - 1.14.x - - 1.15.x - - 1.16.x - - 1.17.x - - tip - -install: - - go build . - -script: - - go test -v diff --git a/vendor/github.com/shopspring/decimal/CHANGELOG.md b/vendor/github.com/shopspring/decimal/CHANGELOG.md index aea61154b..432d0fd4e 100644 --- a/vendor/github.com/shopspring/decimal/CHANGELOG.md +++ b/vendor/github.com/shopspring/decimal/CHANGELOG.md @@ -1,3 +1,30 @@ +## Decimal v1.4.0 +#### BREAKING +- Drop support for Go version older than 1.10 [#361](https://github.com/shopspring/decimal/pull/361) + +#### FEATURES +- Add implementation of natural logarithm [#339](https://github.com/shopspring/decimal/pull/339) [#357](https://github.com/shopspring/decimal/pull/357) +- Add improved implementation of power operation [#358](https://github.com/shopspring/decimal/pull/358) +- Add Compare method which forwards calls to Cmp [#346](https://github.com/shopspring/decimal/pull/346) +- Add NewFromBigRat constructor [#288](https://github.com/shopspring/decimal/pull/288) +- Add NewFromUint64 constructor [#352](https://github.com/shopspring/decimal/pull/352) + +#### ENHANCEMENTS +- Migrate to Github Actions [#245](https://github.com/shopspring/decimal/pull/245) [#340](https://github.com/shopspring/decimal/pull/340) +- Fix examples for RoundDown, RoundFloor, RoundUp, and RoundCeil [#285](https://github.com/shopspring/decimal/pull/285) [#328](https://github.com/shopspring/decimal/pull/328) [#341](https://github.com/shopspring/decimal/pull/341) +- Use Godoc standard to mark deprecated Equals and StringScaled methods [#342](https://github.com/shopspring/decimal/pull/342) +- Removed unnecessary min function for RescalePair method [#265](https://github.com/shopspring/decimal/pull/265) +- Avoid reallocation of initial slice in MarshalBinary (GobEncode) [#355](https://github.com/shopspring/decimal/pull/355) +- Optimize NumDigits method [#301](https://github.com/shopspring/decimal/pull/301) [#356](https://github.com/shopspring/decimal/pull/356) +- Optimize BigInt method [#359](https://github.com/shopspring/decimal/pull/359) +- Support scanning uint64 [#131](https://github.com/shopspring/decimal/pull/131) [#364](https://github.com/shopspring/decimal/pull/364) +- Add docs section with alternative libraries [#363](https://github.com/shopspring/decimal/pull/363) + +#### BUGFIXES +- Fix incorrect calculation of decimal modulo [#258](https://github.com/shopspring/decimal/pull/258) [#317](https://github.com/shopspring/decimal/pull/317) +- Allocate new(big.Int) in Copy method to deeply clone it [#278](https://github.com/shopspring/decimal/pull/278) +- Fix overflow edge case in QuoRem method [#322](https://github.com/shopspring/decimal/pull/322) + ## Decimal v1.3.1 #### ENHANCEMENTS diff --git a/vendor/github.com/shopspring/decimal/README.md b/vendor/github.com/shopspring/decimal/README.md index 2e35df068..318c9df58 100644 --- a/vendor/github.com/shopspring/decimal/README.md +++ b/vendor/github.com/shopspring/decimal/README.md @@ -1,6 +1,8 @@ # decimal -[](https://app.travis-ci.com/shopspring/decimal) [](https://godoc.org/github.com/shopspring/decimal) [](https://goreportcard.com/report/github.com/shopspring/decimal) +[](https://github.com/shopspring/decimal/actions/workflows/ci.yml) +[](https://godoc.org/github.com/shopspring/decimal) +[](https://goreportcard.com/report/github.com/shopspring/decimal) Arbitrary-precision fixed-point decimal numbers in go. @@ -20,7 +22,12 @@ Run `go get github.com/shopspring/decimal` ## Requirements -Decimal library requires Go version `>=1.7` +Decimal library requires Go version `>=1.10` + +## Documentation + +http://godoc.org/github.com/shopspring/decimal + ## Usage @@ -57,14 +64,16 @@ func main() { } ``` -## Documentation - -http://godoc.org/github.com/shopspring/decimal +## Alternative libraries -## Production Usage +When working with decimal numbers, you might face problems this library is not perfectly suited for. +Fortunately, thanks to the wonderful community we have a dozen other libraries that you can choose from. +Explore other alternatives to find the one that best fits your needs :) -* [Spring](https://shopspring.com/), since August 14, 2014. -* If you are using this in production, please let us know! +* [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) +* [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 diff --git a/vendor/github.com/shopspring/decimal/const.go b/vendor/github.com/shopspring/decimal/const.go new file mode 100644 index 000000000..e5d6fa87e --- /dev/null +++ b/vendor/github.com/shopspring/decimal/const.go @@ -0,0 +1,63 @@ +package decimal + +import ( + "strings" +) + +const ( + strLn10 = "2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286248633409525465082806756666287369098781689482907208325554680843799894826233198528393505308965377732628846163366222287698219886746543667474404243274365155048934314939391479619404400222105101714174800368808401264708068556774321622835522011480466371565912137345074785694768346361679210180644507064800027750268491674655058685693567342067058113642922455440575892572420824131469568901675894025677631135691929203337658714166023010570308963457207544037084746994016826928280848118428931484852494864487192780967627127577539702766860595249671667418348570442250719796500471495105049221477656763693866297697952211071826454973477266242570942932258279850258550978526538320760672631716430950599508780752371033310119785754733154142180842754386359177811705430982748238504564801909561029929182431823752535770975053956518769751037497088869218020518933950723853920514463419726528728696511086257149219884997874887377134568620916705849807828059751193854445009978131146915934666241071846692310107598438319191292230792503747298650929009880391941702654416816335727555703151596113564846546190897042819763365836983716328982174407366009162177850541779276367731145041782137660111010731042397832521894898817597921798666394319523936855916447118246753245630912528778330963604262982153040874560927760726641354787576616262926568298704957954913954918049209069438580790032763017941503117866862092408537949861264933479354871737451675809537088281067452440105892444976479686075120275724181874989395971643105518848195288330746699317814634930000321200327765654130472621883970596794457943468343218395304414844803701305753674262153675579814770458031413637793236291560128185336498466942261465206459942072917119370602444929358037007718981097362533224548366988505528285966192805098447175198503666680874970496982273220244823343097169111136813588418696549323714996941979687803008850408979618598756579894836445212043698216415292987811742973332588607915912510967187510929248475023930572665446276200923068791518135803477701295593646298412366497023355174586195564772461857717369368404676577047874319780573853271810933883496338813069945569399346101090745616033312247949360455361849123333063704751724871276379140924398331810164737823379692265637682071706935846394531616949411701841938119405416449466111274712819705817783293841742231409930022911502362192186723337268385688273533371925103412930705632544426611429765388301822384091026198582888433587455960453004548370789052578473166283701953392231047527564998119228742789713715713228319641003422124210082180679525276689858180956119208391760721080919923461516952599099473782780648128058792731993893453415320185969711021407542282796298237068941764740642225757212455392526179373652434440560595336591539160312524480149313234572453879524389036839236450507881731359711238145323701508413491122324390927681724749607955799151363982881058285740538000653371655553014196332241918087621018204919492651483892" +) + +var ( + ln10 = newConstApproximation(strLn10) +) + +type constApproximation struct { + exact Decimal + approximations []Decimal +} + +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] +} diff --git a/vendor/github.com/shopspring/decimal/decimal.go b/vendor/github.com/shopspring/decimal/decimal.go index 84405ec1c..a37a2301e 100644 --- a/vendor/github.com/shopspring/decimal/decimal.go +++ b/vendor/github.com/shopspring/decimal/decimal.go @@ -4,14 +4,14 @@ // // The best way to create a new Decimal is to use decimal.NewFromString, ex: // -// n, err := decimal.NewFromString("-123.4567") -// n.String() // output: "-123.4567" +// n, err := decimal.NewFromString("-123.4567") +// n.String() // output: "-123.4567" // // To use Decimal as part of a struct: // -// type Struct struct { -// Number Decimal -// } +// type StructName struct { +// Number Decimal +// } // // Note: This can "only" represent numbers with a maximum of 2^31 digits after the decimal point. package decimal @@ -32,18 +32,31 @@ import ( // // Example: // -// d1 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)) -// d1.String() // output: "0.6666666666666667" -// d2 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(30000)) -// d2.String() // output: "0.0000666666666667" -// d3 := decimal.NewFromFloat(20000).Div(decimal.NewFromFloat(3)) -// d3.String() // output: "6666.6666666666666667" -// decimal.DivisionPrecision = 3 -// d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)) -// d4.String() // output: "0.667" -// +// d1 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)) +// d1.String() // output: "0.6666666666666667" +// d2 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(30000)) +// d2.String() // output: "0.0000666666666667" +// d3 := decimal.NewFromFloat(20000).Div(decimal.NewFromFloat(3)) +// d3.String() // output: "6666.6666666666666667" +// decimal.DivisionPrecision = 3 +// d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)) +// 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. +// +// Example: +// +// d1, err := decimal.NewFromFloat(15.2).PowInt32(-2) +// d1.String() // output: "0.0043282548476454" +// +// decimal.PowPrecisionNegativeExponent = 24 +// d2, err := decimal.NewFromFloat(15.2).PowInt32(-2) +// d2.String() // output: "0.004328254847645429362881" +var PowPrecisionNegativeExponent = 16 + // MarshalJSONWithoutQuotes should be set to true if you want the decimal to // be JSON marshaled as a number, instead of as a string. // WARNING: this is dangerous for decimals with many digits, since many JSON @@ -91,12 +104,12 @@ func New(value int64, exp int32) Decimal { } } -// NewFromInt converts a int64 to Decimal. +// NewFromInt converts an int64 to Decimal. // // Example: // -// NewFromInt(123).String() // output: "123" -// NewFromInt(-10).String() // output: "-10" +// NewFromInt(123).String() // output: "123" +// NewFromInt(-10).String() // output: "-10" func NewFromInt(value int64) Decimal { return Decimal{ value: big.NewInt(value), @@ -104,12 +117,12 @@ func NewFromInt(value int64) Decimal { } } -// NewFromInt32 converts a int32 to Decimal. +// NewFromInt32 converts an int32 to Decimal. // // Example: // -// NewFromInt(123).String() // output: "123" -// NewFromInt(-10).String() // output: "-10" +// NewFromInt(123).String() // output: "123" +// NewFromInt(-10).String() // output: "-10" func NewFromInt32(value int32) Decimal { return Decimal{ value: big.NewInt(int64(value)), @@ -117,6 +130,18 @@ func NewFromInt32(value int32) Decimal { } } +// NewFromUint64 converts an uint64 to Decimal. +// +// Example: +// +// NewFromUint64(123).String() // output: "123" +func NewFromUint64(value uint64) Decimal { + return Decimal{ + value: new(big.Int).SetUint64(value), + exp: 0, + } +} + // NewFromBigInt returns a new Decimal from a big.Int, value * 10 ^ exp func NewFromBigInt(value *big.Int, exp int32) Decimal { return Decimal{ @@ -125,15 +150,33 @@ func NewFromBigInt(value *big.Int, exp int32) Decimal { } } +// NewFromBigRat returns a new Decimal from a big.Rat. The numerator and +// denominator are divided and rounded to the given precision. +// +// Example: +// +// d1 := NewFromBigRat(big.NewRat(0, 1), 0) // output: "0" +// d2 := NewFromBigRat(big.NewRat(4, 5), 1) // output: "0.8" +// d3 := NewFromBigRat(big.NewRat(1000, 3), 3) // output: "333.333" +// d4 := NewFromBigRat(big.NewRat(2, 7), 4) // output: "0.2857" +func NewFromBigRat(value *big.Rat, precision int32) Decimal { + return Decimal{ + value: new(big.Int).Set(value.Num()), + exp: 0, + }.DivRound(Decimal{ + value: new(big.Int).Set(value.Denom()), + exp: 0, + }, precision) +} + // NewFromString returns a new Decimal from a string representation. // Trailing zeroes are not trimmed. // // Example: // -// d, err := NewFromString("-123.45") -// d2, err := NewFromString(".0001") -// d3, err := NewFromString("1.47000") -// +// d, err := NewFromString("-123.45") +// d2, err := NewFromString(".0001") +// d3, err := NewFromString("1.47000") func NewFromString(value string) (Decimal, error) { originalInput := value var intString string @@ -211,15 +254,14 @@ func NewFromString(value string) (Decimal, error) { // // Example: // -// r := regexp.MustCompile("[$,]") -// d1, err := NewFromFormattedString("$5,125.99", r) +// r := regexp.MustCompile("[$,]") +// d1, err := NewFromFormattedString("$5,125.99", r) // -// r2 := regexp.MustCompile("[_]") -// d2, err := NewFromFormattedString("1_000_000", r2) -// -// r3 := regexp.MustCompile("[USD\\s]") -// d3, err := NewFromFormattedString("5000 USD", r3) +// r2 := regexp.MustCompile("[_]") +// d2, err := NewFromFormattedString("1_000_000", r2) // +// r3 := regexp.MustCompile("[USD\\s]") +// d3, err := NewFromFormattedString("5000 USD", r3) func NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error) { parsedValue := replRegexp.ReplaceAllString(value, "") d, err := NewFromString(parsedValue) @@ -230,13 +272,12 @@ func NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, e } // RequireFromString returns a new Decimal from a string representation -// or panics if NewFromString would have returned an error. +// or panics if NewFromString had returned an error. // // Example: // -// d := RequireFromString("-123.45") -// d2 := RequireFromString(".0001") -// +// d := RequireFromString("-123.45") +// d2 := RequireFromString(".0001") func RequireFromString(value string) Decimal { dec, err := NewFromString(value) if err != nil { @@ -332,8 +373,7 @@ func newFromFloat(val float64, bits uint64, flt *floatInfo) Decimal { // // Example: // -// NewFromFloatWithExponent(123.456, -2).String() // output: "123.46" -// +// NewFromFloatWithExponent(123.456, -2).String() // output: "123.46" func NewFromFloatWithExponent(value float64, exp int32) Decimal { if math.IsNaN(value) || math.IsInf(value, 0) { panic(fmt.Sprintf("Cannot create a Decimal from %v", value)) @@ -418,7 +458,7 @@ func NewFromFloatWithExponent(value float64, exp int32) Decimal { func (d Decimal) Copy() Decimal { d.ensureInitialized() return Decimal{ - value: &(*d.value), + value: new(big.Int).Set(d.value), exp: d.exp, } } @@ -430,7 +470,7 @@ func (d Decimal) Copy() Decimal { // // Example: // -// d := New(12345, -4) +// d := New(12345, -4) // d2 := d.rescale(-1) // d3 := d2.rescale(-4) // println(d1) @@ -442,7 +482,6 @@ func (d Decimal) Copy() Decimal { // 1.2345 // 1.2 // 1.2000 -// func (d Decimal) rescale(exp int32) Decimal { d.ensureInitialized() @@ -552,11 +591,13 @@ func (d Decimal) Div(d2 Decimal) Decimal { return d.DivRound(d2, int32(DivisionPrecision)) } -// QuoRem does divsion with remainder +// QuoRem does division with remainder // d.QuoRem(d2,precision) returns quotient q and remainder r such that -// d = d2 * q + r, q an integer multiple of 10^(-precision) -// 0 <= r < abs(d2) * 10 ^(-precision) if d>=0 -// 0 >= r > -abs(d2) * 10 ^(-precision) if d<0 +// +// d = d2 * q + r, q an integer multiple of 10^(-precision) +// 0 <= r < abs(d2) * 10 ^(-precision) if d>=0 +// 0 >= r > -abs(d2) * 10 ^(-precision) if d<0 +// // Note that precision<0 is allowed as input. func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) { d.ensureInitialized() @@ -565,7 +606,7 @@ func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) { panic("decimal division by 0") } scale := -precision - e := int64(d.exp - d2.exp - scale) + e := int64(d.exp) - int64(d2.exp) - int64(scale) if e > math.MaxInt32 || e < math.MinInt32 { panic("overflow in decimal QuoRem") } @@ -599,8 +640,10 @@ func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) { // DivRound divides and rounds to a given precision // i.e. to an integer multiple of 10^(-precision) -// for a positive quotient digit 5 is rounded up, away from 0 -// if the quotient is negative then digit 5 is rounded down, away from 0 +// +// for a positive quotient digit 5 is rounded up, away from 0 +// if the quotient is negative then digit 5 is rounded down, away from 0 +// // Note that precision<0 is allowed as input. func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal { // QuoRem already checks initialization @@ -628,24 +671,278 @@ func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal { // Mod returns d % d2. func (d Decimal) Mod(d2 Decimal) Decimal { - quo := d.Div(d2).Truncate(0) - return d.Sub(d2.Mul(quo)) + _, r := d.QuoRem(d2, 0) + return r } -// Pow returns d to the power d2 +// 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 { - var temp Decimal - if d2.IntPart() == 0 { - return NewFromFloat(1) + 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") } - temp = d.Pow(d2.Div(NewFromFloat(2))) - if d2.IntPart()%2 == 0 { - return temp.Mul(temp) + + 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 d2.IntPart() > 0 { - return temp.Mul(temp).Mul(d) + + if isExpNeg { + return New(1, 0).DivRound(result, precision), nil } - return temp.Mul(temp).Div(d) + + return result, nil } // ExpHullAbrham calculates the natural exponent of decimal (e to the power of d) using Hull-Abraham algorithm. @@ -655,9 +952,8 @@ func (d Decimal) Pow(d2 Decimal) Decimal { // // Example: // -// NewFromFloat(26.1).ExpHullAbrham(2).String() // output: "220000000000" -// NewFromFloat(26.1).ExpHullAbrham(20).String() // output: "216314672147.05767284" -// +// 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. @@ -747,15 +1043,14 @@ func (d Decimal) ExpHullAbrham(overallPrecision uint32) (Decimal, error) { // // 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" +// d, err := NewFromFloat(26.1).ExpTaylor(2).String() +// d.String() // output: "216314672147.06" // -// NewFromFloat(26.1).ExpTaylor(-10).String() -// d.String() // output: "220000000000" +// 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() { @@ -812,14 +1107,162 @@ func (d Decimal) ExpTaylor(precision int32) (Decimal, error) { 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) -// Note: Current implementation is extremely slow for large decimals and/or decimals with large fractional part func (d Decimal) NumDigits() int { - // Note(mwoss): It can be optimized, unnecessary cast of big.Int to string - if d.IsNegative() { - return len(d.value.String()) - 1 + 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 + } + } + + estimatedNumDigits := int(float64(d.value.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 { + return estimatedNumDigits + 1 } - return len(d.value.String()) + + return estimatedNumDigits } // IsInteger returns true when decimal can be represented as an integer value, otherwise, it returns false. @@ -851,10 +1294,9 @@ func abs(n int32) int32 { // Cmp compares the numbers represented by d and d2 and returns: // -// -1 if d < d2 -// 0 if d == d2 -// +1 if d > d2 -// +// -1 if d < d2 +// 0 if d == d2 +// +1 if d > d2 func (d Decimal) Cmp(d2 Decimal) int { d.ensureInitialized() d2.ensureInitialized() @@ -868,12 +1310,21 @@ func (d Decimal) Cmp(d2 Decimal) int { return rd.value.Cmp(rd2.value) } +// Compare compares the numbers represented by d and d2 and returns: +// +// -1 if d < d2 +// 0 if d == d2 +// +1 if d > d2 +func (d Decimal) Compare(d2 Decimal) int { + return d.Cmp(d2) +} + // Equal returns whether the numbers represented by d and d2 are equal. func (d Decimal) Equal(d2 Decimal) bool { return d.Cmp(d2) == 0 } -// Equals is deprecated, please use Equal method instead +// Deprecated: Equals is deprecated, please use Equal method instead. func (d Decimal) Equals(d2 Decimal) bool { return d.Equal(d2) } @@ -905,7 +1356,6 @@ func (d Decimal) LessThanOrEqual(d2 Decimal) bool { // -1 if d < 0 // 0 if d == 0 // +1 if d > 0 -// func (d Decimal) Sign() int { if d.value == nil { return 0 @@ -968,9 +1418,7 @@ func (d Decimal) IntPart() int64 { // BigInt returns integer component of the decimal as a BigInt. func (d Decimal) BigInt() *big.Int { scaledD := d.rescale(0) - i := &big.Int{} - i.SetString(scaledD.String(), 10) - return i + return scaledD.value } // BigFloat returns decimal as BigFloat. @@ -1014,13 +1462,12 @@ func (d Decimal) InexactFloat64() float64 { // // Example: // -// d := New(-12345, -3) -// println(d.String()) +// d := New(-12345, -3) +// println(d.String()) // // Output: // -// -12.345 -// +// -12.345 func (d Decimal) String() string { return d.string(true) } @@ -1030,14 +1477,13 @@ func (d Decimal) String() string { // // Example: // -// NewFromFloat(0).StringFixed(2) // output: "0.00" -// NewFromFloat(0).StringFixed(0) // output: "0" -// NewFromFloat(5.45).StringFixed(0) // output: "5" -// NewFromFloat(5.45).StringFixed(1) // output: "5.5" -// NewFromFloat(5.45).StringFixed(2) // output: "5.45" -// NewFromFloat(5.45).StringFixed(3) // output: "5.450" -// NewFromFloat(545).StringFixed(-1) // output: "550" -// +// NewFromFloat(0).StringFixed(2) // output: "0.00" +// NewFromFloat(0).StringFixed(0) // output: "0" +// NewFromFloat(5.45).StringFixed(0) // output: "5" +// NewFromFloat(5.45).StringFixed(1) // output: "5.5" +// NewFromFloat(5.45).StringFixed(2) // output: "5.45" +// NewFromFloat(5.45).StringFixed(3) // output: "5.450" +// NewFromFloat(545).StringFixed(-1) // output: "550" func (d Decimal) StringFixed(places int32) string { rounded := d.Round(places) return rounded.string(false) @@ -1048,14 +1494,13 @@ func (d Decimal) StringFixed(places int32) string { // // Example: // -// NewFromFloat(0).StringFixedBank(2) // output: "0.00" -// NewFromFloat(0).StringFixedBank(0) // output: "0" -// NewFromFloat(5.45).StringFixedBank(0) // output: "5" -// NewFromFloat(5.45).StringFixedBank(1) // output: "5.4" -// NewFromFloat(5.45).StringFixedBank(2) // output: "5.45" -// NewFromFloat(5.45).StringFixedBank(3) // output: "5.450" -// NewFromFloat(545).StringFixedBank(-1) // output: "540" -// +// NewFromFloat(0).StringFixedBank(2) // output: "0.00" +// NewFromFloat(0).StringFixedBank(0) // output: "0" +// NewFromFloat(5.45).StringFixedBank(0) // output: "5" +// NewFromFloat(5.45).StringFixedBank(1) // output: "5.4" +// NewFromFloat(5.45).StringFixedBank(2) // output: "5.45" +// NewFromFloat(5.45).StringFixedBank(3) // output: "5.450" +// NewFromFloat(545).StringFixedBank(-1) // output: "540" func (d Decimal) StringFixedBank(places int32) string { rounded := d.RoundBank(places) return rounded.string(false) @@ -1073,9 +1518,8 @@ 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(5.45).Round(1).String() // output: "5.5" +// NewFromFloat(545).Round(-1).String() // output: "550" func (d Decimal) Round(places int32) Decimal { if d.exp == -places { return d @@ -1104,11 +1548,10 @@ func (d Decimal) Round(places int32) Decimal { // // Example: // -// NewFromFloat(545).RoundCeil(-2).String() // output: "600" -// NewFromFloat(500).RoundCeil(-2).String() // output: "500" -// NewFromFloat(1.1001).RoundCeil(2).String() // output: "1.11" -// NewFromFloat(-1.454).RoundCeil(1).String() // output: "-1.5" -// +// NewFromFloat(545).RoundCeil(-2).String() // output: "600" +// NewFromFloat(500).RoundCeil(-2).String() // output: "500" +// NewFromFloat(1.1001).RoundCeil(2).String() // output: "1.11" +// NewFromFloat(-1.454).RoundCeil(1).String() // output: "-1.4" func (d Decimal) RoundCeil(places int32) Decimal { if d.exp >= -places { return d @@ -1130,11 +1573,10 @@ func (d Decimal) RoundCeil(places int32) Decimal { // // Example: // -// NewFromFloat(545).RoundFloor(-2).String() // output: "500" -// NewFromFloat(-500).RoundFloor(-2).String() // output: "-500" -// NewFromFloat(1.1001).RoundFloor(2).String() // output: "1.1" -// NewFromFloat(-1.454).RoundFloor(1).String() // output: "-1.4" -// +// NewFromFloat(545).RoundFloor(-2).String() // output: "500" +// NewFromFloat(-500).RoundFloor(-2).String() // output: "-500" +// NewFromFloat(1.1001).RoundFloor(2).String() // output: "1.1" +// NewFromFloat(-1.454).RoundFloor(1).String() // output: "-1.5" func (d Decimal) RoundFloor(places int32) Decimal { if d.exp >= -places { return d @@ -1156,11 +1598,10 @@ func (d Decimal) RoundFloor(places int32) Decimal { // // Example: // -// NewFromFloat(545).RoundUp(-2).String() // output: "600" -// NewFromFloat(500).RoundUp(-2).String() // output: "500" -// NewFromFloat(1.1001).RoundUp(2).String() // output: "1.11" -// NewFromFloat(-1.454).RoundUp(1).String() // output: "-1.4" -// +// NewFromFloat(545).RoundUp(-2).String() // output: "600" +// NewFromFloat(500).RoundUp(-2).String() // output: "500" +// NewFromFloat(1.1001).RoundUp(2).String() // output: "1.11" +// NewFromFloat(-1.454).RoundUp(1).String() // output: "-1.5" func (d Decimal) RoundUp(places int32) Decimal { if d.exp >= -places { return d @@ -1184,11 +1625,10 @@ func (d Decimal) RoundUp(places int32) Decimal { // // Example: // -// NewFromFloat(545).RoundDown(-2).String() // output: "500" -// NewFromFloat(-500).RoundDown(-2).String() // output: "-500" -// NewFromFloat(1.1001).RoundDown(2).String() // output: "1.1" -// NewFromFloat(-1.454).RoundDown(1).String() // output: "-1.5" -// +// NewFromFloat(545).RoundDown(-2).String() // output: "500" +// NewFromFloat(-500).RoundDown(-2).String() // output: "-500" +// 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 @@ -1209,13 +1649,12 @@ func (d Decimal) RoundDown(places int32) Decimal { // // Examples: // -// NewFromFloat(5.45).RoundBank(1).String() // output: "5.4" -// NewFromFloat(545).RoundBank(-1).String() // output: "540" -// NewFromFloat(5.46).RoundBank(1).String() // output: "5.5" -// NewFromFloat(546).RoundBank(-1).String() // output: "550" -// NewFromFloat(5.55).RoundBank(1).String() // output: "5.6" -// NewFromFloat(555).RoundBank(-1).String() // output: "560" -// +// NewFromFloat(5.45).RoundBank(1).String() // output: "5.4" +// NewFromFloat(545).RoundBank(-1).String() // output: "540" +// NewFromFloat(5.46).RoundBank(1).String() // output: "5.5" +// NewFromFloat(546).RoundBank(-1).String() // output: "550" +// NewFromFloat(5.55).RoundBank(1).String() // output: "5.6" +// NewFromFloat(555).RoundBank(-1).String() // output: "560" func (d Decimal) RoundBank(places int32) Decimal { round := d.Round(places) @@ -1237,11 +1676,13 @@ func (d Decimal) RoundBank(places int32) Decimal { // interval. The amount payable for a cash transaction is rounded to the nearest // multiple of the minimum currency unit available. The following intervals are // available: 5, 10, 25, 50 and 100; any other number throws a panic. -// 5: 5 cent rounding 3.43 => 3.45 -// 10: 10 cent rounding 3.45 => 3.50 (5 gets rounded up) -// 25: 25 cent rounding 3.41 => 3.50 -// 50: 50 cent rounding 3.75 => 4.00 -// 100: 100 cent rounding 3.50 => 4.00 +// +// 5: 5 cent rounding 3.43 => 3.45 +// 10: 10 cent rounding 3.45 => 3.50 (5 gets rounded up) +// 25: 25 cent rounding 3.41 => 3.50 +// 50: 50 cent rounding 3.75 => 4.00 +// 100: 100 cent rounding 3.50 => 4.00 +// // For more details: https://en.wikipedia.org/wiki/Cash_rounding func (d Decimal) RoundCash(interval uint8) Decimal { var iVal *big.Int @@ -1310,8 +1751,7 @@ func (d Decimal) Ceil() Decimal { // // Example: // -// decimal.NewFromString("123.456").Truncate(2).String() // "123.45" -// +// decimal.NewFromString("123.456").Truncate(2).String() // "123.45" func (d Decimal) Truncate(precision int32) Decimal { d.ensureInitialized() if precision >= 0 && -precision > d.exp { @@ -1373,19 +1813,18 @@ func (d *Decimal) UnmarshalBinary(data []byte) error { // MarshalBinary implements the encoding.BinaryMarshaler interface. func (d Decimal) MarshalBinary() (data []byte, err error) { - // Write the exponent first since it's a fixed size - v1 := make([]byte, 4) - binary.BigEndian.PutUint32(v1, uint32(d.exp)) - - // Add the value - var v2 []byte - if v2, err = d.value.GobEncode(); err != nil { - return + // 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 - data = append(v1, v2...) - return + return append(expData, valueData...), nil } // Scan implements the sql.Scanner interface for database deserialization. @@ -1408,6 +1847,11 @@ func (d *Decimal) Scan(value interface{}) error { *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) @@ -1455,7 +1899,8 @@ func (d *Decimal) GobDecode(data []byte) error { } // StringScaled first scales the decimal then calls .String() on it. -// NOTE: buggy, unintuitive, and DEPRECATED! Use StringFixed instead. +// +// Deprecated: buggy and unintuitive. Use StringFixed instead. func (d Decimal) StringScaled(exp int32) string { return d.rescale(exp).String() } @@ -1515,7 +1960,7 @@ func (d *Decimal) ensureInitialized() { // // To call this function with an array, you must do: // -// Min(arr[0], arr[1:]...) +// Min(arr[0], arr[1:]...) // // This makes it harder to accidentally call Min with 0 arguments. func Min(first Decimal, rest ...Decimal) Decimal { @@ -1532,7 +1977,7 @@ func Min(first Decimal, rest ...Decimal) Decimal { // // To call this function with an array, you must do: // -// Max(arr[0], arr[1:]...) +// Max(arr[0], arr[1:]...) // // This makes it harder to accidentally call Max with 0 arguments. func Max(first Decimal, rest ...Decimal) Decimal { @@ -1567,22 +2012,13 @@ func RescalePair(d1 Decimal, d2 Decimal) (Decimal, Decimal) { d1.ensureInitialized() d2.ensureInitialized() - if d1.exp == d2.exp { - return d1, d2 + if d1.exp < d2.exp { + return d1, d2.rescale(d1.exp) + } else if d1.exp > d2.exp { + return d1.rescale(d2.exp), d2 } - baseScale := min(d1.exp, d2.exp) - if baseScale != d1.exp { - return d1.rescale(baseScale), d2 - } - return d1, d2.rescale(baseScale) -} - -func min(x, y int32) int32 { - if x >= y { - return y - } - return x + return d1, d2 } func unquoteIfQuoted(value interface{}) (string, error) { @@ -1594,8 +2030,7 @@ func unquoteIfQuoted(value interface{}) (string, error) { case []byte: bytes = v default: - return "", fmt.Errorf("could not convert value '%+v' to byte array of type '%T'", - value, value) + return "", fmt.Errorf("could not convert value '%+v' to byte array of type '%T'", value, value) } // If the amount is quoted, strip the quotes |
