Skip to content

Commit 6d7a7c1

Browse files
committed
Finish add and sub for BigInt
1 parent b861e99 commit 6d7a7c1

File tree

9 files changed

+657
-94
lines changed

9 files changed

+657
-94
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ A comprehensive decimal mathematics library for [Mojo](https://www.modular.com/m
66

77
## Overview
88

9-
DeciMojo provides a comprehensive high-precision 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.
9+
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.
1010

1111
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.
1212

13+
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.
14+
1315
## Installation
1416

1517
DeciMojo is available in the [modular-community](https://repo.prefix.dev/modular-community) package repository. You can install it using any of these methods:

docs/todo.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

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

5-
- [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.
5+
- [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.
6+
- [ ] Implement different methods for augmented arithmetic assignments to improve memeory-efficiency and performance.

src/decimojo/big_int/__init__.mojo

Whitespace-only changes.

src/decimojo/big_int/arithmetics.mojo

Lines changed: 0 additions & 91 deletions
This file was deleted.

src/decimojo/bigint/__init__.mojo

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# ===----------------------------------------------------------------------=== #
2+
# Copyright 2025 Yuhao Zhu
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ===----------------------------------------------------------------------=== #
16+
17+
"""Sub-package for big integer arithmetic."""
18+
19+
# About the module name `bigint`:
20+
# According to PEP-8, Modules should have short, all-lowercase names.
21+
# Underscores can be used in the module name if it improves readability.
22+
# Python packages should also have short, all-lowercase names, although the use
23+
# of underscores is discouraged.
24+
# Becuase the readability of `bigint` is already good, I use `bigint` instead of
25+
# `big_int` for this sub-package and the module.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# ===----------------------------------------------------------------------=== #
2+
# Copyright 2025 Yuhao Zhu
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ===----------------------------------------------------------------------=== #
16+
17+
"""
18+
Implements basic arithmetic functions for the BigInt type.
19+
"""
20+
21+
import time
22+
import testing
23+
24+
from decimojo.bigint.bigint import BigInt
25+
import decimojo.bigint.comparison
26+
from decimojo.rounding_mode import RoundingMode
27+
28+
29+
fn add(x1: BigInt, x2: BigInt) raises -> BigInt:
30+
"""Returns the sum of two BigInts.
31+
32+
Args:
33+
x1: The first BigInt operand.
34+
x2: The second BigInt operand.
35+
36+
Returns:
37+
The sum of the two BigInts.
38+
"""
39+
40+
# If one of the numbers is zero, return the other number
41+
if len(x1.words) == 1 and x1.words[0] == 0:
42+
return x2
43+
if len(x2.words) == 1 and x2.words[0] == 0:
44+
return x1
45+
46+
# If signs are different, we use `subtract` instead
47+
if x1.sign != x2.sign:
48+
return subtract(x1, -x2)
49+
50+
# At this point, both numbers have the same sign
51+
# The result will have the same sign as the operands
52+
# The result will have at most one more word than the longer operand
53+
var result = BigInt(
54+
empty=True, capacity=max(len(x1.words), len(x2.words)) + 1
55+
)
56+
result.sign = x1.sign # Result has the same sign as the operands
57+
58+
var carry: UInt32 = 0
59+
var ith: Int = 0
60+
var sum_of_words: UInt32 = 0
61+
62+
# Add corresponding words from both numbers
63+
while ith < len(x1.words) or ith < len(x2.words):
64+
sum_of_words = carry
65+
66+
# Add x1's word if available
67+
if ith < len(x1.words):
68+
sum_of_words += x1.words[ith]
69+
70+
# Add x2's word if available
71+
if ith < len(x2.words):
72+
sum_of_words += x2.words[ith]
73+
74+
# Compute new word and carry
75+
carry = UInt32(sum_of_words // 1_000_000_000)
76+
result.words.append(UInt32(sum_of_words % 1_000_000_000))
77+
78+
ith += 1
79+
80+
# Handle final carry if it exists
81+
if carry > 0:
82+
result.words.append(carry)
83+
84+
return result
85+
86+
87+
fn subtract(x1: BigInt, x2: BigInt) raises -> BigInt:
88+
"""Returns the difference of two numbers.
89+
90+
Args:
91+
x1: The first number (minuend).
92+
x2: The second number (subtrahend).
93+
94+
Returns:
95+
The result of subtracting x2 from x1.
96+
"""
97+
# If the subtrahend is zero, return the minuend
98+
if x2.is_zero():
99+
return x1
100+
# If the minuend is zero, return the negated subtrahend
101+
if x1.is_zero():
102+
return -x2
103+
104+
# If signs are different, we use `add` instead
105+
if x1.sign != x2.sign:
106+
return add(x1, -x2)
107+
108+
# At this point, both numbers have the same sign
109+
# We need to determine which number has the larger absolute value
110+
var comparison_result = decimojo.bigint.comparison.compare_absolute(x1, x2)
111+
112+
if comparison_result == 0:
113+
# |x1| = |x2|
114+
return BigInt() # Return zero
115+
116+
# The result will have no more words than the larger operand
117+
var result = BigInt(empty=True, capacity=max(len(x1.words), len(x2.words)))
118+
var borrow: Int32 = 0
119+
var ith: Int = 0
120+
var difference: Int32 = 0 # Int32 is sufficient for the difference
121+
122+
if comparison_result > 0:
123+
# |x1| > |x2|
124+
result.sign = x1.sign
125+
while ith < len(x1.words):
126+
# Subtract the borrow
127+
difference = Int32(x1.words[ith]) - borrow
128+
# Subtract smaller's word if available
129+
if ith < len(x2.words):
130+
difference -= Int32(x2.words[ith])
131+
# Handle borrowing if needed
132+
if difference < Int32(0):
133+
difference += Int32(1_000_000_000)
134+
borrow = Int32(1)
135+
else:
136+
borrow = Int32(0)
137+
result.words.append(UInt32(difference))
138+
ith += 1
139+
140+
else:
141+
# |x1| < |x2|
142+
# Same as above, but we swap x1 and x2
143+
result.sign = not x2.sign
144+
while ith < len(x2.words):
145+
difference = Int32(x2.words[ith]) - borrow
146+
if ith < len(x1.words):
147+
difference -= Int32(x1.words[ith])
148+
if difference < Int32(0):
149+
difference += Int32(1_000_000_000)
150+
borrow = Int32(1)
151+
else:
152+
borrow = Int32(0)
153+
result.words.append(UInt32(difference))
154+
ith += 1
155+
156+
# Remove trailing zeros
157+
while len(result.words) > 1 and result.words[len(result.words) - 1] == 0:
158+
result.words.resize(len(result.words) - 1)
159+
160+
return result
161+
162+
163+
fn negative(x: BigInt) -> BigInt:
164+
"""Returns the negative of a BigInt number.
165+
166+
Args:
167+
x: The BigInt value to compute the negative of.
168+
169+
Returns:
170+
A new BigInt containing the negative of x.
171+
"""
172+
var result = x
173+
result.sign = not result.sign
174+
return result
175+
176+
177+
fn absolute(x: BigInt) -> BigInt:
178+
"""Returns the absolute value of a BigInt number.
179+
180+
Args:
181+
x: The BigInt value to compute the absolute value of.
182+
183+
Returns:
184+
A new BigInt containing the absolute value of x.
185+
"""
186+
if x.sign:
187+
var result = x
188+
result.sign = False
189+
return result
190+
else:
191+
return x
Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ struct BigInt:
5858
# ===------------------------------------------------------------------=== #
5959

6060
alias MAX_OF_WORD = UInt32(999_999_999)
61+
alias BASE_OF_WORD = UInt32(1_000_000_000)
6162

6263
# ===------------------------------------------------------------------=== #
6364
# Constructors and life time dunder methods
@@ -358,6 +359,25 @@ struct BigInt:
358359

359360
return result^
360361

362+
# ===------------------------------------------------------------------=== #
363+
# Basic unary operation dunders
364+
# neg
365+
# ===------------------------------------------------------------------=== #
366+
367+
@always_inline
368+
fn __abs__(self) -> Self:
369+
"""Returns the absolute value of this number.
370+
See `absolute()` for more information.
371+
"""
372+
return decimojo.bigint.arithmetics.absolute(self)
373+
374+
@always_inline
375+
fn __neg__(self) -> Self:
376+
"""Returns the negation of this number.
377+
See `negative()` for more information.
378+
"""
379+
return decimojo.bigint.arithmetics.negative(self)
380+
361381
# ===------------------------------------------------------------------=== #
362382
# Basic binary arithmetic operation dunders
363383
# These methods are called to implement the binary arithmetic operations
@@ -366,7 +386,20 @@ struct BigInt:
366386

367387
@always_inline
368388
fn __add__(self, other: Self) raises -> Self:
369-
return decimojo.big_int.arithmetics.add(self, other)
389+
return decimojo.bigint.arithmetics.add(self, other)
390+
391+
@always_inline
392+
fn __sub__(self, other: Self) raises -> Self:
393+
return decimojo.bigint.arithmetics.subtract(self, other)
394+
395+
# ===------------------------------------------------------------------=== #
396+
# Other methods
397+
# ===------------------------------------------------------------------=== #
398+
399+
@always_inline
400+
fn is_zero(self) -> Bool:
401+
"""Returns True if this BigInt represents zero."""
402+
return len(self.words) == 1 and self.words[0] == 0
370403

371404
# ===------------------------------------------------------------------=== #
372405
# Internal methods

0 commit comments

Comments
 (0)