Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# DeciMojo

A fixed-point decimal mathematics library for [the Mojo programming language 🔥](https://www.modular.com/mojo).
A comprehensive decimal mathematics library for [Mojo](https://www.modular.com/mojo).

**[中文·漢字»](./docs/readme_zht.md)** | **[Repository on GitHub»](https://github.com/forfudan/decimojo)**

## Overview

DeciMojo provides a comprehensive fixed-point decimal 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.
DeciMojo provides a comprehensive decimal 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 type is Decimal: A 128-bit fixed-point decimal implementation supporting up to 29 significant digits with a maximum of 28 decimal places[^fixed_precision]. It features a complete set of mathematical functions including logarithms, exponentiation, roots, and trigonometric operations.

The library is expanding to include `BigInt` and `BigDecimal` types that support arbitrary precision, allowing for calculations with unlimited digits. These extensions are currently under active development.

## Installation

DeciMojo is available in the [modular-community](https://repo.prefix.dev/modular-community) package repository. You can install it using any of these methods:
Expand Down Expand Up @@ -102,9 +104,9 @@ This project draws inspiration from several established decimal implementations

## Nonmenclature

DeciMojo combines "Decimal" and "Mojo" - reflecting both its purpose (decimal arithmetic) and the programming language it's implemented in. The name emphasizes the project's focus on bringing precise decimal calculations to the Mojo ecosystem.
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.

For brevity, you can refer to it as "deci" (derived from the Latin root "decimus" meaning "tenth").
The name 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

Expand Down Expand Up @@ -145,7 +147,7 @@ Regular benchmarks against Python's `decimal` module are available in the `bench

### Future Extensions 🚀 (PLANNED)

- **BigInt**: Arbitrary-precision integer type with unlimited digits.
- **BigInt**: Arbitrary-precision integer type with unlimited digits[^integer].
- **BigDecimal**: Arbitrary-precision decimal type with configurable precision[^arbitrary_precision].
- **BigComplex**: Arbitrary-precision complex number type built on BigDecimal.

Expand All @@ -164,7 +166,7 @@ If you find DeciMojo useful for your research, consider listing it in your citat
@software{Zhu.2025,
author = {Zhu, Yuhao},
year = {2025},
title = {DeciMojo: A fixed-point decimal arithmetic library in Mojo},
title = {DeciMojo: A comprehensive decimal mathematics library for Mojo},
url = {https://github.com/forfudan/decimojo},
version = {0.1.0},
note = {Computer Software}
Expand All @@ -176,4 +178,5 @@ If you find DeciMojo useful for your research, consider listing it in your citat
This repository and its contributions are licensed under the Apache License v2.0.

[^fixed_precision]: The `Decimal` type can represent values with up to 29 significant digits and a maximum of 28 digits after the decimal point. When a value exceeds the maximum representable value (`2^96 - 1`), DeciMojo either raises an error or rounds the value to fit within these constraints. For example, the significant digits of `8.8888888888888888888888888888` (29 eights total with 28 after the decimal point) exceeds the maximum representable value (`2^96 - 1`) and is automatically rounded to `8.888888888888888888888888889` (28 eights total with 27 after the decimal point). DeciMojo's `Decimal` type is similar to `System.Decimal` (C#/.NET), `rust_decimal` in Rust, `DECIMAL/NUMERIC` in SQL Server, etc.
[^integer]: Integers are a special case of decimal numbers (with zero fractional part). Our BigInt implementation serves both as a standalone arbitrary-precision integer type and as the foundation for our upcoming BigDecimal implementation.
[^arbitrary_precision]: Similar to `decimal` and `mpmath` in Python, `java.math.BigDecimal` in Java, etc.
3 changes: 2 additions & 1 deletion docs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

This is a to-do list for Yuhao's personal use.

- [x] (#31) The `exp()` function performs slower than Python's counterpart in specific cases. Detailed investigation reveals the bottleneck stems from multiplication operations between decimals with significant fractional components. These operations currently rely on UInt256 arithmetic, which introduces performance overhead. Optimization of the `multiply()` function is required to address these performance bottlenecks, particularly for high-precision decimal multiplication with many digits after the decimal point.
- [x] (#31) The `exp()` function performs slower than Python's counterpart in specific cases. Detailed investigation reveals the bottleneck stems from multiplication operations between decimals with significant fractional components. These operations currently rely on UInt256 arithmetic, which introduces performance overhead. Optimization of the `multiply()` function is required to address these performance bottlenecks, particularly for high-precision decimal multiplication with many digits after the decimal point.
- [ ] Implement different methods for augmented arithmetic assignments to improve memeory-efficiency and performance.
25 changes: 25 additions & 0 deletions src/decimojo/bigint/__init__.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ===----------------------------------------------------------------------=== #
# Copyright 2025 Yuhao Zhu
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #

"""Sub-package for big integer arithmetic."""

# About the module name `bigint`:
# According to PEP-8, Modules should have short, all-lowercase names.
# Underscores can be used in the module name if it improves readability.
# Python packages should also have short, all-lowercase names, although the use
# of underscores is discouraged.
# Becuase the readability of `bigint` is already good, I use `bigint` instead of
# `big_int` for this sub-package and the module.
191 changes: 191 additions & 0 deletions src/decimojo/bigint/arithmetics.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# ===----------------------------------------------------------------------=== #
# Copyright 2025 Yuhao Zhu
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #

"""
Implements basic arithmetic functions for the BigInt type.
"""

import time
import testing

from decimojo.bigint.bigint import BigInt
import decimojo.bigint.comparison
from decimojo.rounding_mode import RoundingMode


fn add(x1: BigInt, x2: BigInt) raises -> BigInt:
"""Returns the sum of two BigInts.

Args:
x1: The first BigInt operand.
x2: The second BigInt operand.

Returns:
The sum of the two BigInts.
"""

# If one of the numbers is zero, return the other number
if len(x1.words) == 1 and x1.words[0] == 0:
return x2
if len(x2.words) == 1 and x2.words[0] == 0:
return x1

# If signs are different, we use `subtract` instead
if x1.sign != x2.sign:
return subtract(x1, -x2)

# At this point, both numbers have the same sign
# The result will have the same sign as the operands
# The result will have at most one more word than the longer operand
var result = BigInt(
empty=True, capacity=max(len(x1.words), len(x2.words)) + 1
)
result.sign = x1.sign # Result has the same sign as the operands

var carry: UInt32 = 0
var ith: Int = 0
var sum_of_words: UInt32 = 0

# Add corresponding words from both numbers
while ith < len(x1.words) or ith < len(x2.words):
sum_of_words = carry

# Add x1's word if available
if ith < len(x1.words):
sum_of_words += x1.words[ith]

# Add x2's word if available
if ith < len(x2.words):
sum_of_words += x2.words[ith]

# Compute new word and carry
carry = UInt32(sum_of_words // 1_000_000_000)
result.words.append(UInt32(sum_of_words % 1_000_000_000))

ith += 1

# Handle final carry if it exists
if carry > 0:
result.words.append(carry)

return result


fn subtract(x1: BigInt, x2: BigInt) raises -> BigInt:
"""Returns the difference of two numbers.

Args:
x1: The first number (minuend).
x2: The second number (subtrahend).

Returns:
The result of subtracting x2 from x1.
"""
# If the subtrahend is zero, return the minuend
if x2.is_zero():
return x1
# If the minuend is zero, return the negated subtrahend
if x1.is_zero():
return -x2

# If signs are different, we use `add` instead
if x1.sign != x2.sign:
return add(x1, -x2)

# At this point, both numbers have the same sign
# We need to determine which number has the larger absolute value
var comparison_result = decimojo.bigint.comparison.compare_absolute(x1, x2)

if comparison_result == 0:
# |x1| = |x2|
return BigInt() # Return zero

# The result will have no more words than the larger operand
var result = BigInt(empty=True, capacity=max(len(x1.words), len(x2.words)))
var borrow: Int32 = 0
var ith: Int = 0
var difference: Int32 = 0 # Int32 is sufficient for the difference

if comparison_result > 0:
# |x1| > |x2|
result.sign = x1.sign
while ith < len(x1.words):
# Subtract the borrow
difference = Int32(x1.words[ith]) - borrow
# Subtract smaller's word if available
if ith < len(x2.words):
difference -= Int32(x2.words[ith])
# Handle borrowing if needed
if difference < Int32(0):
difference += Int32(1_000_000_000)
borrow = Int32(1)
else:
borrow = Int32(0)
result.words.append(UInt32(difference))
ith += 1

else:
# |x1| < |x2|
# Same as above, but we swap x1 and x2
result.sign = not x2.sign
while ith < len(x2.words):
difference = Int32(x2.words[ith]) - borrow
if ith < len(x1.words):
difference -= Int32(x1.words[ith])
if difference < Int32(0):
difference += Int32(1_000_000_000)
borrow = Int32(1)
else:
borrow = Int32(0)
result.words.append(UInt32(difference))
ith += 1

# Remove trailing zeros
while len(result.words) > 1 and result.words[len(result.words) - 1] == 0:
result.words.resize(len(result.words) - 1)

return result


fn negative(x: BigInt) -> BigInt:
"""Returns the negative of a BigInt number.

Args:
x: The BigInt value to compute the negative of.

Returns:
A new BigInt containing the negative of x.
"""
var result = x
result.sign = not result.sign
return result


fn absolute(x: BigInt) -> BigInt:
"""Returns the absolute value of a BigInt number.

Args:
x: The BigInt value to compute the absolute value of.

Returns:
A new BigInt containing the absolute value of x.
"""
if x.sign:
var result = x
result.sign = False
return result
else:
return x
Loading