Overview of the apd package
mastermath/big types, providing features like standard mathematical functions (sqrt, ln, pow), configurable precision, and condition flags/traps to ensure computational accuracy.repository·master·Indexed 21 days ago
https://github.com/cockroachdb/apdAn arbitrary-precision decimal package for Go that implements the General Decimal Arithmetic specification. It provides a robust, error-aware alternative to math/big, featuring configurable precision, condition flags, and traps. The package is built around three primary types: BigInt for high-performance arbitrary-precision integers, Decimal for representing decimal values, and Context for performing arithmetic operations and managing rounding and precision.
math/big types, providing features like standard mathematical functions (sqrt, ln, pow), configurable precision, and condition flags/traps to ensure computational accuracy.The apd package is built around three primary types that serve different roles in decimal arithmetic:
BigInt: A wrapper around big.Int designed to reduce memory allocations. It uses an inline array for small values and provides fast-paths for basic arithmetic. It exposes an API identical to big.Int.Decimal: Represents a decimal value using a BigInt and an exponent. While it holds the value, it does not contain arithmetic operations itself.Context: The central component for performing arithmetic. A Context defines the precision, range, and restrictions for operations. All arithmetic operations are defined on the Context type and return both an error and a Condition flag.In apd, arithmetic is not performed by the Decimal type directly. Instead, you use a Context to perform operations on Decimal values.
When an operation is performed via a Context, it returns two values:
Condition: A bitfield of flags indicating the state of the result (e.g., whether the result was rounded, inexact, or overflowed).You can configure a Context with Traps to automatically trigger an error if a specific Condition occurs, allowing you to guarantee exactness in your computations.
The constWithPrecision type is an internal optimization used for constants like $\ln(10)$. It maintains an unrounded Decimal value and a lookup table (vals) of the constant rounded down to various power-of-two precisions.
When a user requests a constant with a specific precision via the get(precision uint32) method, the system performs a logarithmic lookup to return the pre-calculated value with the smallest available precision that is at least as high as the requested precision. This avoids expensive re-calculation of high-precision digits when lower precision is sufficient.
The BigInt type provides arbitrary-precision integer support. It is a high-performance wrapper around Go's math/big.Int designed to minimize memory allocations by using an inline array for small values (up to 128 bits).
Best Practice: To avoid heap allocation, do not use NewBigInt. Instead, declare a zero-valued BigInt directly on the stack. The zero value is ready to use immediately.
// Recommended: stack allocation
var z apd.BigInt
z.SetInt64(12345)
// Avoid this if performance/allocation is a concern:
// z := apd.NewBigInt(12345)The ErrDecimal type is designed for performing a sequence of decimal operations while accumulating errors and flags. Instead of checking for errors after every single operation, you can chain multiple arithmetic calls and perform a single error check at the end of the sequence. If any operation in the chain results in an error, subsequent operations are skipped, and the error state is preserved.
Key behaviors:
Add, Mul, Sub, etc.) take a destination decimal d and return it, allowing for fluent API usage.e.Err() is non-nil, the operation is bypassed.e.Err() at the end of your calculation chain to determine if any step failed.// Example of chained operations with ErrDecimal
errDec := apd.MakeErrDecimal(ctx)
// Perform multiple operations
errDec.Add(d, x, y)
errDec.Mul(d, d, z)
errDec.Sub(d, d, w)
// Check for errors once at the end
if err := errDec.Err(); err != nil {
// handle error
}The Context type manages the precision, exponent limits, rounding behavior, and error handling (traps) for all decimal arithmetic operations. It is safe for concurrent use, but should not be modified concurrently. You can use BaseContext as a starting point for most operations.
Key configuration fields:
Precision: The total number of digits (before and after the decimal point) used for rounding.MaxExponent / MinExponent: Limits for the effective exponent of the decimal values.Traps: A Condition bitmask that determines which arithmetic error conditions (like Overflow or DivisionByZero) will trigger a Go error return.Rounding: The Rounder strategy used during operations.// Using the default BaseContext
ctx := apd.BaseContext
// Creating a custom context with specific precision
customCtx := ctx.WithPrecision(10)To convert a Decimal back to standard Go types:
Int64() to get the int64 representation. This returns an error if the decimal is not finite, has a fractional part, or is out of the int64 range.Float64() to get the float64 representation. Note that this conversion may lose precision.d, _ := apd.NewFromString("123")
val, err := d.Int64()
if err != nil {
// handle error (e.g. fractional part or overflow)
}
f, err := d.Float64()The Context provides several ways to handle integer conversion and rounding:
Ceil(d, x): Smallest integer $\ge x$.Floor(d, x): Largest integer $\le x$.RoundToIntegralValue(d, x): Sets $d$ to the integral value of $x$, ignoring Inexact and Rounded flags.RoundToIntegralExact(d, x): Sets $d$ to the integral value of $x$ exactly.Rem(d, x, y): Sets $d$ to the remainder of $x/y$.The Text(format byte) method converts a Decimal to a string based on the provided format character.
Supported formats:
'e': decimal exponent, e.g., d.dddd e±dd'E': decimal exponent, e.g., d.dddd E±dd'f': no exponent, e.g., ddddd.dddd (may append trailing zeros to match precision)'g': uses 'e' for large exponents, otherwise uses 'f''G': uses 'E' for large exponents, otherwise uses 'f'If an unrecognized format character is provided, it returns a string starting with % followed by the character (e.g., %x).
// Example usage of Text
str := myDecimal.Text('f')The Decimal type implements the fmt.Formatter interface via the Format(s fmt.State, format rune) method. This allows Decimal values to be used directly with standard library functions like fmt.Printf, fmt.Sprintf, etc.
Supported format runes:
'e', 'E', 'f', 'g', 'G': Standard floating-point formats.'F': Handled like 'f'.'v', 's': Handled like 'G'.Supported fmt.State flags:
'+': Force sign for positive numbers.' ': Space for positive numbers.'0': Zero padding on the left (for Finite forms).'-': Left justification (padding on the right).width: Supports output field width for padding.import "fmt"
// Using Decimal with fmt.Printf
fmt.Printf("Value: %10.2f\n", myDecimal) // width 10, format 'f'
fmt.Printf("Sign: %+f\n", myDecimal) // force sign
fmt.Printf("Zero: %010f\n", myDecimal) // zero paddingThe Compose method on a *Decimal sets the internal decimal value using provided parts. This is used to reconstruct a decimal from its raw components.
Parameters:
form (byte): The state of the decimal (0 for Finite, 1 for Infinite, 2 for NaN).negative (bool): Whether the value is negative.coefficient ([]byte): A base-2 big-endian integer. For finite numbers, this is the significand. For non-finite numbers, this is ignored.exponent (int32): The exponent. For non-finite numbers, this is ignored.Constraints:
coefficient slice should not be modified after being passed to Compose.err := d.Compose(0, false, []byte{0x01}, 2)
if err != nil {
// Handle error (e.g., unknown form)
}
// Resulting decimal is 1 * 10^2 = 100