162 lines
4.0 KiB
Go
162 lines
4.0 KiB
Go
package decimal
|
|
|
|
import (
|
|
"math/big"
|
|
"strings"
|
|
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
)
|
|
|
|
// RatFromString parses a decimal string into a big.Rat
|
|
// Supports standard decimal notation like "123.45", "-0.001", etc.
|
|
func RatFromString(value string) (*big.Rat, error) {
|
|
if strings.TrimSpace(value) == "" {
|
|
return nil, merrors.InvalidArgument("decimal: empty value")
|
|
}
|
|
r := new(big.Rat)
|
|
if _, ok := r.SetString(value); !ok {
|
|
return nil, merrors.InvalidArgument("decimal: invalid decimal value: " + value)
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// MulRat multiplies two rational numbers
|
|
func MulRat(a, b *big.Rat) *big.Rat {
|
|
return new(big.Rat).Mul(a, b)
|
|
}
|
|
|
|
// DivRat divides two rational numbers
|
|
func DivRat(a, b *big.Rat) (*big.Rat, error) {
|
|
if b.Sign() == 0 {
|
|
return nil, merrors.InvalidArgument("decimal: division by zero")
|
|
}
|
|
return new(big.Rat).Quo(a, b), nil
|
|
}
|
|
|
|
// AddRat adds two rational numbers
|
|
func AddRat(a, b *big.Rat) *big.Rat {
|
|
return new(big.Rat).Add(a, b)
|
|
}
|
|
|
|
// SubRat subtracts two rational numbers (a - b)
|
|
func SubRat(a, b *big.Rat) *big.Rat {
|
|
return new(big.Rat).Sub(a, b)
|
|
}
|
|
|
|
// NegRat negates a rational number
|
|
func NegRat(a *big.Rat) *big.Rat {
|
|
return new(big.Rat).Neg(a)
|
|
}
|
|
|
|
// RoundRatToScale rounds a rational number to a specific number of decimal places
|
|
// using the specified rounding mode
|
|
func RoundRatToScale(value *big.Rat, scale uint32, mode RoundingMode) (*big.Rat, error) {
|
|
scaleFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil)
|
|
numerator := new(big.Int).Mul(new(big.Int).Set(value.Num()), scaleFactor)
|
|
denominator := value.Denom()
|
|
|
|
quotient := new(big.Int)
|
|
remainder := new(big.Int)
|
|
quotient.QuoRem(numerator, denominator, remainder)
|
|
|
|
// No remainder, already exact
|
|
if remainder.Sign() == 0 {
|
|
return new(big.Rat).SetFrac(quotient, scaleFactor), nil
|
|
}
|
|
|
|
sign := quotient.Sign()
|
|
absQuotient := new(big.Int).Abs(new(big.Int).Set(quotient))
|
|
absRemainder := new(big.Int).Abs(remainder)
|
|
absDenominator := new(big.Int).Abs(denominator)
|
|
|
|
doubledRemainder := new(big.Int).Mul(absRemainder, big.NewInt(2))
|
|
cmp := doubledRemainder.Cmp(absDenominator)
|
|
|
|
shouldIncrement := false
|
|
|
|
switch mode {
|
|
case RoundingModeDown:
|
|
shouldIncrement = false
|
|
case RoundingModeHalfUp:
|
|
if cmp >= 0 {
|
|
shouldIncrement = true
|
|
}
|
|
case RoundingModeHalfEven, RoundingModeUnspecified:
|
|
if cmp > 0 {
|
|
shouldIncrement = true
|
|
} else if cmp == 0 {
|
|
// Tie: round to even
|
|
if absQuotient.Bit(0) == 1 {
|
|
shouldIncrement = true
|
|
}
|
|
}
|
|
default:
|
|
// Default to HALF_EVEN
|
|
if cmp > 0 {
|
|
shouldIncrement = true
|
|
} else if cmp == 0 {
|
|
if absQuotient.Bit(0) == 1 {
|
|
shouldIncrement = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if shouldIncrement {
|
|
if sign < 0 {
|
|
absQuotient.Add(absQuotient, big.NewInt(1))
|
|
quotient = absQuotient.Neg(absQuotient)
|
|
} else {
|
|
absQuotient.Add(absQuotient, big.NewInt(1))
|
|
quotient = absQuotient
|
|
}
|
|
}
|
|
|
|
return new(big.Rat).SetFrac(quotient, scaleFactor), nil
|
|
}
|
|
|
|
// FormatRat formats a rational number as a decimal string with the specified scale
|
|
func FormatRat(r *big.Rat, scale uint32) string {
|
|
sign := ""
|
|
if r.Sign() < 0 {
|
|
sign = "-"
|
|
}
|
|
|
|
absRat := new(big.Rat).Abs(r)
|
|
scaleFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil)
|
|
numerator := new(big.Int).Mul(absRat.Num(), scaleFactor)
|
|
numerator.Quo(numerator, absRat.Denom())
|
|
|
|
intStr := numerator.String()
|
|
if scale == 0 {
|
|
return sign + intStr
|
|
}
|
|
|
|
if len(intStr) <= int(scale) {
|
|
intStr = strings.Repeat("0", int(scale)-len(intStr)+1) + intStr
|
|
}
|
|
|
|
pointPos := len(intStr) - int(scale)
|
|
return sign + intStr[:pointPos] + "." + intStr[pointPos:]
|
|
}
|
|
|
|
// CmpRat compares two rational numbers
|
|
// Returns -1 if a < b, 0 if a == b, 1 if a > b
|
|
func CmpRat(a, b *big.Rat) int {
|
|
return a.Cmp(b)
|
|
}
|
|
|
|
// IsZero checks if a rational number is zero
|
|
func IsZero(r *big.Rat) bool {
|
|
return r.Sign() == 0
|
|
}
|
|
|
|
// IsPositive checks if a rational number is positive
|
|
func IsPositive(r *big.Rat) bool {
|
|
return r.Sign() > 0
|
|
}
|
|
|
|
// IsNegative checks if a rational number is negative
|
|
func IsNegative(r *big.Rat) bool {
|
|
return r.Sign() < 0
|
|
}
|