Skip to content

Commit b8c9991

Browse files
authored
[bigint] Add to_int and improve from_int (#49)
This pull request includes several changes to the `BigInt` structure in the `src/decimojo/bigint/bigint.mojo` file to improve its functionality and add new initialization methods. Additionally, new tests have been added to validate these changes. Enhancements to `BigInt` structure: * `src/decimojo/bigint/bigint.mojo`: Added new initialization methods for `BigInt` including `__init__` with different parameter signatures and the `to_int` method for converting `BigInt` to `Int`. Updated the `from_components` method to `from_raw_words` and improved documentation and examples. New tests: * `tests/bigint/test_bigint_to_int.mojo`: Added new tests to validate the `to_int` method and `__int__` operator for various numerical cases, including edge cases and error scenarios.
1 parent 54ad5ec commit b8c9991

2 files changed

Lines changed: 205 additions & 21 deletions

File tree

src/decimojo/bigint/bigint.mojo

Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ mathematical methods that do not implement a trait.
2626
from memory import UnsafePointer
2727
import testing
2828

29+
import decimojo.bigint.arithmetics
30+
import decimojo.bigint.comparison
2931
import decimojo.str
3032

3133

3234
@value
33-
struct BigInt:
35+
struct BigInt(Absable, IntableRaising, Writable):
3436
"""Represents an integer with arbitrary precision.
3537
3638
Notes:
@@ -62,6 +64,13 @@ struct BigInt:
6264

6365
# ===------------------------------------------------------------------=== #
6466
# Constructors and life time dunder methods
67+
#
68+
# __init__(out self)
69+
# __init__(out self, empty: Bool)
70+
# __init__(out self, empty: Bool, capacity: Int)
71+
# __init__(out self, *words: UInt32, sign: Bool) raises
72+
# __init__(out self, value: Int) raises
73+
# __init__(out self, value: String) raises
6574
# ===------------------------------------------------------------------=== #
6675

6776
fn __init__(out self):
@@ -99,23 +108,43 @@ struct BigInt:
99108
fn __init__(out self, *words: UInt32, sign: Bool) raises:
100109
"""Initializes a BigInt from raw components.
101110
111+
Args:
112+
words: The UInt32 words representing the coefficient.
113+
Each UInt32 word represents digits ranging from 0 to 10^9 - 1.
114+
The words are stored in little-endian order.
115+
sign: The sign of the BigInt.
116+
102117
Notes:
103118
104119
This method checks whether the words are smaller than `999_999_999`.
120+
121+
Example:
122+
```console
123+
BigInt(123456789, 987654321, sign=False) # 987654321_123456789
124+
BigInt(123456789, 987654321, sign=True) # -987654321_123456789
125+
```
126+
127+
End of examples.
105128
"""
106-
self.words = Self._words_type()
129+
self.words = Self._words_type(capacity=len(words))
107130
self.sign = sign
108131

109132
# Check if the words are valid
110133
for word in words:
111134
if word > Self.MAX_OF_WORD:
112135
raise Error(
113-
"Error in `from_components`: Word value exceeds maximum"
136+
"Error in `BigInt.__init__()`: Word value exceeds maximum"
114137
" value of 999_999_999"
115138
)
116139
else:
117140
self.words.append(word)
118141

142+
fn __init__(out self, value: Int) raises:
143+
"""Initializes a BigInt from an Int.
144+
See `from_int()` for more information.
145+
"""
146+
self = Self.from_int(value)
147+
119148
fn __init__(out self, value: String) raises:
120149
"""Initializes a BigInt from a string representation.
121150
See `from_string()` for more information.
@@ -127,44 +156,50 @@ struct BigInt:
127156

128157
# ===------------------------------------------------------------------=== #
129158
# Constructing methods that are not dunders
159+
#
160+
# from_raw_words(*words: UInt32, sign: Bool) -> Self
161+
# from_int(value: Int) -> Self
162+
# from_uint128(value: UInt128, sign: Bool = False) -> Self
163+
# from_string(value: String) -> Self
130164
# ===------------------------------------------------------------------=== #
131165

132166
@staticmethod
133-
fn from_components(*words: UInt32, sign: Bool = False) raises -> Self:
134-
"""Creates a BigInt from raw components.
167+
fn from_raw_words(*words: UInt32, sign: Bool) -> Self:
168+
"""Initializes a BigInt from raw words without validating the words.
169+
170+
Args:
171+
words: The UInt32 words representing the coefficient.
172+
Each UInt32 word represents digits ranging from 0 to 10^9 - 1.
173+
The words are stored in little-endian order.
174+
sign: The sign of the BigInt.
135175
136176
Notes:
137177
138-
Compare to `BigInt.__init__()`, this method checks the validity of words
139-
by checking if the words are smaller than `999_999_999`.
178+
This method does not validate whether the words are smaller than
179+
`999_999_999`.
140180
"""
141181

142-
var result = Self(empty=True)
182+
result = Self(empty=True, capacity=len(words))
143183
result.sign = sign
144-
145-
# Check if the words are valid
146184
for word in words:
147-
if word > Self.MAX_OF_WORD:
148-
raise Error(
149-
"Error in `from_components`: Word value exceeds maximum"
150-
" value of 999_999_999"
151-
)
152-
else:
153-
result.words.append(word)
154-
155-
return result
185+
result.words.append(word)
186+
return result^
156187

157188
@staticmethod
158-
fn from_int(value: Int) -> Self:
189+
fn from_int(value: Int) raises -> Self:
159190
"""Creates a BigInt from an integer."""
160191
if value == 0:
161192
return Self()
162193

163194
var result = Self(empty=True)
164-
165195
var remainder: Int
166196
var quotient: Int
167197
if value < 0:
198+
# Handle the case of Int.MIN due to asymmetry of Int.MIN and Int.MAX
199+
if value == Int.MIN:
200+
return Self.from_raw_words(
201+
UInt32(854775807), UInt32(223372036), UInt32(9), sign=True
202+
)
168203
result.sign = True
169204
remainder = -value
170205
else:
@@ -301,6 +336,12 @@ struct BigInt:
301336
# Output dunders, type-transfer dunders
302337
# ===------------------------------------------------------------------=== #
303338

339+
fn __int__(self) raises -> Int:
340+
"""Returns the number as Int.
341+
See `to_int()` for more information.
342+
"""
343+
return self.to_int()
344+
304345
fn __str__(self) -> String:
305346
"""Returns string representation of the BigInt.
306347
See `to_str()` for more information.
@@ -321,6 +362,37 @@ struct BigInt:
321362
"""
322363
writer.write(String(self))
323364

365+
fn to_int(self) raises -> Int:
366+
"""Returns the number as Int.
367+
368+
Returns:
369+
The number as Int.
370+
371+
Raises:
372+
Error: If the number is too large or too small to fit in Int.
373+
"""
374+
375+
# 2^63-1 = 9_223_372_036_854_775_807
376+
# is larger than 10^18 -1 but smaller than 10^27 - 1
377+
378+
if len(self.words) > 3:
379+
raise Error(
380+
"Error in `BigInt.to_int()`: The number exceeds the size of Int"
381+
)
382+
383+
var value: Int128 = 0
384+
for i in range(len(self.words)):
385+
value += Int128(self.words[i]) * Int128(Self.BASE_OF_WORD) ** i
386+
387+
value = -value if self.sign else value
388+
389+
if value < Int128(Int.MIN) or value > Int128(Int.MAX):
390+
raise Error(
391+
"Error in `BigInt.to_int()`: The number exceeds the size of Int"
392+
)
393+
394+
return Int(value)
395+
324396
fn to_str(self) -> String:
325397
"""Returns string representation of the BigInt."""
326398

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
Test BigInt conversion methods: to_int() and __int__
3+
for different numerical cases.
4+
"""
5+
6+
import testing
7+
8+
from decimojo.bigint.bigint import BigInt
9+
import decimojo.bigint.arithmetics as arithmetics
10+
11+
12+
fn test_int_conversion() raises:
13+
print("------------------------------------------------------")
14+
print("--- Testing BigInt to Int Conversion ---")
15+
16+
# Test positive integer
17+
var b1 = BigInt("123")
18+
var i1 = b1.to_int()
19+
print("BigInt(123).to_int() =", i1)
20+
testing.assert_equal(i1, 123)
21+
22+
# Test through __int__() operator
23+
var i1b = Int(b1)
24+
print("Int(BigInt(123)) =", i1b)
25+
testing.assert_equal(i1b, 123)
26+
27+
# Test negative integer
28+
var b2 = BigInt("-456")
29+
var i2 = b2.to_int()
30+
print("BigInt(-456).to_int() =", i2)
31+
testing.assert_equal(i2, -456)
32+
33+
# Test zero
34+
var b3 = BigInt("0")
35+
var i3 = b3.to_int()
36+
print("BigInt(0).to_int() =", i3)
37+
testing.assert_equal(i3, 0)
38+
39+
# Test large positive number within Int range
40+
var b4 = BigInt("9999999999") # 10 billion is within Int64 range
41+
var i4 = b4.to_int()
42+
print("BigInt(9999999999).to_int() =", i4)
43+
testing.assert_equal(i4, 9999999999)
44+
45+
# Test large negative number within Int range
46+
var b5 = BigInt("-9999999999")
47+
var i5 = b5.to_int()
48+
print("BigInt(-9999999999).to_int() =", i5)
49+
testing.assert_equal(i5, -9999999999)
50+
51+
# Test Int.MAX edge case
52+
var b6 = BigInt(String(Int.MAX))
53+
var i6 = b6.to_int()
54+
print("BigInt(Int.MAX).to_int() =", i6)
55+
testing.assert_equal(i6, Int.MAX)
56+
57+
# Test Int.MIN edge case
58+
var b7 = BigInt(String(Int.MIN))
59+
var i7 = b7.to_int()
60+
print("BigInt(Int.MIN).to_int() =", i7)
61+
testing.assert_equal(i7, Int.MIN)
62+
63+
64+
fn test_error_cases() raises:
65+
print("------------------------------------------------------")
66+
print("--- Testing Error Cases ---")
67+
68+
# Test number larger than Int.MAX
69+
var b1 = BigInt(String(Int.MAX)) + BigInt("1")
70+
print("Testing conversion of:", b1, "(Int.MAX + 1)")
71+
var exception_caught = False
72+
try:
73+
var _b1 = b1.to_int()
74+
except:
75+
exception_caught = True
76+
testing.assert_true(
77+
exception_caught, "Expected error for value exceeding Int.MAX"
78+
)
79+
80+
# Test number smaller than Int.MIN
81+
var b2 = BigInt(String(Int.MIN)) - BigInt("1")
82+
print("Testing conversion of:", b2, "(Int.MIN - 1)")
83+
exception_caught = False
84+
try:
85+
var _b2 = b2.to_int()
86+
except:
87+
exception_caught = True
88+
testing.assert_true(
89+
exception_caught, "Expected error for value less than Int.MIN"
90+
)
91+
92+
# Test very large number
93+
var b3 = BigInt("99999999999999999999999999999") # Way beyond Int64 range
94+
print("Testing conversion of very large number:", b3)
95+
exception_caught = False
96+
try:
97+
var _b3 = b3.to_int()
98+
except:
99+
exception_caught = True
100+
testing.assert_true(
101+
exception_caught, "Expected error for very large BigInt"
102+
)
103+
104+
105+
fn main() raises:
106+
print("Starting BigInt to Int conversion tests...")
107+
108+
test_int_conversion()
109+
110+
test_error_cases()
111+
112+
print("\nAll tests completed!")

0 commit comments

Comments
 (0)