Skip to content

Commit daa1a12

Browse files
authored
[bigint] Implement multiply for BigInt (#51)
This pull request focuses on enhancing the `BigInt` type in the `decimojo` library by adding multiplication functionality and comprehensive tests. The most important changes include the addition of the `multiply` function, modifications to the `BigInt` structure to support the new functionality, and the creation of extensive tests to ensure the correctness of the multiplication operation. ### Enhancements to `BigInt` functionality: * Added the `multiply` function to perform multiplication of two `BigInt` numbers in `src/decimojo/bigint/arithmetics.mojo`. * Introduced the `__mul__` and `__imul__` methods to the `BigInt` structure to support the multiplication operator. ### Modifications to `BigInt` structure: * Replaced the alias `_words_type` with `List[UInt32]` for the `words` field in the `BigInt` structure. * Updated the `__init__` methods to use `List[UInt32]` directly for initializing the `words` field. * Added utility methods `is_one_or_minus_one` and `is_negative` to the `BigInt` structure for special case handling. ### Comprehensive tests for multiplication: * Created a new test file `tests/bigint/test_bigint_multiply.mojo` with extensive test cases covering basic multiplication, special cases, negative numbers, large numbers, and the commutative property.
1 parent 67d6b24 commit daa1a12

File tree

3 files changed

+428
-8
lines changed

3 files changed

+428
-8
lines changed

src/decimojo/bigint/arithmetics.mojo

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,77 @@ fn absolute(x: BigInt) -> BigInt:
187187
return -x
188188
else:
189189
return x
190+
191+
192+
fn multiply(x1: BigInt, x2: BigInt) raises -> BigInt:
193+
"""Returns the product of two BigInt numbers.
194+
195+
Args:
196+
x1: The first BigInt operand (multiplicand).
197+
x2: The second BigInt operand (multiplier).
198+
199+
Returns:
200+
The product of the two BigInt numbers.
201+
"""
202+
# CASE: One of the operands is zero
203+
if x1.is_zero() or x2.is_zero():
204+
return BigInt.from_raw_words(UInt32(0), sign=x1.sign != x2.sign)
205+
206+
# CASE: One of the operands is one or negative one
207+
if x1.is_one_or_minus_one():
208+
var result = x2
209+
result.sign = x1.sign != x2.sign
210+
return result^
211+
212+
if x2.is_one_or_minus_one():
213+
var result = x1
214+
result.sign = x1.sign != x2.sign
215+
return result^
216+
217+
# The maximum number of words in the result is the sum of the words in the operands
218+
var max_result_len = len(x1.words) + len(x2.words)
219+
var result = BigInt(empty=True, capacity=max_result_len)
220+
result.sign = x1.sign != x2.sign
221+
222+
# Initialize result words with zeros
223+
for _ in range(max_result_len):
224+
result.words.append(0)
225+
226+
# Perform the multiplication word by word (from least significant to most significant)
227+
# x1 = x1[0] + x1[1] * 10^9
228+
# x2 = x2[0] + x2[1] * 10^9
229+
# x1 * x2 = x1[0] * x2[0] + (x1[0] * x2[1] + x1[1] * x2[0]) * 10^9 + x1[1] * x2[1] * 10^18
230+
var carry: UInt64 = 0
231+
for i in range(len(x1.words)):
232+
# Skip if the word is zero
233+
if x1.words[i] == 0:
234+
continue
235+
236+
carry = UInt64(0)
237+
238+
for j in range(len(x2.words)):
239+
# Skip if the word is zero
240+
if x2.words[j] == 0:
241+
continue
242+
243+
# Calculate the product of the current words
244+
# plus the carry from the previous multiplication
245+
# plus the value already at this position in the result
246+
var product = UInt64(x1.words[i]) * UInt64(
247+
x2.words[j]
248+
) + carry + UInt64(result.words[i + j])
249+
250+
# The lower 9 digits (base 10^9) go into the current word
251+
# The upper digits become the carry for the next position
252+
result.words[i + j] = UInt32(product % 1_000_000_000)
253+
carry = product // 1_000_000_000
254+
255+
# If there is a carry left, add it to the next position
256+
if carry > 0:
257+
result.words[i + len(x2.words)] += UInt32(carry)
258+
259+
# Remove trailing zeros
260+
while len(result.words) > 1 and result.words[len(result.words) - 1] == 0:
261+
result.words.resize(len(result.words) - 1)
262+
263+
return result^

src/decimojo/bigint/bigint.mojo

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ struct BigInt(Absable, IntableRaising, Writable):
4646
which can be of arbitrary length stored in little-endian order.
4747
Each UInt32 word represents digits ranging from 0 to 10^9 - 1.
4848
- A Bool value for the sign.
49-
"""
5049
51-
# Internal representation fields
52-
alias _words_type = List[UInt32, hint_trivial_type=True]
50+
The value of the BigInt is calculated as follows:
51+
52+
x = x[0] * 10^0 + x[1] * 10^9 + x[2] * 10^18 + ... x[n] * 10^(9n)
53+
"""
5354

54-
var words: Self._words_type
55+
var words: List[UInt32]
5556
"""A list of UInt32 words representing the coefficient."""
5657
var sign: Bool
5758
"""Sign information."""
@@ -76,7 +77,7 @@ struct BigInt(Absable, IntableRaising, Writable):
7677

7778
fn __init__(out self):
7879
"""Initializes a BigInt with value 0."""
79-
self.words = Self._words_type(UInt32(0))
80+
self.words = List[UInt32](UInt32(0))
8081
self.sign = False
8182

8283
fn __init__(out self, empty: Bool):
@@ -87,7 +88,7 @@ struct BigInt(Absable, IntableRaising, Writable):
8788
If True, the BigInt is empty.
8889
If False, the BigInt is intialized with value 0.
8990
"""
90-
self.words = Self._words_type()
91+
self.words = List[UInt32]()
9192
self.sign = False
9293
if not empty:
9394
self.words.append(UInt32(0))
@@ -101,7 +102,7 @@ struct BigInt(Absable, IntableRaising, Writable):
101102
If False, the BigInt is intialized with value 0.
102103
capacity: The capacity of the BigInt.
103104
"""
104-
self.words = Self._words_type(capacity=capacity)
105+
self.words = List[UInt32](capacity=capacity)
105106
self.sign = False
106107
if not empty:
107108
self.words.append(UInt32(0))
@@ -127,7 +128,7 @@ struct BigInt(Absable, IntableRaising, Writable):
127128
128129
End of examples.
129130
"""
130-
self.words = Self._words_type(capacity=len(words))
131+
self.words = List[UInt32](capacity=len(words))
131132
self.sign = sign
132133

133134
# Check if the words are valid
@@ -470,6 +471,10 @@ struct BigInt(Absable, IntableRaising, Writable):
470471
fn __sub__(self, other: Self) raises -> Self:
471472
return decimojo.bigint.arithmetics.subtract(self, other)
472473

474+
@always_inline
475+
fn __mul__(self, other: Self) raises -> Self:
476+
return decimojo.bigint.arithmetics.multiply(self, other)
477+
473478
# ===------------------------------------------------------------------=== #
474479
# Basic binary augmented arithmetic assignments dunders
475480
# These methods are called to implement the binary augmented arithmetic
@@ -485,6 +490,10 @@ struct BigInt(Absable, IntableRaising, Writable):
485490
fn __isub__(mut self, other: Self) raises:
486491
self = decimojo.bigint.arithmetics.subtract(self, other)
487492

493+
@always_inline
494+
fn __imul__(mut self, other: Self) raises:
495+
self = decimojo.bigint.arithmetics.multiply(self, other)
496+
488497
# ===------------------------------------------------------------------=== #
489498
# Other methods
490499
# ===------------------------------------------------------------------=== #
@@ -494,6 +503,16 @@ struct BigInt(Absable, IntableRaising, Writable):
494503
"""Returns True if this BigInt represents zero."""
495504
return len(self.words) == 1 and self.words[0] == 0
496505

506+
@always_inline
507+
fn is_one_or_minus_one(self) -> Bool:
508+
"""Returns True if this BigInt represents one or negative one."""
509+
return len(self.words) == 1 and self.words[0] == 1
510+
511+
@always_inline
512+
fn is_negative(self) -> Bool:
513+
"""Returns True if this BigInt is negative."""
514+
return self.sign
515+
497516
# ===------------------------------------------------------------------=== #
498517
# Internal methods
499518
# ===------------------------------------------------------------------=== #

0 commit comments

Comments
 (0)