apd

repository·master·Indexed 21 days ago

https://github.com/cockroachdb/apd

An 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.

Tokens
9.1K
Snippets
42
Records
48
Agent score
73%

What's inside apd

  1. Overview of the apd package

    master
    apd is an arbitrary-precision decimal package for Go that implements the General Decimal Arithmetic specification. It is designed to be a more robust and error-aware alternative to Go's math/big types, providing features like standard mathematical functions (sqrt, ln, pow), configurable precision, and condition flags/traps to ensure computational accuracy.
  2. Understand the core types in apd

    master

    The apd package is built around three primary types that serve different roles in decimal arithmetic:

    1. 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.
    2. Decimal: Represents a decimal value using a BigInt and an exponent. While it holds the value, it does not contain arithmetic operations itself.
    3. 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.
  3. How Context, Decimals, and Conditions work together

    master

    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:

    1. An error: Indicates if the operation failed (e.g., due to precision or range issues).
    2. A 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.

  4. How constWithPrecision works for high-precision constants

    master

    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.

  5. Use BigInt for arbitrary-precision integers

    master

    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)
  6. Use ErrDecimal for chained arithmetic operations

    master

    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:

    • Chaining: Most methods (like Add, Mul, Sub, etc.) take a destination decimal d and return it, allowing for fluent API usage.
    • Error Propagation: If e.Err() is non-nil, the operation is bypassed.
    • Single Check: Use 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
    }
  7. Configure decimal operations with Context

    master

    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)
  8. Convert Decimal to int64 or float64

    master

    To convert a Decimal back to standard Go types:

    • Use 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.
    • Use 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()
  9. Use rounding and integer functions

    master

    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$.
  10. Convert a Decimal to a string using Text()

    master

    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')
  11. Implement fmt.Formatter with Decimal

    master

    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 padding
  12. Compose a Decimal from constituent parts

    master

    The 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:

    • The coefficient slice should not be modified after being passed to Compose.
    • Successive calls with the same arguments must result in the same decimal value.
    • If the value cannot be represented (e.g., an unknown form is provided), an error is returned.
    err := d.Compose(0, false, []byte{0x01}, 2)
    if err != nil {
        // Handle error (e.g., unknown form)
    }
    // Resulting decimal is 1 * 10^2 = 100