bigdecimal-rs

repository·trunk·Indexed 19 days ago

https://github.com/akubera/bigdecimal-rs

A Rust library for arbitrary-precision decimal arithmetic designed to avoid floating-point precision errors. Built on top of num-bigint, it provides a Context struct for managing precision and rounding modes, support for scientific and engineering notation, and a wide range of mathematical functions including square roots, cube roots, and exponentials. It includes BigDecimalRef for efficient immutable references and supports construction from strings, integers, and floating-point types.

Tokens
5.8K
Snippets
21
Records
26
Agent score
65%

What's inside bigdecimal

  1. What is BigDecimal and how does it work?

    trunk

    A BigDecimal allows storing any real number to arbitrary precision, which avoids common floating point errors (like 0.1 + 0.2 ≠ 0.3).

    Internally, it uses a BigInt object paired with a 64-bit integer (scale) that determines the position of the decimal point. While the precision is described as arbitrary, it is practically limited to $2^{63}$ decimal places.

    Important Note: It is not recommended to convert a floating-point number to a BigDecimal directly, as the floating-point representation may be unexpected. Instead, use string parsing.

    use bigdecimal::BigDecimal;
    use std::str::FromStr;
    
    let input = "0.8";
    let dec = BigDecimal::from_str(&input).unwrap();
    let float = f32::from_str(&input).unwrap();
    
    println!("Input ({}) with 10 decimals: {} vs {})", input, dec, float);
  2. Use BigDecimalRef for efficient immutable references

    trunk

    When writing library functions that require an immutable reference to a BigDecimal, prefer using BigDecimalRef<'a> or types that implement Into<BigDecimalRef<'a>>. This is more efficient than using &BigDecimal because it avoids cloning the underlying large digit buffer when performing operations like negation.

    To create a BigDecimalRef from a BigDecimal, use the .to_ref() method. To convert it back to an owned BigDecimal, use .to_owned() or .to_owned_with_scale(scale).

    # use bigdecimal::*;
    # use std::ops::Neg;
    fn add_one<'a, N: Into<BigDecimalRef<'a>>>(n: N) -> BigDecimal {
        n.into() + 1
    }
    
    let n: BigDecimal = "123.456".parse().unwrap();
    
    // Call via standard reference (implements Into)
    let m = add_one(&n);
    assert_eq!(m, "124.456".parse::<BigDecimal>().unwrap());
    
    // Call by negating the reference (fast: no-digit cloning involved)
    let m = add_one(n.to_ref().neg());
    assert_eq!(m, "-122.456".parse::<BigDecimal>().unwrap());
  3. Configure arithmetical Context

    trunk

    The Context struct stores rules for numerical operations, specifically how many digits to keep (precision) and how to handle rounding (rounding mode).

    While Context::default() uses compile-time constants determined by environment variables, it is highly recommended to explicitly create and configure a Context for your specific needs to ensure predictable behavior.

    Default Compile-time Constants

    If you rely on Context::default(), the values are determined by these environment variables at compile time:

    VariableDescriptionDefault
    RUST_BIGDECIMAL_DEFAULT_PRECISIONdigit precision100
    RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODErounding-modeHalfEven

    Creating and Modifying Context

    You can create a new context using Context::new(precision, rounding) or derive new contexts from an existing one using the with_ pattern, which returns a new copy with the updated value.

    ```rust
    use bigdecimal::{Context, RoundingMode};
    use stdlib::num::NonZeroU64;
    
    // Create a new context
    let ctx = Context::new(NonZeroU64::new(50).unwrap(), RoundingMode::HalfUp);
    
    // Or derive a new context from an existing one
    let new_ctx = ctx.with_precision(NonZeroU64::new(100).unwrap())
                     .with_rounding_mode(RoundingMode::Down);
  4. Perform arithmetic operations with Context

    trunk

    To ensure arithmetic results (like multiplication or inversion) adhere to specific precision and rounding rules, use the methods provided by the Context struct rather than performing operations directly on BigDecimal objects. This ensures that intermediate or final results are correctly rounded according to your defined rules.

    use bigdecimal::{BigDecimal, Context};
    
    let x: BigDecimal = "1.5".parse().unwrap();
    let y: BigDecimal = "3.1415926".parse().unwrap();
    
    // Define a context with 5 digits of precision
    let ctx = Context::default().with_prec(5).unwrap();
    
    // Multiply using the context
    let product = ctx.multiply(&x, &y);
    // product is rounded to 5 digits: "4.7124"
    
    // Invert (1/n) using the context
    let one_over_three = ctx.invert(&"3".parse::<BigDecimal>().unwrap());
    // one_over_three is rounded to 5 digits: "0.33333"
  5. Configure the default rounding mode at compile-time

    trunk
    The default RoundingMode used by the library can be customized during compilation. By default, the library uses HalfEven. To change this, set the RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE environment variable to the name of your desired mode (e.g., Up, Down, Ceiling, Floor, HalfUp, HalfDown, or HalfEven) before building the project.
  6. Perform low-level rounding with round_pair

    trunk

    The round_pair method allows you to perform rounding on a specific pair of decimal digits. This is useful for manual digit manipulation.

    Parameters:

    • sign: The Sign of the number (e.g., Sign::Plus or Sign::Minus).
    • pair: A tuple (u8, u8) representing the two digits in question. For example, to round 0.345 to two places, you would pass (4, 5). Both digits must be less than 10.
    • trailing_zeros: A bool indicating if all digits after the pair are zero. This affects how modes like HalfUp or HalfEven treat the digit 5.

    Returns: Returns the first number of the pair, rounded. Note that the sign is not preserved in the returned u8.

    // Example: To round 0.345 to two places using HalfUp
    // sign: Sign::Plus, pair: (4, 5), trailing_zeros: false
    let rounded_digit = RoundingMode::HalfUp.round_pair(Sign::Plus, (4, 5), false);
  7. Compute square, cube, and exponent of a BigDecimal

    trunk

    The BigDecimal type provides methods for common power operations:

    • square(): Returns the square of the number.
    • cube(): Returns the cube of the number.
    • exp(): Returns $e^x$ (the exponential function).
    # use bigdecimal::*;
    let a = BigDecimal::from_str("1.5").unwrap();
    let sq = a.square(); // 2.25
    let cb = a.cube();   // 3.375
    
    let e = BigDecimal::from_str("1").unwrap().exp(); // ~2.718...
  8. Round a BigDecimal

    trunk

    The round(digits: i64) method on BigDecimal rounds the number to the specified number of decimal places. The digits parameter represents the number of digits to keep after the decimal point (positive) or the power of 10 to round to (negative).

    # use bigdecimal::*;
    let a = BigDecimal::from_str("1.4499999999").unwrap();
    
    let r1 = a.round(1); // "1.4"
    let r2 = a.round(2); // "1.45"
    let r3 = a.round(-1); // "0"
  9. Convert BigDecimal to plain string

    trunk

    Use to_plain_string() to get a string representation of the BigDecimal without scientific notation (e.g., avoiding e+ or e- formats).

    # use bigdecimal::*;
    let n: BigDecimal = "1e-18".parse().unwrap();
    let s = n.to_plain_string();
    assert_eq!(s, "0.000000000000000001");
  10. Format BigDecimal as strings

    trunk

    Convert BigDecimal to various string representations:

    • to_plain_string(): Standard decimal notation (e.g., 0.0000000001). Never uses scientific notation.
    • to_scientific_notation(): Scientific notation (e.g., 1.2345678e7).
    • to_engineering_notation(): Engineering notation (exponent is a multiple of three, e.g., 12.345678e6).
    use bigdecimal::BigDecimal;
    
    let n = BigDecimal::from(12345678);
    println!("{}", n.to_plain_string());          // "12345678"
    println!("{}", n.to_scientific_notation()); // "1.2345678e7"
    println!("{}", n.to_engineering_notation()); // "12.345678e6"
  11. Round a u32 value at a specific digit with round_u32

    trunk

    The round_u32 method rounds a u32 value at a specific 0-based digit index.

    Parameters:

    • at_digit: A NonZeroU8 representing the 0-based index of the digit at which to round (0 is the first digit).
    • sign: The Sign of the number.
    • value: The u32 number containing the digits.
    • trailing_zeros: A bool indicating if all digits after the target digit are zero.

    Returns: Returns the u32 value with the digit at at_digit rounded according to the RoundingMode.

    use stdlib::num::NonZeroU8;
    
    // Example: To round 823418 at digit-index 3 (the '3')
    // Result depends on the RoundingMode instance used
    let rounded_val = mode.round_u32(NonZeroU8::new(4).unwrap(), Sign::Plus, 823418, true);
  12. Parse BigDecimal from strings and scientific notation

    trunk

    The BigDecimal::from_str method allows creating a BigDecimal from a string representation. It supports standard decimal notation, scientific notation (e.g., 1.23e+1, 1.23E-8), and underscores as digit separators (e.g., 31_862_140.830_686_979). It also handles very large or very small exponents (e.g., 1E10000).

    let val = BigDecimal::from_str("1.23E+3").unwrap();
    let val_with_underscores = BigDecimal::from_str("31_862_140.830_686_979").unwrap();