Skip to content

Commit

Permalink
decimal: review error descriptions
Browse files Browse the repository at this point in the history
* decimal: review error descriptions

* decimal: review error descriptions
  • Loading branch information
eapenkin authored Aug 27, 2023
1 parent f458f1e commit a4898ec
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
- name: Run fuzzing for multiplication
run: go test -fuzztime 20s -fuzz ^FuzzDecimalMul$ github.com/govalues/decimal

- name: Run fuzzing for fused multiply-addidtion
- name: Run fuzzing for fused multiply-addition
run: go test -fuzztime 40s -fuzz ^FuzzDecimalFMA$ github.com/govalues/decimal

- name: Run fuzzing for division
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [0.1.9] - 2025-08-27

### Changed

- Reviewed error descriptions.

## [0.1.8] - 2023-08-23

### Changed
Expand Down
36 changes: 22 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,28 @@ pkg: github.com/govalues/benchmarks
cpu: AMD Ryzen 7 3700C with Radeon Vega Mobile Gfx
```

| Test Case | Expression | govalues | [cockroachdb] v3.2.0 | cockroachdb vs govalues | [shopspring] v1.3.1 | shopspring vs govalues |
| ------------------- | -------------------- | -------: | -------------------: | ----------------------: | ------------------: | ---------------------: |
| Decimal_Add | 2 + 3 | 15.73n | 50.24n | +219.39% | 141.85n | +801.78% |
| Decimal_Mul | 2 * 3 | 15.67n | 67.34n | +329.57% | 139.55n | +790.27% |
| Decimal_QuoFinite | 2 / 4 | 61.08n | 371.35n | +508.02% | 629.70n | +931.03% |
| Decimal_QuoInfinite | 2 / 3 | 560.80n | 946.10n | +68.69% | 2736.50n | +387.92% |
| Decimal_Pow | 1.1^60 | 1.02µ | 2.89µ | +182.58% | 20.08µ | +1865.17% |
| Parse | 123456789.1234567890 | 99.16n | 219.15n | +121.01% | 480.10n | +384.17% |
| Parse | 123.456 | 37.41n | 203.80n | +444.85% | 229.20n | +512.75% |
| Parse | 1 | 16.37n | 76.27n | +365.91% | 128.90n | +687.42% |
| Decimal_String | 123456789.1234567890 | 76.47n | 215.15n | +181.35% | 329.35n | +330.69% |
| Decimal_String | 123.456 | 40.86n | 70.52n | +72.59% | 222.10n | +443.56% |
| Decimal_String | 1 | 5.32n | 19.41n | +264.88% | 192.75n | +3523.46% |
| **Geometric Mean** | | 52.70n | 170.10n | +222.71% | 445.40n | +745.22% |
| Test Case | Expression | govalues | [cockroachdb] v3.2.0 | cockroachdb vs govalues | [shopspring] v1.3.1 | shopspring vs govalues |
| -------------- | -------------------- | -------: | -------------------: | ----------------------: | ------------------: | ---------------------: |
| Add | 2 + 3 | 15.79n | 47.95n | +203.64% | 141.95n | +798.99% |
| Mul | 2 * 3 | 16.61n | 54.66n | +229.18% | 144.95n | +772.93% |
| QuoFinite | 2 / 4 | 64.74n | 381.15n | +488.74% | 645.35n | +896.83% |
| QuoInfinite | 2 / 3 | 595.30n | 1001.50n | +68.23% | 2810.50n | +372.11% |
| Pow | 1.1^60 | 1.31µ | 3.17µ | +142.42% | 20.50µ | +1469.53% |
| Pow | 1.01^600 | 4.36µ | 13.86µ | +217.93% | 44.39µ | +918.44% |
| Pow | 1.001^6000 | 7.39µ | 24.69µ | +234.34% | 656.84µ | +8793.66% |
| Parse | 1 | 17.27n | 78.25n | +353.23% | 128.80n | +646.02% |
| Parse | 123.456 | 39.80n | 211.85n | +432.22% | 237.60n | +496.91% |
| Parse | 123456789.1234567890 | 106.20n | 233.10n | +119.59% | 510.90n | +381.30% |
| String | 1 | 5.45n | 19.91n | +265.49% | 197.85n | +3531.94% |
| String | 123.456 | 42.38n | 74.83n | +76.57% | 229.50n | +441.53% |
| String | 123456789.1234567890 | 77.90n | 210.40n | +170.11% | 328.90n | +322.24% |
| NewFromFloat64 | 1 | 155.50n | 361.00n | +132.23% | 234.40n | +50.76% |
| NewFromFloat64 | 123.456 | 237.10n | 588.80n | +148.37% | 770.30n | +224.95% |
| NewFromFloat64 | 123456789.1234567890 | 335.60n | 636.80n | +89.78% | 753.80n | +124.65% |
| Float64 | 1 | 28.92n | 51.64n | +78.59% | 456.70n | +1479.46% |
| Float64 | 123.456 | 97.36n | 102.68n | +5.46% | 680.05n | +598.49% |
| Float64 | 123456789.1234567890 | 206.10n | 304.60n | +47.79% | 792.60n | +284.55% |
| **Geom. Mean** | | 121.40n | 313.60n | +158.28% | 912.20n | +651.24% |

The benchmark results shown in the table are provided for informational purposes only and may vary depending on your specific use case.

Expand Down
40 changes: 21 additions & 19 deletions decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
//
// A decimal is a struct with three fields:
//
// - Sign: a boolean indicating whether the decimal is negative.
// - Scale: an integer indicating the position of the floating decimal point.
// - Sign: a boolean indicating whether the decimal is negative;
// - Scale: an integer indicating the position of the floating decimal point;
// - Coefficient: an integer value of the decimal without the decimal point.
//
// The scale field determines the position of the decimal point in the coefficient.
Expand Down Expand Up @@ -161,13 +161,13 @@ func MustNew(coef int64, scale int) Decimal {
// See also method [Decimal.Int64].
//
// NewFromInt64 returns an error:
// - if whole and frac have different signs.
// - if scale is negative or more than [MaxScale].
// - if frac / 10^scale is not within the range (-1, 1).
// - if the integer part of the result has more than ([MaxPrec] - scale) digits.
// - if whole and fractional parts have different signs;
// - if scale is negative or more than [MaxScale];
// - if frac / 10^scale is not within the range (-1, 1);
// - if the integer part of the result has more than [MaxPrec] digits.
func NewFromInt64(whole, frac int64, scale int) (Decimal, error) {
if whole > 0 && frac < 0 || whole < 0 && frac > 0 {
return Decimal{}, fmt.Errorf("inconsistent signs")
return Decimal{}, fmt.Errorf("converting integers: inconsistent signs")
}
// Integer
w, err := New(whole, 0)
Expand All @@ -180,7 +180,7 @@ func NewFromInt64(whole, frac int64, scale int) (Decimal, error) {
return Decimal{}, fmt.Errorf("converting integers: %w", err)
}
if !f.WithinOne() {
return Decimal{}, fmt.Errorf("inconsistent fraction")
return Decimal{}, fmt.Errorf("converting integers: inconsistent fraction")
}
// Decimal
d, err := w.Add(f)
Expand All @@ -193,11 +193,11 @@ func NewFromInt64(whole, frac int64, scale int) (Decimal, error) {
// NewFromFloat64 converts a float to a (possibly rounded) decimal.
//
// NewFromFloat64 returns an error:
// - if the integer part of the result has more than [MaxPrec] digits.
// - if the integer part of the result has more than [MaxPrec] digits;
// - if the float is a special value (NaN or Inf).
func NewFromFloat64(f float64) (Decimal, error) {
if math.IsNaN(f) || math.IsInf(f, 0) {
return Decimal{}, fmt.Errorf("special value")
return Decimal{}, fmt.Errorf("converting float: special value %v", f)
}
d, err := Parse(strconv.FormatFloat(f, 'f', -1, 64))
if err != nil {
Expand Down Expand Up @@ -245,9 +245,9 @@ func (d Decimal) ULP() Decimal {
// but tries to maintain trailing zeros in the fractional part to preserve scale.
//
// Parse returns an error:
// - if the integer part of the result has more than [MaxPrec] digits.
// - if the string does not represent a valid decimal number.
// - if the string is longer than 330 bytes.
// - if the integer part of the result has more than [MaxPrec] digits;
// - if the string does not represent a valid decimal number;
// - if the string is longer than 330 bytes;
// - if the exponent is less than -330 or greater than 330.
func Parse(s string) (Decimal, error) {
return ParseExact(s, 0)
Expand Down Expand Up @@ -1072,7 +1072,9 @@ func (d Decimal) mulSint(e Decimal, minScale int) (Decimal, error) {

// Pow returns the (possibly rounded) decimal raised to the given power.
//
// Pow returns an error if the integer part of the power has more than [MaxPrec] digits.
// Pow returns an error if:
// - the integer part of the result has more than [MaxPrec] digits;
// - the decimal is zero and the power is negative.
func (d Decimal) Pow(power int) (Decimal, error) {
return d.PowExact(power, 0)
}
Expand Down Expand Up @@ -1162,7 +1164,7 @@ func (d Decimal) powSint(power, minScale int) (Decimal, error) {
power = power - 1

// Coefficient (Multiplication)
ecoef.mul(dcoef, ecoef)
ecoef.mul(ecoef, dcoef)

// Sign
eneg = eneg != dneg
Expand All @@ -1171,8 +1173,8 @@ func (d Decimal) powSint(power, minScale int) (Decimal, error) {
escale = escale + dscale
if escale > 2*MaxScale {
shift := escale - 2*MaxScale
escale = 2 * MaxScale
ecoef.rshDown(ecoef, shift)
escale = escale - shift
}
}
if power > 0 {
Expand All @@ -1188,8 +1190,8 @@ func (d Decimal) powSint(power, minScale int) (Decimal, error) {
dscale = dscale * 2
if dscale > 2*MaxScale {
shift := dscale - 2*MaxScale
dscale = 2 * MaxScale
dcoef.rshDown(dcoef, shift)
dscale = dscale - shift
}
}
}
Expand Down Expand Up @@ -1435,7 +1437,7 @@ func (d Decimal) fmaSint(e, f Decimal, minScale int) (Decimal, error) {
//
// Quo returns an error if:
// - the integer part of the result has more than [MaxPrec] digits;
// - divisor e is zero.
// - the divisor is zero.
func (d Decimal) Quo(e Decimal) (Decimal, error) {
return d.QuoExact(e, 0)
}
Expand All @@ -1452,7 +1454,7 @@ func (d Decimal) QuoExact(e Decimal, scale int) (Decimal, error) {

// Special case: zero divisor
if e.IsZero() {
return Decimal{}, errDivisionByZero
return Decimal{}, fmt.Errorf("%v / %v: %w", d, e, errDivisionByZero)
}

// Special case: zero dividend
Expand Down
142 changes: 100 additions & 42 deletions decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1768,11 +1768,61 @@ func TestDecimal_Pow(t *testing.T) {
{"-1", 1, "-1"},
{"-1", 2, "1"},

{"1", -2, "1"},
{"1", -1, "1"},
{"1", 0, "1"},
{"1", 1, "1"},
{"1", 2, "1"},
// One Tenths
{"0.1", -18, "1000000000000000000"},
{"0.1", -10, "10000000000"},
{"0.1", -9, "1000000000"},
{"0.1", -8, "100000000"},
{"0.1", -7, "10000000"},
{"0.1", -6, "1000000"},
{"0.1", -5, "100000"},
{"0.1", -4, "10000"},
{"0.1", -3, "1000"},
{"0.1", -2, "100"},
{"0.1", -1, "10"},
{"0.1", 0, "1"},
{"0.1", 1, "0.1"},
{"0.1", 2, "0.01"},
{"0.1", 3, "0.001"},
{"0.1", 4, "0.0001"},
{"0.1", 5, "0.00001"},
{"0.1", 6, "0.000001"},
{"0.1", 7, "0.0000001"},
{"0.1", 8, "0.00000001"},
{"0.1", 9, "0.000000001"},
{"0.1", 10, "0.0000000001"},
{"0.1", 18, "0.000000000000000001"},
{"0.1", 19, "0.0000000000000000001"},
{"0.1", 20, "0.0000000000000000000"},
{"0.1", 40, "0.0000000000000000000"},

// Negative One Tenths
{"-0.1", -18, "1000000000000000000"},
{"-0.1", -10, "10000000000"},
{"-0.1", -9, "-1000000000"},
{"-0.1", -8, "100000000"},
{"-0.1", -7, "-10000000"},
{"-0.1", -6, "1000000"},
{"-0.1", -5, "-100000"},
{"-0.1", -4, "10000"},
{"-0.1", -3, "-1000"},
{"-0.1", -2, "100"},
{"-0.1", -1, "-10"},
{"-0.1", 0, "1"},
{"-0.1", 1, "-0.1"},
{"-0.1", 2, "0.01"},
{"-0.1", 3, "-0.001"},
{"-0.1", 4, "0.0001"},
{"-0.1", 5, "-0.00001"},
{"-0.1", 6, "0.000001"},
{"-0.1", 7, "-0.0000001"},
{"-0.1", 8, "0.00000001"},
{"-0.1", 9, "-0.000000001"},
{"-0.1", 10, "0.0000000001"},
{"-0.1", 18, "0.000000000000000001"},
{"-0.1", 19, "-0.0000000000000000001"},
{"-0.1", 20, "0.0000000000000000000"},
{"-0.1", 40, "0.0000000000000000000"},

// Twos
{"2", -63, "0.0000000000000000001"},
Expand Down Expand Up @@ -1801,6 +1851,33 @@ func TestDecimal_Pow(t *testing.T) {
{"2", 32, "4294967296"},
{"2", 63, "9223372036854775808"},

// Negative Twos
{"-2", -63, "-0.0000000000000000001"},
{"-2", -32, "0.0000000002328306437"},
{"-2", -16, "0.0000152587890625"},
{"-2", -9, "-0.001953125"},
{"-2", -8, "0.00390625"},
{"-2", -7, "-0.0078125"},
{"-2", -6, "0.015625"},
{"-2", -5, "-0.03125"},
{"-2", -4, "0.0625"},
{"-2", -3, "-0.125"},
{"-2", -2, "0.25"},
{"-2", -1, "-0.5"},
{"-2", 0, "1"},
{"-2", 1, "-2"},
{"-2", 2, "4"},
{"-2", 3, "-8"},
{"-2", 4, "16"},
{"-2", 5, "-32"},
{"-2", 6, "64"},
{"-2", 7, "-128"},
{"-2", 8, "256"},
{"-2", 9, "-512"},
{"-2", 16, "65536"},
{"-2", 32, "4294967296"},
{"-2", 63, "-9223372036854775808"},

// Squares
{"-3", 2, "9"},
{"-2", 2, "4"},
Expand Down Expand Up @@ -1841,36 +1918,12 @@ func TestDecimal_Pow(t *testing.T) {
{"13", 3, "2197"},
{"14", 3, "2744"},

// One Tenth
{"0.1", 0, "1"},
{"0.1", 1, "0.1"},
{"0.1", 2, "0.01"},
{"0.1", 3, "0.001"},
{"0.1", 4, "0.0001"},
{"0.1", 5, "0.00001"},
{"0.1", 6, "0.000001"},
{"0.1", 7, "0.0000001"},
{"0.1", 8, "0.00000001"},
{"0.1", 9, "0.000000001"},

// One Half
{"0.5", 0, "1"},
{"0.5", 1, "0.5"},
{"0.5", 2, "0.25"},
{"0.5", 3, "0.125"},
{"0.5", 4, "0.0625"},
{"0.5", 5, "0.03125"},
{"0.5", 6, "0.015625"},
{"0.5", 7, "0.0078125"},
{"0.5", 8, "0.00390625"},
{"0.5", 9, "0.001953125"},

// Interest accrual
{"1.1", 60, "304.4816395414180996"},
{"1.01", 600, "391.5833969993197743"},
{"1.001", 6000, "402.2211245663552923"},
{"1.0001", 60000, "403.3077910727185433"},
{"1.00001", 600000, "403.4166908911542153"},
{"1.1", 60, "304.4816395414180996"}, // no error
{"1.01", 600, "391.5833969993197743"}, // no error
{"1.001", 6000, "402.2211245663552923"}, // no error
{"1.0001", 60000, "403.3077910727185433"}, // no error
{"1.00001", 600000, "403.4166908911542153"}, // no error
}
for _, tt := range tests {
d := MustParse(tt.d)
Expand All @@ -1893,17 +1946,22 @@ func TestDecimal_Pow(t *testing.T) {
}{
"overflow 1": {"2", 64, 0},
"overflow 2": {"2", -64, 0},
"overflow 3": {"10", 19, 0},
"zero": {"0.1", -20, 0},
"overflow 3": {"0.5", -64, 0},
"overflow 4": {"10", 19, 0},
"overflow 5": {"10", -19, 0},
"overflow 6": {"0.1", -19, 0},
"zero 1": {"0", -1, 0},
"scale 1": {"1", 1, MaxScale},
"scale 2": {"1", 1, -1},
}
for _, tt := range tests {
d := MustParse(tt.d)
_, err := d.PowExact(tt.power, tt.scale)
if err == nil {
t.Errorf("%q.PowExact(%d, %d) did not fail", d, tt.power, tt.scale)
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
d := MustParse(tt.d)
_, err := d.PowExact(tt.power, tt.scale)
if err == nil {
t.Errorf("%q.PowExact(%d, %d) did not fail", d, tt.power, tt.scale)
}
})
}
})
}
Expand Down

0 comments on commit a4898ec

Please sign in to comment.