Documentation
¶
Overview ¶
Package decimal provides arbitrary-precision fixed-point decimal arithmetic.
A value is stored as the pair (i, scale) representing the rational number i / 10^scale. All operations route through a Context that pins the output Scale and rounding Mode, so results stay free of the cumulative drift that plagues binary floating point.
Index ¶
- Variables
- func Cmp(a, b Decimal) int
- type Context
- type Decimal
- func Add(ctx Context, a, b Decimal) Decimal
- func Div(ctx Context, a, b Decimal) (Decimal, error)
- func Exp(ctx Context, x Decimal) Decimal
- func Log(ctx Context, x Decimal) (Decimal, error)
- func Mul(ctx Context, a, b Decimal) Decimal
- func MustParse(ctx Context, s string) Decimal
- func Neg(d Decimal) Decimal
- func NewFromInt64(ctx Context, n int64) Decimal
- func NewFromScaledInt(i *big.Int, scale int32) Decimal
- func Parse(ctx Context, s string) (Decimal, error)
- func ParseExact(s string) (Decimal, error)
- func Pow(ctx Context, base, exp Decimal) (Decimal, error)
- func Sqrt(ctx Context, x Decimal) (Decimal, error)
- func Sub(ctx Context, a, b Decimal) Decimal
- type RoundingMode
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidDecimal is returned by ParseExact and Parse when the input // is not a well-formed decimal literal (empty, illegal characters, or // more than one decimal point). ErrInvalidDecimal = errors.New("invalid decimal") // ErrDivisionByZero is returned by Div, and by Pow when raising 0 to a // negative integer power (which would require dividing by zero). ErrDivisionByZero = errors.New("division by zero") // ErrNegativeSqrt is returned by Sqrt when the input is negative. ErrNegativeSqrt = errors.New("square root of negative number") // ErrNonPositiveLog is returned by Log when the input is zero or // negative — neither is in the domain of the real-valued logarithm. ErrNonPositiveLog = errors.New("logarithm of non-positive number") // ErrInvalidPow is returned by Pow when the base is negative and the // exponent is not an integer (the result would be complex-valued). ErrInvalidPow = errors.New("invalid power: negative base with non-integer exponent") // ErrRoundingNecessary is the panic value raised when an operation is // configured with RoundingModeUnnecessary but a non-zero residue would // have to be discarded. Recover with: // // defer func() { // if r := recover(); r != nil { // if err, ok := r.(error); ok && errors.Is(err, decimal.ErrRoundingNecessary) { // // handle // } // } // }() ErrRoundingNecessary = errors.New("rounding necessary but mode is Unnecessary") )
Functions ¶
Types ¶
type Context ¶
type Context struct {
// Scale is the number of fractional digits in normalized results.
Scale int32
// Mode selects the rounding strategy; see RoundingMode. The zero value
// is RoundingModeHalfUp.
Mode RoundingMode
}
Context bundles the target precision and rounding policy used by every Context-aware operation in this package.
Scale is the number of fractional digits in the output (so 10^-Scale is the smallest representable unit). Mode chooses how excess digits are dropped when an operation would otherwise produce more.
The zero value Context{} is integer-precision (Scale = 0) HalfUp rounding, i.e. ordinary "round half away from zero" to whole numbers.
Context is plain data: copy it freely, share it across goroutines, and reuse it in concurrent calls.
type Decimal ¶
type Decimal struct {
// contains filtered or unexported fields
}
Decimal is a fixed-point decimal number whose value is i / 10^scale.
scale is the number of fractional digits. With scale = 2, an i of 1234 represents 12.34. Both fields are unexported; build a Decimal through NewFromScaledInt, NewFromInt64, Parse, or its variants.
Decimal is a value type. Copying it is cheap and produces a fully independent value — there is no shared mutable state to worry about.
func Add ¶
Add returns a + b normalized to ctx.
The two operands are aligned to the larger of their scales before being summed, so addition itself is exact; the only place rounding can sneak in is the final clamp to ctx.Scale (using ctx.Mode) when the aligned scale exceeds it.
func Div ¶
Div returns a / b at ctx.Scale digits of precision, rounded with ctx.Mode.
Division by zero yields ErrDivisionByZero. Because division is an inherently approximate finite-precision operation, the result has a last-digit error of at most 0.5 ULP under HalfUp and at most 1 ULP under Up or Down.
func Exp ¶ added in v0.1.1
Exp returns e^x at ctx.Scale digits, rounded with ctx.Mode.
Defined on the entire real line. Exp(0) short-circuits to 1, and negative inputs are handled as 1 / Exp(|x|).
Algorithm:
- Take |x| and apply argument reduction by repeatedly halving until |x| < 0.5 (k iterations). Halving is done by "multiply by 5, scale + 1", which is exact.
- Apply the Taylor series 1 + x + x²/2! + … to the reduced argument, stopping once the next term rounds to zero at the working scale.
- Square the accumulator k times to recover e^x for the original argument.
- If x was negative, take the reciprocal of the final result.
Beware that for very large |x| (say 1000) the result is itself a huge number; both runtime and output size grow with |x|, so the caller should keep that in mind.
func Log ¶ added in v0.1.1
Log returns ln(x) (the natural logarithm) at ctx.Scale digits, rounded with ctx.Mode.
Inputs ≤ 0 yield ErrNonPositiveLog. Log(1) is 0.
Algorithm:
- Repeatedly take sqrt(x) until x falls into [0.9, 1.1] (count iterations). This shrinks the convergence radius of the series in step 2 to something tiny.
- With y close to 1, compute z = (y - 1) / (y + 1) and apply the atanh series atanh(z) = z + z³/3 + z⁵/5 + ….
- ln(y) = 2·atanh(z); then ln(x) = 2^count · ln(y).
Even for |log10(x)| ≈ 100 the sqrt loop only needs ~20 passes, so the total cost scales roughly linearly with ctx.Scale.
func Mul ¶
Mul returns a * b normalized to ctx.
The intermediate result is held at a.scale + b.scale and is exact; rounding (per ctx.Mode) only happens when collapsing the product down to ctx.Scale.
func MustParse ¶
MustParse is like Parse but panics on parse failure. Convenient for initializing package-level constants from string literals.
func NewFromInt64 ¶
NewFromInt64 wraps an int64 as a Decimal and normalizes it to ctx.Scale.
For example, with ctx.Scale = 2, NewFromInt64(ctx, 5) returns 5.00 (i = 500, scale = 2).
func NewFromScaledInt ¶
NewFromScaledInt builds a Decimal from a pre-scaled integer i and the given scale, so the resulting value is i / 10^scale.
A nil i is treated as 0. The returned Decimal does not share storage with the supplied *big.Int, so the caller may keep mutating its own copy without affecting the result.
This constructor performs no rounding or normalization; if you need the value to obey a Context's Scale, call Context.Normalize on it.
func Parse ¶
Parse is ParseExact followed by normalization to ctx.Scale using ctx.Mode.
Inputs with more fractional digits than ctx.Scale are rounded according to the rounding mode.
func ParseExact ¶
ParseExact parses a decimal string and preserves the exact scale found in the input — no rounding is performed.
The input accepts an optional leading sign and digits with or without a decimal point. An empty string, illegal characters, or more than one decimal point all yield ErrInvalidDecimal. Examples:
"123.45" → 12345 / 100 "-0.10" → -10 / 100 (trailing zero is preserved) ".5" → 5 / 10 "1." → 1 / 1
Scientific notation (e.g. "1e5") is not supported. Use Parse instead when you want the result clamped to a Context's Scale.
func Pow ¶ added in v0.1.1
Pow returns base^exp at ctx.Scale digits, rounded with ctx.Mode.
Behavior splits on whether exp is an integer:
Integer exp (including negative and zero): handled by binary exponentiation (square-and-multiply) using only multiplication and division, so there is no Log/Exp approximation in the loop. base may be negative, e.g. Pow(-2, 3) = -8 and Pow(-2, 4) = 16. Pow(0, exp) for exp < 0 returns ErrDivisionByZero; Pow(0, 0) returns 1 by convention.
Non-integer exp: computed as exp · ln(base) followed by Exp, so base must be > 0. A non-positive base with a fractional exponent returns ErrInvalidPow (the result would be complex-valued).
"Integer" here means the scaled integer leaves no remainder modulo 10^scale: 2.00 is integer 2, but 2.001 is not.
func Sqrt ¶ added in v0.1.1
Sqrt returns √x at ctx.Scale digits, rounded with ctx.Mode.
A negative x yields ErrNegativeSqrt; Sqrt(0) is 0.
Internally x is scaled up to an integer N = x · 10^(2·ws) where ws = ctx.Scale + 12 guard digits, and big.Int.Sqrt produces the floor of √N. The result is then normalized back to ctx.Scale, so the final-digit error stays under 0.5 ULP in HalfUp mode.
func (Decimal) Scale ¶
Scale returns the number of fractional digits stored on the Decimal.
12.345 (i = 12345, scale = 3) returns 3. The scale is not collapsed when trailing digits are zero, so 12.30 (i = 1230, scale = 2) still returns 2.
func (Decimal) String ¶
String renders the value in conventional decimal notation.
Trailing zeros implied by scale are preserved exactly: Decimal{i:1230, scale:2} prints as "12.30". When scale is 0 the output has no decimal point. Negative values are prefixed with "-" before the absolute magnitude. The output never contains thousands separators or scientific notation.
type RoundingMode ¶
type RoundingMode uint8
RoundingMode selects the rounding strategy used by Context.Normalize and every operation that produces results at a target scale (Add, Sub, Mul, Div, Sqrt, Exp, Log, Pow).
const ( // RoundingModeHalfUp is the everyday "round half away from zero" rule. // Exact halves move away from zero: 1.235 → 1.24, -1.235 → -1.24. // Matches Java BigDecimal.ROUND_HALF_UP and Python decimal.ROUND_HALF_UP. // This is the zero value of RoundingMode. RoundingModeHalfUp RoundingMode = iota // RoundingModeHalfEven is banker's rounding: the IEEE 754 default and // Python decimal's default. Behaves like HalfUp except that exact halves // pick the neighbour whose last digit is even, eliminating the systematic // upward bias of HalfUp on long sums. 1.225 → 1.22, 1.235 → 1.24, // -1.225 → -1.22. RoundingModeHalfEven // RoundingModeDown truncates toward zero (the absolute value never grows). // 1.235 and -1.235 both become 1.23 / -1.23 at Scale = 2. RoundingModeDown // RoundingModeUp rounds away from zero whenever any non-zero residue // remains. 1.231 and -1.231 both become 1.24 / -1.24 at Scale = 2. RoundingModeUp // RoundingModeCeiling rounds toward +∞ whenever any non-zero residue // remains. Positive values round away from zero; negative values round // toward zero. 1.231 → 1.24, -1.231 → -1.23 at Scale = 2. RoundingModeCeiling // RoundingModeFloor rounds toward -∞ whenever any non-zero residue // remains. Positive values round toward zero; negative values round // away from zero. 1.231 → 1.23, -1.231 → -1.24 at Scale = 2. RoundingModeFloor // RoundingModeHalfDown is "round half toward zero": behaves like HalfUp // except that exact halves stay put rather than stepping away. 1.235 → // 1.23, 1.236 → 1.24, -1.235 → -1.23. Matches Java BigDecimal.ROUND_HALF_DOWN // and Python decimal.ROUND_HALF_DOWN. RoundingModeHalfDown // RoundingMode05Up implements Python's decimal.ROUND_05UP rule: after // truncating toward zero, if the kept last digit is 0 or 5 then any // non-zero residue causes a step away from zero; otherwise the residue // is dropped. Used in some accounting contexts to avoid producing 5s // as final digits unless they are exact. RoundingMode05Up // RoundingModeUnnecessary asserts that no rounding will be required. // If an operation under this mode has to discard a non-zero residue, // it panics with ErrRoundingNecessary. Equivalent to Java's // RoundingMode.UNNECESSARY (which throws ArithmeticException). RoundingModeUnnecessary )
Constants are ordered by expected usage frequency, so the zero value of Context.Mode is RoundingModeHalfUp — the rule most users mean when they say "round to N decimal places".