From 34ca84c279ea01433ee0d44d883cae43fed492bf Mon Sep 17 00:00:00 2001 From: Eugene Apenkin Date: Wed, 1 Nov 2023 15:30:09 +0400 Subject: [PATCH] decimal: improve examples --- CHANGELOG.md | 6 + README.md | 58 +++++--- decimal.go | 128 +++++++++------- decimal_test.go | 48 +++--- doc_test.go | 385 +++++++++++++++++++++++++----------------------- 5 files changed, 342 insertions(+), 283 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c54f7b..e375e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.1.15] - 2023-10-31 + +### Changed + +- Improved examples and documentation. + ## [0.1.14] - 2023-10-13 ### Changed diff --git a/README.md b/README.md index 1dab7e5..63e4354 100644 --- a/README.md +++ b/README.md @@ -51,16 +51,32 @@ import ( ) func main() { - d := decimal.MustNew(8, 0) // d = 8 - e := decimal.MustParse("12.5") // e = 12.5 + // Constructors + d, _ := decimal.New(8, 0) // d = 8 + e, _ := decimal.Parse("12.5") // e = 12.5 + f, _ := decimal.NewFromFloat64(2.567) // f = 2.567 + g, _ := decimal.NewFromInt64(7, 896, 3) // g = 7.896 + + // Conversions + fmt.Println(f.Int64(9)) // 2 567000000 + fmt.Println(f.Float64()) // 2.567 + fmt.Println(f.String()) // 2.567 + + // Operations fmt.Println(d.Add(e)) // 8 + 12.5 fmt.Println(d.Sub(e)) // 8 - 12.5 fmt.Println(d.Mul(e)) // 8 * 12.5 fmt.Println(d.Quo(e)) // 8 / 12.5 - fmt.Println(d.QuoRem(e)) // 8 // 12.5 and 8 mod 12.5 - fmt.Println(d.FMA(e, e)) // 8 * 12.5 + 12.5 + fmt.Println(d.QuoRem(e)) // 8 div 12.5, 8 mod 12.5 + fmt.Println(d.FMA(e, f)) // 8 * 12.5 + 2.567 fmt.Println(d.Pow(2)) // 8 ^ 2 fmt.Println(d.Inv()) // 1 / 8 + + // Rounding to 2 decimal places + fmt.Println(g.Round(2)) // 7.90 + fmt.Println(g.Ceil(2)) // 7.90 + fmt.Println(g.Floor(2)) // 7.89 + fmt.Println(g.Trunc(2)) // 7.89 } ``` @@ -75,7 +91,7 @@ For examples related to financial calculations, see the `money` package Comparison with other popular packages: -| Feature | govalues | [cockroachdb] v3.2.0 | [shopspring] v1.3.1 | +| Feature | govalues | [cockroachdb] v3.2.1 | [shopspring] v1.3.1 | | ---------------- | ------------ | -------------------- | ------------------- | | Speed | High | Medium | Low[^reason] | | Mutability | Immutable | Mutable[^reason] | Immutable | @@ -93,25 +109,26 @@ too slow and cockroachdb's decimal was mutable. ```text goos: linux goarch: amd64 -pkg: github.com/govalues/decimaltests +pkg: github.com/govalues/decimal-tests cpu: AMD Ryzen 7 3700C with Radeon Vega Mobile Gfx ``` -| Test Case | Expression | govalues | [cockroachdb] v3.2.0 | [shopspring] v1.3.1 | govalues vs cockroachdb | govalues vs shopspring | +| Test Case | Expression | govalues | [cockroachdb] v3.2.1 | [shopspring] v1.3.1 | govalues vs cockroachdb | govalues vs shopspring | | ----------- | -------------------- | -------: | -------------------: | ------------------: | ----------------------: | ---------------------: | -| Add | 2 + 3 | 15.79n | 47.95n | 141.95n | +203.64% | +798.99% | -| Mul | 2 * 3 | 16.61n | 54.66n | 144.95n | +229.18% | +772.93% | -| QuoFinite | 2 / 4 | 64.74n | 381.15n | 645.35n | +488.74% | +896.83% | -| QuoInfinite | 2 / 3 | 595.30n | 1001.50n | 2810.50n | +68.23% | +372.11% | -| Pow | 1.1^60 | 1.31µ | 3.17µ | 20.50µ | +142.42% | +1469.53% | -| Pow | 1.01^600 | 4.36µ | 13.86µ | 44.39µ | +217.93% | +918.44% | -| Pow | 1.001^6000 | 7.39µ | 24.69µ | 656.84µ | +234.34% | +8793.66% | -| Parse | 1 | 17.27n | 78.25n | 128.80n | +353.23% | +646.02% | -| Parse | 123.456 | 39.80n | 211.85n | 237.60n | +432.22% | +496.91% | -| Parse | 123456789.1234567890 | 106.20n | 233.10n | 510.90n | +119.59% | +381.30% | -| String | 1 | 5.45n | 19.91n | 197.85n | +265.49% | +3531.94% | -| String | 123.456 | 42.38n | 74.83n | 229.50n | +76.57% | +441.53% | -| String | 123456789.1234567890 | 77.90n | 210.40n | 328.90n | +170.11% | +322.24% | +| Add | 2 + 3 | 15.53n | 46.68n | 142.30n | +200.45% | +816.00% | +| Mul | 2 * 3 | 15.64n | 52.83n | 137.35n | +237.76% | +778.20% | +| QuoFinite | 2 / 4 | 51.65n | 179.60n | 619.40n | +247.76% | +1099.34% | +| QuoInfinite | 2 / 3 | 568.80n | 935.20n | 2749.00n | +64.43% | +383.30% | +| Pow | 1.1^60 | 1.28µ | 3.28µ | 16.03µ | +156.99% | +1156.09% | +| Pow | 1.01^600 | 4.31µ | 10.43µ | 37.00µ | +142.15% | +758.69% | +| Pow | 1.001^6000 | 7.54µ | 20.39µ | 651.51µ | +170.58% | +8544.78% | +| Parse | 1 | 17.14n | 77.64n | 129.15n | +353.00% | +653.50% | +| Parse | 123.456 | 36.15n | 201.85n | 235.25n | +458.37% | +550.76% | +| Parse | 123456789.1234567890 | 98.90n | 210.95n | 475.05n | +113.30% | +380.33% | +| String | 1 | 5.18n | 21.43n | 208.00n | +313.99% | +3918.16% | +| String | 123.456 | 42.31n | 67.55n | 226.55n | +59.66% | +435.52% | +| String | 123456789.1234567890 | 76.04n | 209.50n | 329.95n | +175.49% | +333.89% | +| Telco | see [specification] | 134.00n | 947.60n | 3945.50n | +607.13% | +2844.40% | The benchmark results shown in the table are provided for informational purposes only and may vary depending on your specific use case. @@ -146,3 +163,4 @@ This ensures alignment with the project's objectives and roadmap. [awesomeb]: https://awesome.re/mentioned-badge.svg [cockroachdb]: https://pkg.go.dev/github.com/cockroachdb/apd [shopspring]: https://pkg.go.dev/github.com/shopspring/decimal +[specification]: https://speleotrove.com/decimal/telcoSpec.html diff --git a/decimal.go b/decimal.go index a9e1f46..11e2efe 100644 --- a/decimal.go +++ b/decimal.go @@ -13,8 +13,8 @@ import ( // It is designed to be safe for concurrent use by multiple goroutines. // The numeric value of a decimal is equal to: // -// coef / 10^scale if neg = false -// -coef / 10^scale if neg = true +// coef / 10^scale if !neg +// -coef / 10^scale if neg type Decimal struct { neg bool // indicates whether the decimal is negative scale int8 // position of the floating decimal point @@ -46,29 +46,31 @@ var ( errDivisionByZero = errors.New("division by zero") ) -func newDecimalUnsafe(neg bool, coef fint, scale int) Decimal { +// newUnsafe creates a new decimal without checking for overflow. +func newUnsafe(neg bool, coef fint, scale int) Decimal { if coef == 0 { neg = false } return Decimal{neg: neg, coef: coef, scale: int8(scale)} } -func newDecimalSafe(neg bool, coef fint, scale int) (Decimal, error) { +// newSafe creates a new decimal and checks for overflow. +func newSafe(neg bool, coef fint, scale int) (Decimal, error) { switch { case scale < MinScale || scale > MaxScale: return Decimal{}, errScaleRange case coef > maxCoef: return Decimal{}, errDecimalOverflow } - return newDecimalUnsafe(neg, coef, scale), nil + return newUnsafe(neg, coef, scale), nil } -// newDecimalFromFint creates a new decimal from uint64 coefficient. +// newFromFint creates a new decimal from uint64 coefficient. // This method does not use overflowError to return descriptive errors, // as it must be as fast as possible. -func newDecimalFromFint(neg bool, coef fint, scale, minScale int) (Decimal, error) { +func newFromFint(neg bool, coef fint, scale, minScale int) (Decimal, error) { var ok bool - // Normalization + // Scale normalization switch { case scale < minScale: coef, ok = coef.lsh(minScale - scale) @@ -80,7 +82,7 @@ func newDecimalFromFint(neg bool, coef fint, scale, minScale int) (Decimal, erro coef = coef.rshHalfEven(scale - MaxScale) scale = MaxScale } - return newDecimalSafe(neg, coef, scale) + return newSafe(neg, coef, scale) } func overflowError(gotPrec, gotScale, wantScale int) error { @@ -104,15 +106,15 @@ func unknownOverflowError(wantScale int) error { } } -// newDecimalFromBint creates a new decimal from *big.Int coefficient. +// newFromBint creates a new decimal from *big.Int coefficient. // This method uses overflowError to return descriptive errors. -func newDecimalFromBint(neg bool, coef *bint, scale, minScale int) (Decimal, error) { - // Check for overflow +func newFromBint(neg bool, coef *bint, scale, minScale int) (Decimal, error) { + // Overflow validation prec := coef.prec() if prec-scale > MaxPrec-minScale { return Decimal{}, overflowError(prec, scale, minScale) } - // Normalization + // Scale normalization switch { case scale < minScale: coef.lsh(coef, minScale-scale) @@ -124,24 +126,24 @@ func newDecimalFromBint(neg bool, coef *bint, scale, minScale int) (Decimal, err coef.rshHalfEven(coef, prec-MaxPrec) scale = scale - (prec - MaxPrec) } - // Handle the rare case when rshHalfEven rounded + // Handling the rare case when rshHalfEven rounded // a 19-digit coefficient to a 20-digit coefficient. if coef.hasPrec(MaxPrec + 1) { - return newDecimalFromBint(neg, coef, scale, minScale) + return newFromBint(neg, coef, scale, minScale) } - return newDecimalSafe(neg, coef.fint(), scale) + return newSafe(neg, coef.fint(), scale) } // New returns a decimal equal to coef / 10^scale. // -// New returns an error if scale is negative or more than [MaxScale]. +// New returns an error if scale is negative or greater than [MaxScale]. func New(coef int64, scale int) (Decimal, error) { var neg bool if coef < 0 { neg = true coef = -coef } - return newDecimalSafe(neg, fint(coef), scale) + return newSafe(neg, fint(coef), scale) } // MustNew is like [New] but panics if the decimal cannot be constructed. @@ -160,7 +162,7 @@ func MustNew(coef int64, scale int) Decimal { // // NewFromInt64 returns an error: // - if whole and fractional parts have different signs; -// - if scale is negative or more than [MaxScale]; +// - if scale is negative or greater than [MaxScale]; // - if frac / 10^scale is not within the range (-1, 1). func NewFromInt64(whole, frac int64, scale int) (Decimal, error) { if whole > 0 && frac < 0 || whole < 0 && frac > 0 { @@ -207,12 +209,12 @@ func NewFromFloat64(f float64) (Decimal, error) { // Zero returns a decimal with a value of 0, having the same scale as decimal d. func (d Decimal) Zero() Decimal { - return newDecimalUnsafe(false, 0, d.Scale()) + return newUnsafe(false, 0, d.Scale()) } // One returns a decimal with a value of 1, having the same scale as decimal d. func (d Decimal) One() Decimal { - return newDecimalUnsafe(false, pow10[d.Scale()], d.Scale()) + return newUnsafe(false, pow10[d.Scale()], d.Scale()) } // ULP (Unit in the Last Place) returns the smallest representable positive @@ -220,7 +222,7 @@ func (d Decimal) One() Decimal { // It can be useful for implementing rounding and comparison algorithms. // See also method [Decimal.One]. func (d Decimal) ULP() Decimal { - return newDecimalUnsafe(false, 1, d.Scale()) + return newUnsafe(false, 1, d.Scale()) } // Parse converts a string to a (possibly rounded) decimal. @@ -274,6 +276,7 @@ func ParseExact(s string, scale int) (Decimal, error) { return d, nil } +// parseFint parses a decimal string using uint64 arithmetic. // parseFint does not support exponential notation to make it as fast as possible. // //gocyclo:ignore @@ -326,9 +329,10 @@ func parseFint(s string, minScale int) (Decimal, error) { if !hascoef { return Decimal{}, fmt.Errorf("no coefficient: %w", errInvalidDecimal) } - return newDecimalFromFint(neg, coef, scale, minScale) + return newFromFint(neg, coef, scale, minScale) } +// parseBint parses a decimal string using *big.Int arithmetic. // parseBint supports exponential notation. // //gocyclo:ignore @@ -412,7 +416,7 @@ func parseBint(s string, minScale int) (Decimal, error) { scale = scale - exp } - return newDecimalFromBint(neg, coef, scale, minScale) + return newFromBint(neg, coef, scale, minScale) } // MustParse is like [Parse] but panics if the string cannot be parsed. @@ -791,7 +795,7 @@ func (d Decimal) MinScale() int { return d.Scale() - z } -// IsInt returns true if the fractional part of the decimal is zero. +// IsInt returns true if there are no significant digits after the decimal point. func (d Decimal) IsInt() bool { return d.coef%pow10[d.Scale()] == 0 } @@ -829,7 +833,7 @@ func (d Decimal) Round(scale int) Decimal { } coef := d.coef coef = coef.rshHalfEven(d.Scale() - scale) - return newDecimalUnsafe(d.IsNeg(), coef, scale) + return newUnsafe(d.IsNeg(), coef, scale) } // Pad returns a decimal zero-padded to the specified number of digits after @@ -850,14 +854,15 @@ func (d Decimal) Pad(scale int) (Decimal, error) { if !ok { return Decimal{}, fmt.Errorf("padding %v with zeros: %w", d, overflowError(d.Prec(), d.Scale(), scale)) } - return newDecimalSafe(d.IsNeg(), coef, scale) + return newSafe(d.IsNeg(), coef, scale) } // Rescale returns a decimal rounded or zero-padded to the given number of digits // after the decimal point. // For financial calculations, the scale should be equal to or greater than // the scale of the currency. -// Rescale returns an error if the integer part of the result has more +// +// Rescale returns an overflow error if the integer part of the result has more // than ([MaxPrec] - scale) digits. func (d Decimal) Rescale(scale int) (Decimal, error) { if scale < MinScale || scale > MaxScale { @@ -876,7 +881,8 @@ func (d Decimal) Rescale(scale int) (Decimal, error) { // The sign and coefficient of decimal e are ignored. // See also method [Decimal.Rescale]. // -// Qunatize returns an error if the integer part of result has more than ([MaxPrec] - e.Scale()) digits. +// Qunatize returns an overflow error if the integer part of result has more +// than ([MaxPrec] - e.Scale()) digits. func (d Decimal) Quantize(e Decimal) (Decimal, error) { return d.Rescale(e.Scale()) } @@ -897,7 +903,7 @@ func (d Decimal) Trunc(scale int) Decimal { } coef := d.coef coef = coef.rshDown(d.Scale() - scale) - return newDecimalUnsafe(d.IsNeg(), coef, scale) + return newUnsafe(d.IsNeg(), coef, scale) } // Trim returns a decimal with trailing zeros removed @@ -933,7 +939,7 @@ func (d Decimal) Ceil(scale int) Decimal { } else { coef = coef.rshUp(d.Scale() - scale) } - return newDecimalUnsafe(d.IsNeg(), coef, scale) + return newUnsafe(d.IsNeg(), coef, scale) } // Floor returns a decimal rounded down to the specified number of digits @@ -957,17 +963,17 @@ func (d Decimal) Floor(scale int) Decimal { } else { coef = coef.rshDown(d.Scale() - scale) } - return newDecimalUnsafe(d.IsNeg(), coef, scale) + return newUnsafe(d.IsNeg(), coef, scale) } // Neg returns a decimal with the opposite sign. func (d Decimal) Neg() Decimal { - return newDecimalUnsafe(!d.IsNeg(), d.coef, d.Scale()) + return newUnsafe(!d.IsNeg(), d.coef, d.Scale()) } // Abs returns the absolute value of the decimal. func (d Decimal) Abs() Decimal { - return newDecimalUnsafe(false, d.coef, d.Scale()) + return newUnsafe(false, d.coef, d.Scale()) } // CopySign returns a decimal with the same sign as decimal e. @@ -1020,14 +1026,16 @@ func (d Decimal) IsZero() bool { // Mul returns the (possibly rounded) product of decimals d and e. // -// Mul returns an error if the integer part of the result has more than [MaxPrec] digits. +// Mul returns an overflow error if the integer part of the result has +// more than [MaxPrec] digits. func (d Decimal) Mul(e Decimal) (Decimal, error) { return d.MulExact(e, 0) } -// MulExact is similar to [Decimal.Mul], but it allows you to specify the number of digits -// after the decimal point that should be considered significant. -// If any of the significant digits are lost during rounding, the method will return an error. +// MulExact is similar to [Decimal.Mul], but it allows you to specify the number +// of digits after the decimal point that should be considered significant. +// If any of the significant digits are lost during rounding, the method will +// return an overflow error. // This method is useful for financial calculations where the scale should be // equal to or greater than the currency's scale. func (d Decimal) MulExact(e Decimal, scale int) (Decimal, error) { @@ -1059,7 +1067,7 @@ func (d Decimal) mulFint(e Decimal, minScale int) (Decimal, error) { // Scale scale := d.Scale() + e.Scale() - return newDecimalFromFint(neg, dcoef, scale, minScale) + return newFromFint(neg, dcoef, scale, minScale) } func (d Decimal) mulBint(e Decimal, minScale int) (Decimal, error) { @@ -1075,7 +1083,7 @@ func (d Decimal) mulBint(e Decimal, minScale int) (Decimal, error) { // Scale scale := d.Scale() + e.Scale() - return newDecimalFromBint(neg, dcoef, scale, minScale) + return newFromBint(neg, dcoef, scale, minScale) } // Pow returns the (possibly rounded) decimal raised to the given power. @@ -1087,9 +1095,10 @@ func (d Decimal) Pow(power int) (Decimal, error) { return d.PowExact(power, 0) } -// PowExact is similar to [Decimal.Pow], but it allows you to specify the number of digits -// after the decimal point that should be considered significant. -// If any of the significant digits are lost during rounding, the method will return an error. +// PowExact is similar to [Decimal.Pow], but it allows you to specify the number +// of digits after the decimal point that should be considered significant. +// If any of the significant digits are lost during rounding, the method will +// return an overflow error. // This method is useful for financial calculations where the scale should be // equal to or greater than the currency's scale. func (d Decimal) PowExact(power, scale int) (Decimal, error) { @@ -1161,7 +1170,7 @@ func (d Decimal) powFint(power, minScale int) (Decimal, error) { dscale = dscale * 2 } } - return newDecimalFromFint(eneg, ecoef, escale, minScale) + return newFromFint(eneg, ecoef, escale, minScale) } func (d Decimal) powBint(power, minScale int) (Decimal, error) { @@ -1227,7 +1236,7 @@ func (d Decimal) powBint(power, minScale int) (Decimal, error) { escale = 2 * MaxScale } - return newDecimalFromBint(eneg, ecoef, escale, minScale) + return newFromBint(eneg, ecoef, escale, minScale) } // Add returns the (possibly rounded) sum of decimals d and e. @@ -1297,7 +1306,7 @@ func (d Decimal) addFint(e Decimal, minScale int) (Decimal, error) { } } - return newDecimalFromFint(neg, dcoef, scale, minScale) + return newFromFint(neg, dcoef, scale, minScale) } func (d Decimal) addBint(e Decimal, minScale int) (Decimal, error) { @@ -1332,7 +1341,7 @@ func (d Decimal) addBint(e Decimal, minScale int) (Decimal, error) { dcoef.add(dcoef, ecoef) } - return newDecimalFromBint(neg, dcoef, scale, minScale) + return newFromBint(neg, dcoef, scale, minScale) } // Sub returns the (possibly rounded) difference between decimals d and e. @@ -1437,7 +1446,7 @@ func (d Decimal) fmaFint(e, f Decimal, minScale int) (Decimal, error) { } } - return newDecimalFromFint(neg, dcoef, scale, minScale) + return newFromFint(neg, dcoef, scale, minScale) } func (d Decimal) fmaBint(e, f Decimal, minScale int) (Decimal, error) { @@ -1473,7 +1482,7 @@ func (d Decimal) fmaBint(e, f Decimal, minScale int) (Decimal, error) { dcoef.add(dcoef, fcoef) } - return newDecimalFromBint(neg, dcoef, scale, minScale) + return newFromBint(neg, dcoef, scale, minScale) } // Quo returns the (possibly rounded) quotient of decimals d and e. @@ -1505,7 +1514,7 @@ func (d Decimal) QuoExact(e Decimal, scale int) (Decimal, error) { if t := d.Scale() - e.Scale(); scale < t { scale = t } - return newDecimalSafe(false, 0, scale) + return newSafe(false, 0, scale) } // General case @@ -1557,7 +1566,7 @@ func (d Decimal) quoFint(e Decimal, minScale int) (Decimal, error) { // Sign neg := d.IsNeg() != e.IsNeg() - return newDecimalFromFint(neg, dcoef, scale, minScale) + return newFromFint(neg, dcoef, scale, minScale) } func (d Decimal) quoBint(e Decimal, minScale int) (Decimal, error) { @@ -1576,11 +1585,12 @@ func (d Decimal) quoBint(e Decimal, minScale int) (Decimal, error) { // Sign neg := d.IsNeg() != e.IsNeg() - return newDecimalFromBint(neg, dcoef, scale, minScale) + return newFromBint(neg, dcoef, scale, minScale) } // QuoRem returns the quotient q and remainder r of decimals d and e -// such that d = e * q + r, where q is an integer. +// such that d = e * q + r, where q is an integer and the sign of the +// reminder r is the same as the sign of the dividend d. // // QuoRem returns an error if: // - the integer part of the quotient has more than [MaxPrec] digits; @@ -1588,7 +1598,7 @@ func (d Decimal) quoBint(e Decimal, minScale int) (Decimal, error) { func (d Decimal) QuoRem(e Decimal) (q, r Decimal, err error) { q, r, err = d.quoRem(e) if err != nil { - return Decimal{}, Decimal{}, fmt.Errorf("computing [%v // %v] and [%v mod %v]: %w", d, e, d, e, err) + return Decimal{}, Decimal{}, fmt.Errorf("computing [%v div %v] and [%v mod %v]: %w", d, e, d, e, err) } return q, r, nil } @@ -1599,7 +1609,7 @@ func (d Decimal) quoRem(e Decimal) (q, r Decimal, err error) { if err != nil { return Decimal{}, Decimal{}, err } - q = q.Trunc(0) + q = q.Trunc(0) // T-Division // Reminder r, err = e.Mul(q) @@ -1801,8 +1811,14 @@ func (n *NullDecimal) Scan(value any) error { n.Valid = false return nil } + err := n.Decimal.Scan(value) + if err != nil { + n.Decimal = Decimal{} + n.Valid = false + return err + } n.Valid = true - return n.Decimal.Scan(value) + return nil } // Value implements the [driver.Valuer] interface. diff --git a/decimal_test.go b/decimal_test.go index 9bf84ff..aaac0d3 100644 --- a/decimal_test.go +++ b/decimal_test.go @@ -92,7 +92,7 @@ func TestNewFromInt64(t *testing.T) { tests := []struct { whole, frac int64 scale int - d string + want string }{ {0, 0, 0, "0"}, {0, 0, 1, "0.0"}, @@ -119,7 +119,7 @@ func TestNewFromInt64(t *testing.T) { t.Errorf("NewFromInt64(%v, %v, %v) failed: %v", tt.whole, tt.frac, tt.scale, err) continue } - want := MustParse(tt.d) + want := MustParse(tt.want) if got != want { t.Errorf("NewFromInt64(%v, %v, %v) = %q, want %q", tt.whole, tt.frac, tt.scale, got, want) } @@ -400,7 +400,7 @@ func TestDecimal_String(t *testing.T) { {false, maxCoef, 19, "0.9999999999999999999"}, } for _, tt := range tests { - d, err := newDecimalSafe(tt.neg, tt.coef, tt.scale) + d, err := newSafe(tt.neg, tt.coef, tt.scale) if err != nil { t.Errorf("newDecimal(%v, %v, %v) failed: %v", tt.neg, tt.coef, tt.scale, err) continue @@ -2672,7 +2672,7 @@ var corpus = []struct { func FuzzParse(f *testing.F) { for _, c := range corpus { - d, err := newDecimalSafe(c.neg, fint(c.coef), c.scale) + d, err := newSafe(c.neg, fint(c.coef), c.scale) if err != nil { continue } @@ -2698,7 +2698,7 @@ func FuzzDecimalString(f *testing.F) { f.Fuzz( func(t *testing.T, neg bool, scale int, coef uint64) { - want, err := newDecimalSafe(neg, fint(coef), scale) + want, err := newSafe(neg, fint(coef), scale) if err != nil { t.Skip() return @@ -2727,7 +2727,7 @@ func FuzzDecimalInt64(f *testing.F) { f.Fuzz( func(t *testing.T, dneg bool, dscale int, dcoef uint64, scale int) { - want, err := newDecimalSafe(dneg, fint(dcoef), dscale) + want, err := newSafe(dneg, fint(dcoef), dscale) if err != nil { t.Skip() return @@ -2761,7 +2761,7 @@ func FuzzDecimalFloat64(f *testing.F) { f.Fuzz( func(t *testing.T, dneg bool, dscale int, dcoef uint64) { - want, err := newDecimalSafe(dneg, fint(dcoef), dscale) + want, err := newSafe(dneg, fint(dcoef), dscale) if err != nil || want.Prec() > 17 { t.Skip() return @@ -2803,12 +2803,12 @@ func FuzzDecimalMul(f *testing.F) { t.Skip() return } - d, err := newDecimalSafe(dneg, fint(dcoef), dscale) + d, err := newSafe(dneg, fint(dcoef), dscale) if err != nil { t.Skip() return } - e, err := newDecimalSafe(eneg, fint(ecoef), escale) + e, err := newSafe(eneg, fint(ecoef), escale) if err != nil { t.Skip() return @@ -2853,17 +2853,17 @@ func FuzzDecimalFMA(f *testing.F) { t.Skip() return } - d, err := newDecimalSafe(dneg, fint(dcoef), dscale) + d, err := newSafe(dneg, fint(dcoef), dscale) if err != nil { t.Skip() return } - e, err := newDecimalSafe(eneg, fint(ecoef), escale) + e, err := newSafe(eneg, fint(ecoef), escale) if err != nil { t.Skip() return } - g, err := newDecimalSafe(gneg, fint(gcoef), gscale) + g, err := newSafe(gneg, fint(gcoef), gscale) if err != nil { t.Skip() return @@ -2906,12 +2906,12 @@ func FuzzDecimalAdd(f *testing.F) { t.Skip() return } - d, err := newDecimalSafe(dneg, fint(dcoef), dscale) + d, err := newSafe(dneg, fint(dcoef), dscale) if err != nil { t.Skip() return } - e, err := newDecimalSafe(eneg, fint(ecoef), escale) + e, err := newSafe(eneg, fint(ecoef), escale) if err != nil { t.Skip() return @@ -2958,12 +2958,12 @@ func FuzzDecimalQuo(f *testing.F) { t.Skip() return } - d, err := newDecimalSafe(dneg, fint(dcoef), dscale) + d, err := newSafe(dneg, fint(dcoef), dscale) if err != nil { t.Skip() return } - e, err := newDecimalSafe(eneg, fint(ecoef), escale) + e, err := newSafe(eneg, fint(ecoef), escale) if err != nil { t.Skip() return @@ -3013,12 +3013,12 @@ func FuzzDecimalQuoRem(f *testing.F) { t.Skip() return } - want, err := newDecimalSafe(dneg, fint(dcoef), dscale) + want, err := newSafe(dneg, fint(dcoef), dscale) if err != nil { t.Skip() return } - e, err := newDecimalSafe(eneg, fint(ecoef), escale) + e, err := newSafe(eneg, fint(ecoef), escale) if err != nil { t.Skip() return @@ -3061,13 +3061,13 @@ func FuzzDecimalCmp(f *testing.F) { f.Fuzz( func(t *testing.T, dneg bool, dscale int, dcoef uint64, eneg bool, escale int, ecoef uint64) { - d, err := newDecimalSafe(dneg, fint(dcoef), dscale) + d, err := newSafe(dneg, fint(dcoef), dscale) if err != nil { t.Skip() return } - e, err := newDecimalSafe(eneg, fint(ecoef), escale) + e, err := newSafe(eneg, fint(ecoef), escale) if err != nil { t.Skip() return @@ -3101,13 +3101,13 @@ func FuzzDecimalCmpSub(f *testing.F) { f.Fuzz( func(t *testing.T, dneg bool, dscale int, dcoef uint64, eneg bool, escale int, ecoef uint64) { - d, err := newDecimalSafe(dneg, fint(dcoef), dscale) + d, err := newSafe(dneg, fint(dcoef), dscale) if err != nil { t.Skip() return } - e, err := newDecimalSafe(eneg, fint(ecoef), escale) + e, err := newSafe(eneg, fint(ecoef), escale) if err != nil { t.Skip() return @@ -3139,12 +3139,12 @@ func FuzzDecimalNew(f *testing.F) { f.Fuzz( func(t *testing.T, neg bool, scale int, coef uint64) { - got, err := newDecimalFromFint(neg, fint(coef), scale, 0) + got, err := newFromFint(neg, fint(coef), scale, 0) if err != nil { t.Skip() return } - want, err := newDecimalFromBint(neg, fint(coef).bint(), scale, 0) + want, err := newFromBint(neg, fint(coef).bint(), scale, 0) if err != nil { t.Errorf("newDecimalFromBint(%v, %v, %v, 0) failed: %v", neg, coef, scale, err) return diff --git a/doc_test.go b/doc_test.go index 6086121..73bec89 100644 --- a/doc_test.go +++ b/doc_test.go @@ -88,71 +88,103 @@ func approximate(terms int) (decimal.Decimal, error) { return pi, nil } -// This example calculates an approximate value of pi using the Leibniz formula for pi. -// The Leibniz formula is an infinite series that converges to pi/4, and is -// given by the equation: 1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + ... = pi/4. -// This example computes the series up to the 50,000th term using decimal arithmetic -// and returns the approximate value of pi. +// This example calculates an approximate value of π using the [Leibniz formula]. +// The Leibniz formula is an infinite series that converges to π/4, and is +// given by the equation: 1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + ... = π/4. +// This example computes the series up to the 500,000th term using decimal arithmetic +// and returns the approximate value of π. +// +// [Leibniz formula]: https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80 func Example_piApproximation() { - pi, err := approximate(50000) + pi, err := approximate(500000) if err != nil { panic(err) } fmt.Println(pi) fmt.Println(decimal.Pi) // Output: - // 3.141572653589795330 + // 3.141590653589793206 // 3.141592653589793238 } +// This example demonstrates the advantage of decimal types for financial calculations. +// It calculates sum 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1. +// In decimal arithmetic, the result is exactly 1.0. +// In float64 arithmetic, due to binary floating-point representation, +// the sum slightly deviates from 1.0. +func Example_float64Inaccuracy() { + // Decimal arithmetic + d := decimal.MustParse("0.0") + e := decimal.MustParse("0.1") + for i := 0; i < 10; i++ { + d, _ = d.Add(e) + } + fmt.Println(d) + // float64 arithmetic + f := 0.0 + for i := 0; i < 10; i++ { + f += 0.1 + } + fmt.Println(f) + // Output: + // 1.0 + // 0.9999999999999999 +} + func ExampleMustNew() { - fmt.Println(decimal.MustNew(123, 3)) - fmt.Println(decimal.MustNew(123, 2)) - fmt.Println(decimal.MustNew(123, 1)) - fmt.Println(decimal.MustNew(123, 0)) - // Output: - // 0.123 - // 1.23 - // 12.3 - // 123 + fmt.Println(decimal.MustNew(567, 0)) + fmt.Println(decimal.MustNew(567, 1)) + fmt.Println(decimal.MustNew(567, 2)) + fmt.Println(decimal.MustNew(567, 3)) + fmt.Println(decimal.MustNew(567, 4)) + // Output: + // 567 + // 56.7 + // 5.67 + // 0.567 + // 0.0567 } func ExampleNew() { - fmt.Println(decimal.New(123, 3)) - fmt.Println(decimal.New(123, 2)) - fmt.Println(decimal.New(123, 1)) - fmt.Println(decimal.New(123, 0)) + fmt.Println(decimal.New(567, 0)) + fmt.Println(decimal.New(567, 1)) + fmt.Println(decimal.New(567, 2)) + fmt.Println(decimal.New(567, 3)) + fmt.Println(decimal.New(567, 4)) // Output: - // 0.123 - // 1.23 - // 12.3 - // 123 + // 567 + // 56.7 + // 5.67 + // 0.567 + // 0.0567 } func ExampleNewFromInt64() { - fmt.Println(decimal.NewFromInt64(1, 23, 5)) - fmt.Println(decimal.NewFromInt64(1, 23, 4)) - fmt.Println(decimal.NewFromInt64(1, 23, 3)) - fmt.Println(decimal.NewFromInt64(1, 23, 2)) + fmt.Println(decimal.NewFromInt64(8, 9, 1)) + fmt.Println(decimal.NewFromInt64(8, 9, 2)) + fmt.Println(decimal.NewFromInt64(8, 9, 3)) + fmt.Println(decimal.NewFromInt64(8, 9, 4)) + fmt.Println(decimal.NewFromInt64(8, 9, 5)) // Output: - // 1.00023 - // 1.0023 - // 1.023 - // 1.23 + // 8.9 + // 8.09 + // 8.009 + // 8.0009 + // 8.00009 } func ExampleNewFromFloat64() { - fmt.Println(decimal.NewFromFloat64(1.23e-2)) - fmt.Println(decimal.NewFromFloat64(1.23e-1)) - fmt.Println(decimal.NewFromFloat64(1.23e0)) - fmt.Println(decimal.NewFromFloat64(1.23e1)) - fmt.Println(decimal.NewFromFloat64(1.23e2)) + fmt.Println(decimal.NewFromFloat64(5.67e-2)) + fmt.Println(decimal.NewFromFloat64(5.67e-1)) + fmt.Println(decimal.NewFromFloat64(5.67e0)) + fmt.Println(decimal.NewFromFloat64(5.67e1)) + fmt.Println(decimal.NewFromFloat64(5.67e2)) // Output: - // 0.0123 - // 0.123 - // 1.23 - // 12.3 - // 123 + // 0.0567 + // 0.567 + // 5.67 + // 56.7 + // 567 } func ExampleDecimal_Zero() { @@ -195,24 +227,22 @@ func ExampleDecimal_ULP() { } func ExampleParse() { - fmt.Println(decimal.Parse("-1.23")) - // Output: -1.23 + fmt.Println(decimal.Parse("5.67")) + // Output: 5.67 } func ExampleParseExact() { - fmt.Println(decimal.ParseExact("-1.23", 0)) - fmt.Println(decimal.ParseExact("-1.23", 1)) - fmt.Println(decimal.ParseExact("-1.23", 2)) - fmt.Println(decimal.ParseExact("-1.23", 3)) - fmt.Println(decimal.ParseExact("-1.23", 4)) - fmt.Println(decimal.ParseExact("-1.23", 5)) + fmt.Println(decimal.ParseExact("5.67", 0)) + fmt.Println(decimal.ParseExact("5.67", 1)) + fmt.Println(decimal.ParseExact("5.67", 2)) + fmt.Println(decimal.ParseExact("5.67", 3)) + fmt.Println(decimal.ParseExact("5.67", 4)) // Output: - // -1.23 - // -1.23 - // -1.23 - // -1.230 - // -1.2300 - // -1.23000 + // 5.67 + // 5.67 + // 5.67 + // 5.670 + // 5.6700 } func ExampleMustParse() { @@ -240,20 +270,18 @@ func ExampleDecimal_Float64() { } func ExampleDecimal_Int64() { - d := decimal.MustParse("123.567") - fmt.Println(d.Int64(5)) - fmt.Println(d.Int64(4)) - fmt.Println(d.Int64(3)) - fmt.Println(d.Int64(2)) - fmt.Println(d.Int64(1)) + d := decimal.MustParse("5.67") fmt.Println(d.Int64(0)) + fmt.Println(d.Int64(1)) + fmt.Println(d.Int64(2)) + fmt.Println(d.Int64(3)) + fmt.Println(d.Int64(4)) // Output: - // 123 56700 true - // 123 5670 true - // 123 567 true - // 123 57 true - // 123 6 true - // 124 0 true + // 6 0 true + // 5 7 true + // 5 67 true + // 5 670 true + // 5 6700 true } type Value struct { @@ -262,40 +290,42 @@ type Value struct { func ExampleDecimal_UnmarshalText() { var v Value - _ = json.Unmarshal([]byte(`{"number": "-15.67"}`), &v) + _ = json.Unmarshal([]byte(`{"number": "15.67"}`), &v) fmt.Println(v) - // Output: {-15.67} + // Output: {15.67} } func ExampleDecimal_MarshalText() { - v := Value{Number: decimal.MustParse("-15.67")} + v := Value{ + Number: decimal.MustParse("15.67"), + } b, _ := json.Marshal(v) fmt.Println(string(b)) - // Output: {"number":"-15.67"} + // Output: {"number":"15.67"} } func ExampleDecimal_Scan() { var d decimal.Decimal - _ = d.Scan("-15.67") + _ = d.Scan("15.67") fmt.Println(d) - // Output: -15.67 + // Output: 15.67 } func ExampleDecimal_Value() { - d := decimal.MustParse("-15.67") + d := decimal.MustParse("15.67") fmt.Println(d.Value()) - // Output: -15.67 + // Output: 15.67 } func ExampleDecimal_Format() { - d := decimal.MustParse("-15.679") + d := decimal.MustParse("15.679") fmt.Printf("%k\n", d) fmt.Printf("%f\n", d) fmt.Printf("%.2f\n", d) // Output: - // -1567.9% - // -15.679 - // -15.68 + // 1567.9% + // 15.679 + // 15.68 } func ExampleDecimal_Coef() { @@ -359,27 +389,32 @@ func ExampleDecimal_FMAExact() { d := decimal.MustParse("2") e := decimal.MustParse("3") f := decimal.MustParse("4") + fmt.Println(d.FMAExact(e, f, 0)) + fmt.Println(d.FMAExact(e, f, 1)) fmt.Println(d.FMAExact(e, f, 2)) - // Output: 10.00 + fmt.Println(d.FMAExact(e, f, 3)) + fmt.Println(d.FMAExact(e, f, 4)) + // Output: + // 10 + // 10.0 + // 10.00 + // 10.000 + // 10.0000 } func ExampleDecimal_Pow() { d := decimal.MustParse("2") - fmt.Println(d.Pow(-3)) fmt.Println(d.Pow(-2)) fmt.Println(d.Pow(-1)) fmt.Println(d.Pow(0)) fmt.Println(d.Pow(1)) fmt.Println(d.Pow(2)) - fmt.Println(d.Pow(3)) // Output: - // 0.125 // 0.25 // 0.5 // 1 // 2 // 4 - // 8 } func ExampleDecimal_PowExact() { @@ -451,28 +486,26 @@ func ExampleDecimal_SubExact() { } func ExampleDecimal_Quo() { - d := decimal.MustParse("-15.67") + d := decimal.MustParse("15.67") e := decimal.MustParse("2") fmt.Println(d.Quo(e)) - // Output: -7.835 + // Output: 7.835 } func ExampleDecimal_QuoExact() { - d := decimal.MustParse("-15.66") + d := decimal.MustParse("15.66") e := decimal.MustParse("3") fmt.Println(d.QuoExact(e, 0)) fmt.Println(d.QuoExact(e, 1)) fmt.Println(d.QuoExact(e, 2)) fmt.Println(d.QuoExact(e, 3)) fmt.Println(d.QuoExact(e, 4)) - fmt.Println(d.QuoExact(e, 5)) // Output: - // -5.22 - // -5.22 - // -5.22 - // -5.220 - // -5.2200 - // -5.22000 + // 5.22 + // 5.22 + // 5.22 + // 5.220 + // 5.2200 } func ExampleDecimal_QuoRem() { @@ -554,119 +587,107 @@ func ExampleDecimal_Clamp() { } func ExampleDecimal_Rescale() { - d := decimal.MustParse("15.679") - fmt.Println(d.Rescale(5)) - fmt.Println(d.Rescale(4)) - fmt.Println(d.Rescale(3)) - fmt.Println(d.Rescale(2)) - fmt.Println(d.Rescale(1)) + d := decimal.MustParse("5.678") fmt.Println(d.Rescale(0)) + fmt.Println(d.Rescale(1)) + fmt.Println(d.Rescale(2)) + fmt.Println(d.Rescale(3)) + fmt.Println(d.Rescale(4)) // Output: - // 15.67900 - // 15.6790 - // 15.679 - // 15.68 - // 15.7 - // 16 + // 6 + // 5.7 + // 5.68 + // 5.678 + // 5.6780 } func ExampleDecimal_Quantize() { - d := decimal.MustParse("15.6789") - x := decimal.MustParse("0.01") + d := decimal.MustParse("5.678") + x := decimal.MustParse("1") y := decimal.MustParse("0.1") - z := decimal.MustParse("1") + z := decimal.MustParse("0.01") fmt.Println(d.Quantize(x)) fmt.Println(d.Quantize(y)) fmt.Println(d.Quantize(z)) // Output: - // 15.68 - // 15.7 - // 16 + // 6 + // 5.7 + // 5.68 } func ExampleDecimal_Pad() { - d := decimal.MustParse("15.67") - fmt.Println(d.Pad(5)) - fmt.Println(d.Pad(4)) - fmt.Println(d.Pad(3)) - fmt.Println(d.Pad(2)) - fmt.Println(d.Pad(1)) + d := decimal.MustParse("5.67") fmt.Println(d.Pad(0)) + fmt.Println(d.Pad(1)) + fmt.Println(d.Pad(2)) + fmt.Println(d.Pad(3)) + fmt.Println(d.Pad(4)) // Output: - // 15.67000 - // 15.6700 - // 15.670 - // 15.67 - // 15.67 - // 15.67 + // 5.67 + // 5.67 + // 5.67 + // 5.670 + // 5.6700 } func ExampleDecimal_Round() { - d := decimal.MustParse("15.6789") - fmt.Println(d.Round(5)) - fmt.Println(d.Round(4)) - fmt.Println(d.Round(3)) - fmt.Println(d.Round(2)) - fmt.Println(d.Round(1)) + d := decimal.MustParse("5.678") fmt.Println(d.Round(0)) + fmt.Println(d.Round(1)) + fmt.Println(d.Round(2)) + fmt.Println(d.Round(3)) + fmt.Println(d.Round(4)) // Output: - // 15.6789 - // 15.6789 - // 15.679 - // 15.68 - // 15.7 - // 16 + // 6 + // 5.7 + // 5.68 + // 5.678 + // 5.678 } func ExampleDecimal_Trunc() { - d := decimal.MustParse("15.6789") - fmt.Println(d.Trunc(5)) - fmt.Println(d.Trunc(4)) - fmt.Println(d.Trunc(3)) - fmt.Println(d.Trunc(2)) - fmt.Println(d.Trunc(1)) + d := decimal.MustParse("5.678") fmt.Println(d.Trunc(0)) + fmt.Println(d.Trunc(1)) + fmt.Println(d.Trunc(2)) + fmt.Println(d.Trunc(3)) + fmt.Println(d.Trunc(4)) // Output: - // 15.6789 - // 15.6789 - // 15.678 - // 15.67 - // 15.6 - // 15 + // 5 + // 5.6 + // 5.67 + // 5.678 + // 5.678 } func ExampleDecimal_Ceil() { - d := decimal.MustParse("15.6789") - fmt.Println(d.Ceil(5)) - fmt.Println(d.Ceil(4)) - fmt.Println(d.Ceil(3)) - fmt.Println(d.Ceil(2)) - fmt.Println(d.Ceil(1)) + d := decimal.MustParse("5.678") fmt.Println(d.Ceil(0)) + fmt.Println(d.Ceil(1)) + fmt.Println(d.Ceil(2)) + fmt.Println(d.Ceil(3)) + fmt.Println(d.Ceil(4)) // Output: - // 15.6789 - // 15.6789 - // 15.679 - // 15.68 - // 15.7 - // 16 + // 6 + // 5.7 + // 5.68 + // 5.678 + // 5.678 } func ExampleDecimal_Floor() { - d := decimal.MustParse("15.6789") - fmt.Println(d.Floor(5)) - fmt.Println(d.Floor(4)) - fmt.Println(d.Floor(3)) - fmt.Println(d.Floor(2)) - fmt.Println(d.Floor(1)) + d := decimal.MustParse("5.678") fmt.Println(d.Floor(0)) + fmt.Println(d.Floor(1)) + fmt.Println(d.Floor(2)) + fmt.Println(d.Floor(3)) + fmt.Println(d.Floor(4)) // Output: - // 15.6789 - // 15.6789 - // 15.678 - // 15.67 - // 15.6 - // 15 + // 5 + // 5.6 + // 5.67 + // 5.678 + // 5.678 } func ExampleDecimal_Scale() { @@ -690,20 +711,18 @@ func ExampleDecimal_MinScale() { } func ExampleDecimal_Trim() { - d := decimal.MustParse("23.4000") - fmt.Println(d.Trim(5)) - fmt.Println(d.Trim(4)) - fmt.Println(d.Trim(3)) - fmt.Println(d.Trim(2)) - fmt.Println(d.Trim(1)) + d := decimal.MustParse("23.400") fmt.Println(d.Trim(0)) + fmt.Println(d.Trim(1)) + fmt.Println(d.Trim(2)) + fmt.Println(d.Trim(3)) + fmt.Println(d.Trim(4)) // Output: - // 23.4000 - // 23.4000 - // 23.400 - // 23.40 // 23.4 // 23.4 + // 23.40 + // 23.400 + // 23.400 } func ExampleDecimal_Abs() {