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
114 changes: 93 additions & 21 deletions src/decimojo/bigint/bigint.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ mathematical methods that do not implement a trait.
from memory import UnsafePointer
import testing

import decimojo.bigint.arithmetics
import decimojo.bigint.comparison
import decimojo.str


@value
struct BigInt:
struct BigInt(Absable, IntableRaising, Writable):
"""Represents an integer with arbitrary precision.

Notes:
Expand Down Expand Up @@ -62,6 +64,13 @@ struct BigInt:

# ===------------------------------------------------------------------=== #
# Constructors and life time dunder methods
#
# __init__(out self)
# __init__(out self, empty: Bool)
# __init__(out self, empty: Bool, capacity: Int)
# __init__(out self, *words: UInt32, sign: Bool) raises
# __init__(out self, value: Int) raises
# __init__(out self, value: String) raises
# ===------------------------------------------------------------------=== #

fn __init__(out self):
Expand Down Expand Up @@ -99,23 +108,43 @@ struct BigInt:
fn __init__(out self, *words: UInt32, sign: Bool) raises:
"""Initializes a BigInt from raw components.

Args:
words: The UInt32 words representing the coefficient.
Each UInt32 word represents digits ranging from 0 to 10^9 - 1.
The words are stored in little-endian order.
sign: The sign of the BigInt.

Notes:

This method checks whether the words are smaller than `999_999_999`.

Example:
```console
BigInt(123456789, 987654321, sign=False) # 987654321_123456789
BigInt(123456789, 987654321, sign=True) # -987654321_123456789
```

End of examples.
"""
self.words = Self._words_type()
self.words = Self._words_type(capacity=len(words))
self.sign = sign

# Check if the words are valid
for word in words:
if word > Self.MAX_OF_WORD:
raise Error(
"Error in `from_components`: Word value exceeds maximum"
"Error in `BigInt.__init__()`: Word value exceeds maximum"
" value of 999_999_999"
)
else:
self.words.append(word)

fn __init__(out self, value: Int) raises:
"""Initializes a BigInt from an Int.
See `from_int()` for more information.
"""
self = Self.from_int(value)

fn __init__(out self, value: String) raises:
"""Initializes a BigInt from a string representation.
See `from_string()` for more information.
Expand All @@ -127,44 +156,50 @@ struct BigInt:

# ===------------------------------------------------------------------=== #
# Constructing methods that are not dunders
#
# from_raw_words(*words: UInt32, sign: Bool) -> Self
# from_int(value: Int) -> Self
# from_uint128(value: UInt128, sign: Bool = False) -> Self
# from_string(value: String) -> Self
# ===------------------------------------------------------------------=== #

@staticmethod
fn from_components(*words: UInt32, sign: Bool = False) raises -> Self:
"""Creates a BigInt from raw components.
fn from_raw_words(*words: UInt32, sign: Bool) -> Self:
"""Initializes a BigInt from raw words without validating the words.

Args:
words: The UInt32 words representing the coefficient.
Each UInt32 word represents digits ranging from 0 to 10^9 - 1.
The words are stored in little-endian order.
sign: The sign of the BigInt.

Notes:

Compare to `BigInt.__init__()`, this method checks the validity of words
by checking if the words are smaller than `999_999_999`.
This method does not validate whether the words are smaller than
`999_999_999`.
"""

var result = Self(empty=True)
result = Self(empty=True, capacity=len(words))
result.sign = sign

# Check if the words are valid
for word in words:
if word > Self.MAX_OF_WORD:
raise Error(
"Error in `from_components`: Word value exceeds maximum"
" value of 999_999_999"
)
else:
result.words.append(word)

return result
result.words.append(word)
return result^

@staticmethod
fn from_int(value: Int) -> Self:
fn from_int(value: Int) raises -> Self:
"""Creates a BigInt from an integer."""
if value == 0:
return Self()

var result = Self(empty=True)

var remainder: Int
var quotient: Int
if value < 0:
# Handle the case of Int.MIN due to asymmetry of Int.MIN and Int.MAX
if value == Int.MIN:
return Self.from_raw_words(
UInt32(854775807), UInt32(223372036), UInt32(9), sign=True
)
result.sign = True
remainder = -value
else:
Expand Down Expand Up @@ -301,6 +336,12 @@ struct BigInt:
# Output dunders, type-transfer dunders
# ===------------------------------------------------------------------=== #

fn __int__(self) raises -> Int:
"""Returns the number as Int.
See `to_int()` for more information.
"""
return self.to_int()

fn __str__(self) -> String:
"""Returns string representation of the BigInt.
See `to_str()` for more information.
Expand All @@ -321,6 +362,37 @@ struct BigInt:
"""
writer.write(String(self))

fn to_int(self) raises -> Int:
"""Returns the number as Int.

Returns:
The number as Int.

Raises:
Error: If the number is too large or too small to fit in Int.
"""

# 2^63-1 = 9_223_372_036_854_775_807
# is larger than 10^18 -1 but smaller than 10^27 - 1

if len(self.words) > 3:
raise Error(
"Error in `BigInt.to_int()`: The number exceeds the size of Int"
)

var value: Int128 = 0
for i in range(len(self.words)):
value += Int128(self.words[i]) * Int128(Self.BASE_OF_WORD) ** i

value = -value if self.sign else value

if value < Int128(Int.MIN) or value > Int128(Int.MAX):
raise Error(
"Error in `BigInt.to_int()`: The number exceeds the size of Int"
)

return Int(value)

fn to_str(self) -> String:
"""Returns string representation of the BigInt."""

Expand Down
112 changes: 112 additions & 0 deletions tests/bigint/test_bigint_to_int.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""
Test BigInt conversion methods: to_int() and __int__
for different numerical cases.
"""

import testing

from decimojo.bigint.bigint import BigInt
import decimojo.bigint.arithmetics as arithmetics


fn test_int_conversion() raises:
print("------------------------------------------------------")
print("--- Testing BigInt to Int Conversion ---")

# Test positive integer
var b1 = BigInt("123")
var i1 = b1.to_int()
print("BigInt(123).to_int() =", i1)
testing.assert_equal(i1, 123)

# Test through __int__() operator
var i1b = Int(b1)
print("Int(BigInt(123)) =", i1b)
testing.assert_equal(i1b, 123)

# Test negative integer
var b2 = BigInt("-456")
var i2 = b2.to_int()
print("BigInt(-456).to_int() =", i2)
testing.assert_equal(i2, -456)

# Test zero
var b3 = BigInt("0")
var i3 = b3.to_int()
print("BigInt(0).to_int() =", i3)
testing.assert_equal(i3, 0)

# Test large positive number within Int range
var b4 = BigInt("9999999999") # 10 billion is within Int64 range
var i4 = b4.to_int()
print("BigInt(9999999999).to_int() =", i4)
testing.assert_equal(i4, 9999999999)

# Test large negative number within Int range
var b5 = BigInt("-9999999999")
var i5 = b5.to_int()
print("BigInt(-9999999999).to_int() =", i5)
testing.assert_equal(i5, -9999999999)

# Test Int.MAX edge case
var b6 = BigInt(String(Int.MAX))
var i6 = b6.to_int()
print("BigInt(Int.MAX).to_int() =", i6)
testing.assert_equal(i6, Int.MAX)

# Test Int.MIN edge case
var b7 = BigInt(String(Int.MIN))
var i7 = b7.to_int()
print("BigInt(Int.MIN).to_int() =", i7)
testing.assert_equal(i7, Int.MIN)


fn test_error_cases() raises:
print("------------------------------------------------------")
print("--- Testing Error Cases ---")

# Test number larger than Int.MAX
var b1 = BigInt(String(Int.MAX)) + BigInt("1")
print("Testing conversion of:", b1, "(Int.MAX + 1)")
var exception_caught = False
try:
var _b1 = b1.to_int()
except:
exception_caught = True
testing.assert_true(
exception_caught, "Expected error for value exceeding Int.MAX"
)

# Test number smaller than Int.MIN
var b2 = BigInt(String(Int.MIN)) - BigInt("1")
print("Testing conversion of:", b2, "(Int.MIN - 1)")
exception_caught = False
try:
var _b2 = b2.to_int()
except:
exception_caught = True
testing.assert_true(
exception_caught, "Expected error for value less than Int.MIN"
)

# Test very large number
var b3 = BigInt("99999999999999999999999999999") # Way beyond Int64 range
print("Testing conversion of very large number:", b3)
exception_caught = False
try:
var _b3 = b3.to_int()
except:
exception_caught = True
testing.assert_true(
exception_caught, "Expected error for very large BigInt"
)


fn main() raises:
print("Starting BigInt to Int conversion tests...")

test_int_conversion()

test_error_cases()

print("\nAll tests completed!")