DeciMojo - An arbitrary-precision decimal and integer mathematics library for Mojo

DeciMojo

An arbitrary-precision decimal and integer mathematics library for Mojo.

Repository on GitHub»

Overview

DeciMojo provides an arbitrary-precision decimal and integer mathematics library for Mojo, delivering exact calculations for financial modeling, scientific computing, and applications where floating-point approximation errors are unacceptable. Beyond basic arithmetic, the library includes advanced mathematical functions with guaranteed precision.

The core types are:

  • A base-10 arbitrary-precision signed integer type (BigInt) and a base-10 arbitrary-precision unsigned integer type (BigUInt) supporting unlimited digits. It features comprehensive arithmetic operations, comparison functions, and supports extremely large integer calculations efficiently.
  • An arbitrary-precision decimal implementation (BigDecimal) allowing for calculations with unlimited digits and decimal places. It provides a complete set of arithmetic operations, comparisons, and mathematical functions like logarithms, exponentiation, roots, trigonometric functions, etc. It also supports rounding modes and conversions to/from built-in types.
  • A 128-bit fixed-point decimal implementation (Decimal128) supporting up to 29 significant digits with a maximum of 28 decimal places. It features a complete set of mathematical functions including logarithms, exponentiation, roots, etc.

This repository includes TOMLMojo, a lightweight TOML parser in pure Mojo. It parses configuration files and test data, supporting basic types, arrays, and nested tables. While created for DeciMojo’s testing framework, it offers general-purpose structured data parsing with a clean, simple API.

type alias information internal representation
BigUInt BUInt arbitrary-precision unsigned integer List[UInt32]
BigInt BInt arbitrary-precision integer BigUInt, Bool
BigDecimal BDec, Decimal arbitrary-precision decimal BigUInt, Int, Bool
Decimal128 Dec128 128-bit fixed-precision decimal UInt32,UInt32,UInt32,UInt32

Examples

Here are some examples showcasing the arbitrary-precision feature of the BigDecimal type (aliases: BDec and Decimal). For some mathematical operations, the default precision (number of significant digits) is set to 36. You can change the precision by passing the precision argument to the function. This default precision will be configurable globally in future when Mojo supports global variables.

from decimojo.prelude import *


fn main() raises:
    var a = BDec("123456789.123456789")  # BDec is an alias for BigDecimal
    var b = Decimal(
        "1234.56789"
    )  # Decimal is a Python-like alias for BigDecimal

    # === Basic Arithmetic === #
    print(a + b)  # 123458023.691346789
    print(a - b)  # 123455554.555566789
    print(a * b)  # 152415787654.32099750190521
    print(a.true_divide(b + 1))  # 99919.0656560820700835791386582569736

    # === Exponential Functions === #
    print(a.sqrt(precision=80))
    # 11111.111066111110969430554981749302328338130654689094538188579359566416821203641
    print(a.cbrt(precision=80))
    # 497.93385938415242742001134219007635925452951248903093962731782327785111102410518
    print(a.root(b, precision=80))
    # 1.0152058862996527138602610522640944903320735973237537866713119992581006582644107
    print(a.power(b, precision=80))
    # 3.3463611024190802340238135400789468682196324482030786573104956727660098625641520E+9989
    print(a.exp(precision=80))
    # 1.8612755889649587035842377856492201091251654136588338983610243887893287518637652E+53616602
    print(a.log(b, precision=80))
    # 2.6173300266565482999078843564152939771708486260101032293924082259819624360226238
    print(a.ln(precision=80))
    # 18.631401767168018032693933348296537542797015174553735308351756611901741276655161

    # === Trigonometric Functions === #
    print(a.sin(precision=200))
    # 0.99985093087193092464780008002600992896256609588456
    #   91036188395766389946401881352599352354527727927177
    #   79589259132243649550891532070326452232864052771477
    #   31418817041042336608522984511928095747763538486886
    print(b.cos(precision=1000))
    # -0.9969577603867772005841841569997528013669868536239849713029893885930748434064450375775817720425329394
    #    9756020177557431933434791661179643984869397089102223199519409695771607230176923201147218218258755323
    #    7563476302904118661729889931783126826250691820526961290122532541861737355873869924820906724540889765
    #    5940445990824482174517106016800118438405307801022739336016834311018727787337447844118359555063575166
    #    5092352912854884589824773945355279792977596081915868398143592738704592059567683083454055626123436523
    #    6998108941189617922049864138929932713499431655377552668020889456390832876383147018828166124313166286
    #    6004871998201597316078894718748251490628361253685772937806895692619597915005978762245497623003811386
    #    0913693867838452088431084666963414694032898497700907783878500297536425463212578556546527017688874265
    #    0785862902484462361413598747384083001036443681873292719322642381945064144026145428927304407689433744
    #    5821277763016669042385158254006302666602333649775547203560187716156055524418512492782302125286330865

    # === Internal representation of the number === #
    (
        Decimal(
            "3.141592653589793238462643383279502884197169399375105820974944"
        ).power(2, precision=60)
    ).print_internal_representation()
    # Internal Representation Details of BigDecimal
    # ----------------------------------------------
    # number:         9.8696044010893586188344909998
    #                 761511353136994072407906264133
    #                 5
    # coefficient:    986960440108935861883449099987
    #                 615113531369940724079062641335
    # negative:       False
    # scale:          59
    # word 0:         62641335
    # word 1:         940724079
    # word 2:         113531369
    # word 3:         99987615
    # word 4:         861883449
    # word 5:         440108935
    # word 6:         986960
    # ----------------------------------------------

Here is a comprehensive quick-start guide showcasing each major function of the BigInt type (BInt).

from decimojo.prelude import *

fn main() raises:
    # === Construction ===
    var a = BigInt("12345678901234567890")         # From string
    var b = BInt(12345)                            # From integer
    
    # === Basic Arithmetic ===
    print(a + b)                                   # Addition: 12345678901234580235
    print(a - b)                                   # Subtraction: 12345678901234555545
    print(a * b)                                   # Multiplication: 152415787814108380241050
    
    # === Division Operations ===
    print(a // b)                                  # Floor division: 999650944609516
    print(a.truncate_divide(b))                    # Truncate division: 999650944609516
    print(a % b)                                   # Modulo: 9615
    
    # === Power Operation ===
    print(BInt(2).power(10))                     # Power: 1024
    print(BInt(2) ** 10)                         # Power (using ** operator): 1024
    
    # === Comparison ===
    print(a > b)                                   # Greater than: True
    print(a == BInt("12345678901234567890"))     # Equality: True
    print(a.is_zero())                             # Check for zero: False
    
    # === Type Conversions ===
    print(String(a))                               # To string: "12345678901234567890"
    
    # === Sign Handling ===
    print(-a)                                      # Negation: -12345678901234567890
    print(abs(BInt("-12345678901234567890")))    # Absolute value: 12345678901234567890
    print(a.is_negative())                         # Check if negative: False

    # === Extremely large numbers ===
    # 3600 digits // 1800 digits
    print(BInt("123456789" * 400) // BInt("987654321" * 200))

Nomenclature

DeciMojo combines “Deci” and “Mojo” - reflecting its purpose and implementation language. “Deci” (from Latin “decimus” meaning “tenth”) highlights our focus on the decimal numeral system that humans naturally use for counting and calculations.

Although the name emphasizes decimals with fractional parts, DeciMojo embraces the full spectrum of decimal mathematics. Our BigInt type, while handling only integers, is designed specifically for the decimal numeral system with its base-10 internal representation. This approach offers optimal performance while maintaining human-readable decimal semantics, contrasting with binary-focused libraries. Furthermore, BigInt serves as the foundation for our BigDecimal implementation, enabling arbitrary-precision calculations across both integer and fractional domains.

The name ultimately emphasizes our mission: bringing precise, reliable decimal calculations to the Mojo ecosystem, addressing the fundamental need for exact arithmetic that floating-point representations cannot provide.

Status

Rome wasn’t built in a day. DeciMojo is currently under active development. It has successfully progressed through the “make it work” phase and is now well into the “make it right” phase with many optimizations already in place. Bug reports and feature requests are welcome! If you encounter issues, please file them here.

6 Likes

This is great! :fire: Would love to get it added to the official Modular community channel

1 Like

Hi Caroline! Thanks!

Yes, I also have this thought and I am editing the recipes. :smiley:

1 Like

Awesome! Let me know if you have any questions!

1 Like

Hi Caroline! I am thinking about renaming the package because the scope is going more than the decimal type. May I ask whether it is okay to change the name of the package in the modular-community repo?

Another question is about the length of the package name that can be hosted at modular-community channel. Is there a minimum number of letters in the package name?

Thank you in advance for your answering!

I realized I answered this via DM but never posted in the public thread - sharing my response below for others who might have the same question:

The best way to update the name without breaking the package for existing users of the modular-community channel is to have the updated recipe do what’s called “multi-output” to create a package under the old name that depends on the renamed package.

There’s an example of a multi-output recipe here: rattler-build/examples/mamba/recipe.yaml at main · prefix-dev/rattler-build · GitHub and I can try to help with a more tailored example next week if you’d like.

Our team also recommends that you only output the old package name until you hit a major breaking version of your package (i.e. if the package is going to break at an API level, you can also use that as an opportunity to move users over to your new package name).

1 Like