From 333c6879fa43fceae2382e276e0edd5ae5b30d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZHU=20Yuhao=20=E6=9C=B1=E5=AE=87=E6=B5=A9?= Date: Tue, 25 Mar 2025 23:34:30 +0100 Subject: [PATCH 1/8] rewrite bigint --- src/decimojo/bigint/arithmetics.mojo | 213 +++++++++++++----------- src/decimojo/bigint/bigint.mojo | 222 +++++++------------------- src/decimojo/bigint/comparison.mojo | 10 +- src/decimojo/biguint/arithmetics.mojo | 2 +- src/decimojo/biguint/biguint.mojo | 17 +- 5 files changed, 192 insertions(+), 272 deletions(-) diff --git a/src/decimojo/bigint/arithmetics.mojo b/src/decimojo/bigint/arithmetics.mojo index d74fb7df..98feb687 100644 --- a/src/decimojo/bigint/arithmetics.mojo +++ b/src/decimojo/bigint/arithmetics.mojo @@ -51,7 +51,9 @@ fn add(x1: BigInt, x2: BigInt) raises -> BigInt: # 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 + empty=True, + capacity=max(len(x1.magnitude.words), len(x2.magnitude.words)) + 1, + sign=False, ) result.sign = x1.sign # Result has the same sign as the operands @@ -60,26 +62,26 @@ fn add(x1: BigInt, x2: BigInt) raises -> BigInt: var sum_of_words: UInt32 = 0 # Add corresponding words from both numbers - while ith < len(x1.words) or ith < len(x2.words): + while ith < len(x1.magnitude.words) or ith < len(x2.magnitude.words): sum_of_words = carry # Add x1's word if available - if ith < len(x1.words): - sum_of_words += x1.words[ith] + if ith < len(x1.magnitude.words): + sum_of_words += x1.magnitude.words[ith] # Add x2's word if available - if ith < len(x2.words): - sum_of_words += x2.words[ith] + if ith < len(x2.magnitude.words): + sum_of_words += x2.magnitude.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)) + result.magnitude.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) + result.magnitude.words.append(carry) return result^ @@ -114,7 +116,11 @@ fn subtract(x1: BigInt, x2: BigInt) raises -> BigInt: 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 result = BigInt( + empty=True, + capacity=max(len(x1.magnitude.words), len(x2.magnitude.words)), + sign=False, + ) var borrow: Int32 = 0 var ith: Int = 0 var difference: Int32 = 0 # Int32 is sufficient for the difference @@ -122,40 +128,43 @@ fn subtract(x1: BigInt, x2: BigInt) raises -> BigInt: if comparison_result > 0: # |x1| > |x2| result.sign = x1.sign - while ith < len(x1.words): + while ith < len(x1.magnitude.words): # Subtract the borrow - difference = Int32(x1.words[ith]) - borrow + difference = Int32(x1.magnitude.words[ith]) - borrow # Subtract smaller's word if available - if ith < len(x2.words): - difference -= Int32(x2.words[ith]) + if ith < len(x2.magnitude.words): + difference -= Int32(x2.magnitude.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)) + result.magnitude.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]) + while ith < len(x2.magnitude.words): + difference = Int32(x2.magnitude.words[ith]) - borrow + if ith < len(x1.magnitude.words): + difference -= Int32(x1.magnitude.words[ith]) if difference < Int32(0): difference += Int32(1_000_000_000) borrow = Int32(1) else: borrow = Int32(0) - result.words.append(UInt32(difference)) + result.magnitude.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) + while ( + len(result.magnitude.words) > 1 + and result.magnitude.words[len(result.magnitude.words) - 1] == 0 + ): + result.magnitude.words.resize(len(result.magnitude.words) - 1) return result^ @@ -201,7 +210,7 @@ fn multiply(x1: BigInt, x2: BigInt) raises -> BigInt: """ # CASE: One of the operands is zero if x1.is_zero() or x2.is_zero(): - return BigInt.from_raw_words(UInt32(0), sign=x1.sign != x2.sign) + return BigInt(UInt32(0), sign=x1.sign != x2.sign) # CASE: One of the operands is one or negative one if x1.is_one_or_minus_one(): @@ -215,50 +224,53 @@ fn multiply(x1: BigInt, x2: BigInt) raises -> BigInt: return result^ # The maximum number of words in the result is the sum of the words in the operands - var max_result_len = len(x1.words) + len(x2.words) - var result = BigInt(empty=True, capacity=max_result_len) + var max_result_len = len(x1.magnitude.words) + len(x2.magnitude.words) + var result = BigInt(empty=True, capacity=max_result_len, sign=False) result.sign = x1.sign != x2.sign # Initialize result words with zeros for _ in range(max_result_len): - result.words.append(0) + result.magnitude.words.append(0) # Perform the multiplication word by word (from least significant to most significant) # x1 = x1[0] + x1[1] * 10^9 # x2 = x2[0] + x2[1] * 10^9 # x1 * x2 = x1[0] * x2[0] + (x1[0] * x2[1] + x1[1] * x2[0]) * 10^9 + x1[1] * x2[1] * 10^18 var carry: UInt64 = 0 - for i in range(len(x1.words)): + for i in range(len(x1.magnitude.words)): # Skip if the word is zero - if x1.words[i] == 0: + if x1.magnitude.words[i] == 0: continue carry = UInt64(0) - for j in range(len(x2.words)): + for j in range(len(x2.magnitude.words)): # Skip if the word is zero - if x2.words[j] == 0: + if x2.magnitude.words[j] == 0: continue # Calculate the product of the current words # plus the carry from the previous multiplication # plus the value already at this position in the result - var product = UInt64(x1.words[i]) * UInt64( - x2.words[j] - ) + carry + UInt64(result.words[i + j]) + var product = UInt64(x1.magnitude.words[i]) * UInt64( + x2.magnitude.words[j] + ) + carry + UInt64(result.magnitude.words[i + j]) # The lower 9 digits (base 10^9) go into the current word # The upper digits become the carry for the next position - result.words[i + j] = UInt32(product % 1_000_000_000) + result.magnitude.words[i + j] = UInt32(product % 1_000_000_000) carry = product // 1_000_000_000 # If there is a carry left, add it to the next position if carry > 0: - result.words[i + len(x2.words)] += UInt32(carry) + result.magnitude.words[i + len(x2.magnitude.words)] += UInt32(carry) # Remove trailing zeros - while len(result.words) > 1 and result.words[len(result.words) - 1] == 0: - result.words.resize(len(result.words) - 1) + while ( + len(result.magnitude.words) > 1 + and result.magnitude.words[len(result.magnitude.words) - 1] == 0 + ): + result.magnitude.words.resize(len(result.magnitude.words) - 1) return result^ @@ -293,9 +305,10 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: return result # CASE: Single words division - if len(x1.words) == 1 and len(x2.words) == 1: - var result = BigInt.from_raw_words( - UInt32(x1.words[0] // x2.words[0]), sign=x1.sign != x2.sign + if len(x1.magnitude.words) == 1 and len(x2.magnitude.words) == 1: + var result = BigInt( + UInt32(x1.magnitude.words[0] // x2.magnitude.words[0]), + sign=x1.sign != x2.sign, ) return result @@ -304,33 +317,34 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: # Divisor is 10^n # Remove the last words (10^9) and shift the rest var result: BigInt - if len(x2.words) == 1: + if len(x2.magnitude.words) == 1: result = x1 else: - var word_shift = len(x2.words) - 1 + var word_shift = len(x2.magnitude.words) - 1 # If we need to drop more words than exists, result is zero - if word_shift >= len(x1.words): + if word_shift >= len(x1.magnitude.words): return BigInt() # Create result with the remaining words - result = BigInt(empty=True) - for i in range(word_shift, len(x1.words)): - result.words.append(x1.words[i]) + result = BigInt(empty=True, sign=False) + for i in range(word_shift, len(x1.magnitude.words)): + result.magnitude.words.append(x1.magnitude.words[i]) # Get the last word of the divisor - var x2_word = x2.words[len(x2.words) - 1] + var x2_word = x2.magnitude.words[len(x2.magnitude.words) - 1] var carry = UInt32(0) var power_of_carry = UInt32(1_000_000_000) // x2_word - for i in range(len(result.words) - 1, -1, -1): - var quot = result.words[i] // x2_word - var rem = result.words[i] % x2_word - result.words[i] = quot + carry * power_of_carry + for i in range(len(result.magnitude.words) - 1, -1, -1): + var quot = result.magnitude.words[i] // x2_word + var rem = result.magnitude.words[i] % x2_word + result.magnitude.words[i] = quot + carry * power_of_carry carry = rem # Remove leading zeros while ( - len(result.words) > 1 and result.words[len(result.words) - 1] == 0 + len(result.magnitude.words) > 1 + and result.magnitude.words[len(result.magnitude.words) - 1] == 0 ): - result.words.resize(len(result.words) - 1) + result.magnitude.words.resize(len(result.magnitude.words) - 1) result.sign = x1.sign != x2.sign return result @@ -341,18 +355,20 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: # CASE: |dividend| == |divisor| if x1.compare_absolute(x2) == 0: - return BigInt.from_raw_words(UInt32(1), sign=x1.sign != x2.sign) + return BigInt(UInt32(1), sign=x1.sign != x2.sign) # CASE: division by a single-word number - if len(x2.words) == 1: - var divisor_value = x2.words[0] - var result = BigInt(empty=True) + if len(x2.magnitude.words) == 1: + var divisor_value = x2.magnitude.words[0] + var result = BigInt(empty=True, sign=False) var temp_remainder: UInt64 = 0 # Process from most significant word to least significant - for i in range(len(x1.words) - 1, -1, -1): + for i in range(len(x1.magnitude.words) - 1, -1, -1): # Combine remainder with current digit - var current = temp_remainder * 1_000_000_000 + UInt64(x1.words[i]) + var current = temp_remainder * 1_000_000_000 + UInt64( + x1.magnitude.words[i] + ) # Calculate quotient and new remainder var quotient_digit = current // UInt64(divisor_value) @@ -360,18 +376,20 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: # Only add significant digits to the result # This avoids leading zeros - if len(result.words) > 0 or quotient_digit > 0: - result.words.append(UInt32(quotient_digit)) + if len(result.magnitude.words) > 0 or quotient_digit > 0: + result.magnitude.words.append(UInt32(quotient_digit)) # If no digits were added, result is zero - if len(result.words) == 0: - result.words.append(0) + if len(result.magnitude.words) == 0: + result.magnitude.words.append(0) # To match the expected base-10^9 representation, # we need to reverse the order of the words in the result - var reversed_result = BigInt(empty=True, capacity=len(result.words)) - for i in range(len(result.words) - 1, -1, -1): - reversed_result.words.append(result.words[i]) + var reversed_result = BigInt( + empty=True, capacity=len(result.magnitude.words), sign=False + ) + for i in range(len(result.magnitude.words) - 1, -1, -1): + reversed_result.magnitude.words.append(result.magnitude.words[i]) # Set the sign reversed_result.sign = x1.sign != x2.sign @@ -379,17 +397,19 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: # CASE: multi-word divisors # Initialize result and working copy of dividend - var result = BigInt(empty=True, capacity=len(x1.words)) + var result = BigInt( + empty=True, capacity=len(x1.magnitude.words), sign=False + ) var remainder = absolute(x1) var normalized_divisor = absolute(x2) # Calculate the number of significant words in each operand - var n = len(remainder.words) - while n > 0 and remainder.words[n - 1] == 0: + var n = len(remainder.magnitude.words) + while n > 0 and remainder.magnitude.words[n - 1] == 0: n -= 1 - var m = len(normalized_divisor.words) - while m > 0 and normalized_divisor.words[m - 1] == 0: + var m = len(normalized_divisor.magnitude.words) + while m > 0 and normalized_divisor.magnitude.words[m - 1] == 0: m -= 1 # If divisor has more significant digits than dividend, result is zero @@ -401,7 +421,7 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: # Initialize result with zeros for _ in range(d + 1): - result.words.append(0) + result.magnitude.words.append(0) # Working variables for the division algorithm var j = d @@ -413,31 +433,29 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: # Get the relevant part of the dividend for this step if j + m < n: - dividend_part = UInt64(remainder.words[j + m]) + dividend_part = UInt64(remainder.magnitude.words[j + m]) if j + m - 1 < n: dividend_part = dividend_part * 1_000_000_000 + UInt64( - remainder.words[j + m - 1] + remainder.magnitude.words[j + m - 1] ) elif j + m - 1 < n: - dividend_part = UInt64(remainder.words[j + m - 1]) + dividend_part = UInt64(remainder.magnitude.words[j + m - 1]) # Calculate quotient digit (cap at MAX_DIGIT) - var divisor_high = UInt64(normalized_divisor.words[m - 1]) + var divisor_high = UInt64(normalized_divisor.magnitude.words[m - 1]) if divisor_high == 0: divisor_high = 1 # Avoid division by zero var q = min(dividend_part // divisor_high, UInt64(999_999_999)) # Create trial product: q * divisor - var trial_product = normalized_divisor * BigInt.from_raw_words( - UInt32(q), sign=False - ) + var trial_product = normalized_divisor * BigInt(UInt32(q), sign=False) # Shift trial product left j positions - var shifted_product = BigInt(empty=True) + var shifted_product = BigInt(empty=True, sign=False) for _ in range(j): - shifted_product.words.append(0) - for word in trial_product.words: - shifted_product.words.append(word[]) + shifted_product.magnitude.words.append(0) + for word in trial_product.magnitude.words: + shifted_product.magnitude.words.append(word[]) # Use binary search for quotient adjustment if shifted_product.compare_absolute(remainder) > 0: @@ -449,16 +467,16 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: var mid: UInt64 = (low + high) / 2 # Recalculate trial product with new q - trial_product = normalized_divisor * BigInt.from_raw_words( + trial_product = normalized_divisor * BigInt( UInt32(mid), sign=False ) # Recalculate shifted product - shifted_product = BigInt(empty=True) + shifted_product = BigInt(empty=True, sign=False) for _ in range(j): - shifted_product.words.append(0) - for word in trial_product.words: - shifted_product.words.append(word[]) + shifted_product.magnitude.words.append(0) + for word in trial_product.magnitude.words: + shifted_product.magnitude.words.append(word[]) if shifted_product.compare_absolute(remainder) <= 0: # This quotient works, try a larger one @@ -469,19 +487,17 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: high = mid - 1 # Recalculate final product with best q found - trial_product = normalized_divisor * BigInt.from_raw_words( - UInt32(q), sign=False - ) + trial_product = normalized_divisor * BigInt(UInt32(q), sign=False) # Recalculate final shifted product - shifted_product = BigInt(empty=True) + shifted_product = BigInt(empty=True, sign=False) for _ in range(j): - shifted_product.words.append(0) - for word in trial_product.words: - shifted_product.words.append(word[]) + shifted_product.magnitude.words.append(0) + for word in trial_product.magnitude.words: + shifted_product.magnitude.words.append(word[]) # Store quotient digit - result.words[j] = UInt32(q) + result.magnitude.words[j] = UInt32(q) # Subtract shifted product from remainder remainder = subtract(remainder, shifted_product) @@ -490,8 +506,11 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: j -= 1 # Remove leading zeros - while len(result.words) > 1 and result.words[len(result.words) - 1] == 0: - result.words.resize(len(result.words) - 1) + while ( + len(result.magnitude.words) > 1 + and result.magnitude.words[len(result.magnitude.words) - 1] == 0 + ): + result.magnitude.words.resize(len(result.magnitude.words) - 1) # Set the sign result.sign = x1.sign != x2.sign diff --git a/src/decimojo/bigint/bigint.mojo b/src/decimojo/bigint/bigint.mojo index b8586ead..17171fe1 100644 --- a/src/decimojo/bigint/bigint.mojo +++ b/src/decimojo/bigint/bigint.mojo @@ -27,6 +27,7 @@ from memory import UnsafePointer import testing import time +from decimojo.biguint.biguint import BigUInt import decimojo.bigint.arithmetics import decimojo.bigint.comparison import decimojo.str @@ -34,26 +35,18 @@ import decimojo.str @value struct BigInt(Absable, IntableRaising, Writable): - """Represents an integer with arbitrary precision. + """Represents a base-10 arbitrary-precision signed integer. Notes: Internal Representation: - Use base-10^9 representation for the coefficient of the integer. - Each integer uses a dynamic structure in memory, where: - - An pointer to an array of UInt32 words for the coefficient on the heap, - which can be of arbitrary length stored in little-endian order. - Each UInt32 word represents digits ranging from 0 to 10^9 - 1. + - A base-10 unsigned integer (BigUInt) for magnitude. - A Bool value for the sign. - - The value of the BigInt is calculated as follows: - - x = x[0] * 10^0 + x[1] * 10^9 + x[2] * 10^18 + ... x[n] * 10^(9n) """ - var words: List[UInt32] - """A list of UInt32 words representing the coefficient.""" + var magnitude: BigUInt + """The magnitude of the BigInt.""" var sign: Bool """Sign information.""" @@ -77,23 +70,22 @@ struct BigInt(Absable, IntableRaising, Writable): fn __init__(out self): """Initializes a BigInt with value 0.""" - self.words = List[UInt32](UInt32(0)) + self.magnitude = BigUInt() self.sign = False - fn __init__(out self, empty: Bool): + fn __init__(out self, empty: Bool, sign: Bool): """Initializes an empty BigInt. Args: empty: A Bool value indicating whether the BigInt is empty. If True, the BigInt is empty. If False, the BigInt is intialized with value 0. + sign: The sign of the BigInt. """ - self.words = List[UInt32]() - self.sign = False - if not empty: - self.words.append(UInt32(0)) + self.magnitude = BigUInt(empty=empty) + self.sign = sign - fn __init__(out self, empty: Bool, capacity: Int): + fn __init__(out self, empty: Bool, capacity: Int, sign: Bool): """Initializes an empty BigInt with a given capacity. Args: @@ -101,14 +93,14 @@ struct BigInt(Absable, IntableRaising, Writable): If True, the BigInt is empty. If False, the BigInt is intialized with value 0. capacity: The capacity of the BigInt. + sign: The sign of the BigInt. """ - self.words = List[UInt32](capacity=capacity) - self.sign = False - if not empty: - self.words.append(UInt32(0)) + self.magnitude = BigUInt(empty=empty, capacity=capacity) + self.sign = sign - fn __init__(out self, *words: UInt32, sign: Bool) raises: + fn __init__(out self, owned *words: UInt32, sign: Bool) raises: """Initializes a BigInt from raw components. + See `from_words()` for safer initialization. Args: words: The UInt32 words representing the coefficient. @@ -118,7 +110,8 @@ struct BigInt(Absable, IntableRaising, Writable): Notes: - This method checks whether the words are smaller than `999_999_999`. + This method does not check whether the words are smaller than + `999_999_999`. Example: ```console @@ -128,18 +121,10 @@ struct BigInt(Absable, IntableRaising, Writable): End of examples. """ - self.words = List[UInt32](capacity=len(words)) self.sign = sign - - # Check if the words are valid + self.magnitude = BigUInt(empty=True, capacity=len(words)) for word in words: - if word > Self.MAX_OF_WORD: - raise Error( - "Error in `BigInt.__init__()`: Word value exceeds maximum" - " value of 999_999_999" - ) - else: - self.words.append(word) + self.magnitude.words.append(word[]) fn __init__(out self, value: Int) raises: """Initializes a BigInt from an Int. @@ -159,15 +144,15 @@ struct BigInt(Absable, IntableRaising, Writable): # ===------------------------------------------------------------------=== # # Constructing methods that are not dunders # - # from_raw_words(*words: UInt32, sign: Bool) -> Self + # from_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_raw_words(*words: UInt32, sign: Bool) -> Self: - """Initializes a BigInt from raw words without validating the words. + fn from_words(*words: UInt32, sign: Bool) raises -> Self: + """Initializes a BigInt from raw words. Args: words: The UInt32 words representing the coefficient. @@ -177,14 +162,21 @@ struct BigInt(Absable, IntableRaising, Writable): Notes: - This method does not validate whether the words are smaller than - `999_999_999`. + This method validates whether the words are smaller than `999_999_999`. """ - result = Self(empty=True, capacity=len(words)) - result.sign = sign + result = Self(empty=True, capacity=len(words), sign=sign) + + # Check if the words are valid for word in words: - result.words.append(word) + if word > Self.MAX_OF_WORD: + raise Error( + "Error in `BigInt.__init__()`: Word value exceeds maximum" + " value of 999_999_999" + ) + else: + result.magnitude.words.append(word) + return result^ @staticmethod @@ -193,13 +185,13 @@ struct BigInt(Absable, IntableRaising, Writable): if value == 0: return Self() - var result = Self(empty=True) + var result = Self(empty=True, sign=False) 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( + return Self( UInt32(854775807), UInt32(223372036), UInt32(9), sign=True ) result.sign = True @@ -211,7 +203,7 @@ struct BigInt(Absable, IntableRaising, Writable): while remainder != 0: quotient = remainder // 1_000_000_000 remainder = remainder % 1_000_000_000 - result.words.append(UInt32(remainder)) + result.magnitude.words.append(UInt32(remainder)) remainder = quotient return result @@ -230,18 +222,11 @@ struct BigInt(Absable, IntableRaising, Writable): if value == 0: return Self() - var result = Self(empty=True) - result.sign = False - - var remainder: UInt128 = value - var quotient: UInt128 - while remainder != 0: - quotient = remainder // 1_000_000_000 - remainder = remainder % 1_000_000_000 - result.words.append(UInt32(remainder)) - remainder = quotient + var result = Self(empty=True, sign=False) + result.magnitude = BigUInt.from_uint128(value) + result.sign = sign - return result + return result^ @staticmethod fn from_string(value: String) raises -> BigInt: @@ -261,83 +246,13 @@ struct BigInt(Absable, IntableRaising, Writable): # Check if the number is zero if len(coef) == 1 and coef[0] == UInt8(0): - return Self.from_raw_words(UInt32(0), sign=sign) + return Self(UInt32(0), sign=False) - # Check whether the number is an integer - # If the fractional part is not zero, raise an error - # If the fractional part is zero, remove the fractional part - if scale > 0: - if scale >= len(coef): - raise Error( - "Error in `from_string`: The number is not an integer." - ) - for i in range(1, scale + 1): - if coef[-i] != 0: - raise Error( - "Error in `from_string`: The number is not an integer." - ) - coef.resize(-scale) - scale = 0 - - var number_of_digits = len(coef) - scale - var number_of_words = number_of_digits // 9 - if number_of_digits % 9 != 0: - number_of_words += 1 - - var result = Self(empty=True, capacity=number_of_words) + var result = Self(empty=True, sign=False) + result.magnitude = BigUInt.from_string(value, ignore_sign=True) result.sign = sign - if scale == 0: - # This is a true integer - var number_of_digits = len(coef) - var number_of_words = number_of_digits // 9 - if number_of_digits % 9 != 0: - number_of_words += 1 - - var end: Int = number_of_digits - var start: Int - while end >= 9: - start = end - 9 - var word: UInt32 = 0 - for digit in coef[start:end]: - word = word * 10 + UInt32(digit[]) - result.words.append(word) - end = start - if end > 0: - var word: UInt32 = 0 - for digit in coef[0:end]: - word = word * 10 + UInt32(digit[]) - result.words.append(word) - - return result - - else: # scale < 0 - # This is a true integer with postive exponent - var number_of_trailing_zero_words = -scale // 9 - var remaining_trailing_zero_digits = -scale % 9 - - for _ in range(number_of_trailing_zero_words): - result.words.append(UInt32(0)) - - for _ in range(remaining_trailing_zero_digits): - coef.append(UInt8(0)) - - var end: Int = number_of_digits + scale + remaining_trailing_zero_digits - var start: Int - while end >= 9: - start = end - 9 - var word: UInt32 = 0 - for digit in coef[start:end]: - word = word * 10 + UInt32(digit[]) - result.words.append(word) - end = start - if end > 0: - var word: UInt32 = 0 - for digit in coef[0:end]: - word = word * 10 + UInt32(digit[]) - result.words.append(word) - - return result + return result^ # ===------------------------------------------------------------------=== # # Output dunders, type-transfer dunders @@ -382,14 +297,16 @@ struct BigInt(Absable, IntableRaising, Writable): # 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: + if len(self.magnitude.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 + for i in range(len(self.magnitude.words)): + value += ( + Int128(self.magnitude.words[i]) * Int128(1_000_000_000) ** i + ) value = -value if self.sign else value @@ -403,21 +320,14 @@ struct BigInt(Absable, IntableRaising, Writable): fn to_str(self) -> String: """Returns string representation of the BigInt.""" - if len(self.words) == 0: + if self.magnitude.is_unitialized(): return String("Unitilialized BigInt") if self.is_zero(): return String("0") var result = String("-") if self.sign else String("") - - for i in range(len(self.words) - 1, -1, -1): - if i == len(self.words) - 1: - result = result + String(self.words[i]) - else: - result = result + String(self.words[i]).rjust( - width=9, fillchar="0" - ) + result += self.magnitude.to_str() return result^ @@ -529,12 +439,12 @@ struct BigInt(Absable, IntableRaising, Writable): @always_inline fn is_zero(self) -> Bool: """Returns True if this BigInt represents zero.""" - return len(self.words) == 1 and self.words[0] == 0 + return self.magnitude.is_zero() @always_inline fn is_one_or_minus_one(self) -> Bool: """Returns True if this BigInt represents one or negative one.""" - return len(self.words) == 1 and self.words[0] == 1 + return self.magnitude.is_one() @always_inline fn is_negative(self) -> Bool: @@ -543,23 +453,7 @@ struct BigInt(Absable, IntableRaising, Writable): fn is_abs_power_of_10(x: BigInt) -> Bool: """Check if abs(x) is a power of 10.""" - for i in range(len(x.words) - 1): - if x.words[i] != 0: - return False - var word = x.words[len(x.words) - 1] - if ( - (word == 1) - or (word == 10) - or (word == 100) - or (word == 1000) - or (word == 10_000) - or (word == 100_000) - or (word == 1_000_000) - or (word == 10_000_000) - or (word == 100_000_000) - ): - return True - return False + return x.magnitude.is_power_of_10() # ===------------------------------------------------------------------=== # # Internal methods @@ -572,11 +466,11 @@ struct BigInt(Absable, IntableRaising, Writable): print("number: ", value) print(" ", value.to_str_with_separators()) print("negative: ", value.sign) - for i in range(len(value.words)): + for i in range(len(value.magnitude.words)): print( "word", i, ": ", - String(value.words[i]).rjust(width=9, fillchar="0"), + String(value.magnitude.words[i]).rjust(width=9, fillchar="0"), ) print("--------------------------------") diff --git a/src/decimojo/bigint/comparison.mojo b/src/decimojo/bigint/comparison.mojo index a90214a9..4ea7aa17 100644 --- a/src/decimojo/bigint/comparison.mojo +++ b/src/decimojo/bigint/comparison.mojo @@ -37,18 +37,18 @@ fn compare_absolute(x1: BigInt, x2: BigInt) -> Int8: (3) -1 if |x1| < |x2|. """ # Compare the number of words - if len(x1.words) > len(x2.words): + if len(x1.magnitude.words) > len(x2.magnitude.words): return Int8(1) - if len(x1.words) < len(x2.words): + if len(x1.magnitude.words) < len(x2.magnitude.words): return Int8(-1) # If the number of words are equal, # compare the words from the most significant to the least significant. - var ith = len(x1.words) - 1 + var ith = len(x1.magnitude.words) - 1 while ith >= 0: - if x1.words[ith] > x2.words[ith]: + if x1.magnitude.words[ith] > x2.magnitude.words[ith]: return Int8(1) - if x1.words[ith] < x2.words[ith]: + if x1.magnitude.words[ith] < x2.magnitude.words[ith]: return Int8(-1) ith -= 1 diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index b4e095d2..2f31e149 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -296,7 +296,7 @@ fn floor_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: # CASE: Divisor is 10^n # First remove the last words (10^9) and then shift the rest - if BigUInt.is_abs_power_of_10(x2): + if BigUInt.is_power_of_10(x2): var result: BigUInt if len(x2.words) == 1: result = x1 diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index 019c78f4..940c274e 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -34,7 +34,7 @@ import decimojo.str @value struct BigUInt(Absable, IntableRaising, Writable): - """Represents an unsigned integer with arbitrary length. + """Represents a base-10 arbitrary-precision unsigned integer. Notes: @@ -272,12 +272,15 @@ struct BigUInt(Absable, IntableRaising, Writable): return result^ @staticmethod - fn from_string(value: String) raises -> BigUInt: + fn from_string(value: String, ignore_sign: Bool = False) raises -> BigUInt: """Initializes a BigUInt from a string representation. The string is normalized with `deciomojo.str.parse_numeric_string()`. Args: value: The string representation of the BigUInt. + ignore_sign: A Bool value indicating whether to ignore the sign. + If True, the sign is ignored. + If False, the sign is considered. Returns: The BigUInt representation of the string. @@ -287,7 +290,7 @@ struct BigUInt(Absable, IntableRaising, Writable): var sign: Bool coef, scale, sign = decimojo.str.parse_numeric_string(value) - if sign: + if (not ignore_sign) and sign: raise Error("Error in `from_string()`: The value is negative") # Check if the number is zero @@ -692,8 +695,8 @@ struct BigUInt(Absable, IntableRaising, Writable): """Returns True if this BigUInt represents two.""" return len(self.words) == 1 and self.words[0] == 2 - fn is_abs_power_of_10(x: BigUInt) -> Bool: - """Check if abs(x) is a power of 10.""" + fn is_power_of_10(x: BigUInt) -> Bool: + """Check if x is a power of 10.""" for i in range(len(x.words) - 1): if x.words[i] != 0: return False @@ -731,6 +734,10 @@ struct BigUInt(Absable, IntableRaising, Writable): ) print("--------------------------------") + fn is_unitialized(self) -> Bool: + """Returns True if the BigUInt is uninitialized.""" + return len(self.words) == 0 + fn remove_trailing_zeros(mut number: BigUInt): """Removes trailing zeros from the BigUInt.""" while len(number.words) > 1 and number.words[-1] == 0: From 04996a2a843c694dd475739ffcdf5e75f1cc54e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZHU=20Yuhao=20=E6=9C=B1=E5=AE=87=E6=B5=A9?= Date: Wed, 26 Mar 2025 00:42:21 +0100 Subject: [PATCH 2/8] Add divmod for biguint --- src/decimojo/biguint/arithmetics.mojo | 223 ++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index 2f31e149..b3c5b5b7 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -230,6 +230,159 @@ fn multiply(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return result^ +fn divmod(x1: BigUInt, x2: BigUInt) raises -> Tuple[BigUInt, BigUInt]: + """Returns the quotient and remainder of two numbers, truncating toward zero. + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The quotient of x1 / x2, truncated toward zero and the remainder. + + Raises: + ValueError: If the divisor is zero. + + Notes: + It is equal to truncated division for positive numbers. + """ + + # CASE: x2 is single word + if len(x2.words) == 1: + # SUB-CASE: Division by zero + if x2.words[0] == 0: + raise Error("Error in `truncate_divide`: Division by zero") + + # SUB-CASE: Division by one + if x2.words[0] == 1: + return Tuple(x1, BigUInt(UInt32(0))) + + # SUB-CASE: Division by two + if x2.words[0] == 2: + var result = x1 + floor_divide_inplace_by_2(result) + if x2.words[0] & 1 == 0: + return Tuple(result, BigUInt(UInt32(0))) + else: + return Tuple(result^, BigUInt(UInt32(1))) + + # SUB-CASE: Single words division + if len(x1.words) == 1: + var result = BigUInt(UInt32(x1.words[0] // x2.words[0])) + var remainder = BigUInt(UInt32(x1.words[0] % x2.words[0])) + return Tuple(result^, remainder^) + + # SUB-CASE: Divisor is single word and is power of 2 + if (x2.words[0] & (x2.words[0] - 1)) == 0: + var quotient = x1 # Make a copy + var divisor = x2.words[0] + # Calculate quotient by repeated division by 2 + while divisor > 1: + floor_divide_inplace_by_2(quotient) + divisor >>= 1 + # Calculate remainder: remainder = x1 - quotient * x2 + var remainder = subtract(x1, multiply(quotient, x2)) + return Tuple(quotient^, remainder^) + + # CASE: Dividend is zero + if x1.is_zero(): + return Tuple(BigUInt(), BigUInt()) # Return zero quotient and remainder + + var comparison_result: Int8 = x1.compare(x2) + # CASE: dividend < divisor + if comparison_result < 0: + return Tuple( + BigUInt(), x1 + ) # Return zero quotient and dividend as remainder + # CASE: dividend == divisor + if comparison_result == 0: + return ( + BigUInt(UInt32(1)), + BigUInt(), + ) # Return one quotient and zero remainder + + # CASE: Duo words division by means of UInt64 + if len(x1.words) <= 2 and len(x2.words) <= 2: + var result = BigUInt.from_uint64(x1.to_uint64() // x2.to_uint64()) + var remainder = BigUInt.from_uint64(x1.to_uint64() % x2.to_uint64()) + return Tuple(result^, remainder^) + + # CASE: Divisor is 10^n + # First remove the last words (10^9) and then shift the rest + if BigUInt.is_power_of_10(x2): + var result: BigUInt + var remainder = BigUInt(empty=True) + + if len(x2.words) == 1: + result = x1 + else: + var word_shift = len(x2.words) - 1 + # If we need to drop more words than exists, result is zero + if word_shift >= len(x1.words): + return Tuple(BigUInt(), x1) + # Create result with the remaining words + result = BigUInt(empty=True) + for i in range(word_shift, len(x1.words)): + result.words.append(x1.words[i]) + for i in range(min(word_shift, len(x1.words))): + remainder.words.append(x1.words[i]) + + # Get the last word of the divisor + var x2_word = x2.words[len(x2.words) - 1] + var carry = UInt32(0) + var power_of_carry = UInt32(1_000_000_000) // x2_word + for i in range(len(result.words) - 1, -1, -1): + var quot = result.words[i] // x2_word + var rem = result.words[i] % x2_word + result.words[i] = quot + carry * power_of_carry + carry = rem + + # Add the final remainder from the digit-wise division + if carry > 0: + # If we already have words in the remainder, we need to add this carry + if len(remainder.words) > 0: + # Create a BigUInt with the carry and multiply by appropriate power of 10 + var carry_biguint = BigUInt(carry) + if len(x2.words) > 1: + for _ in range(len(x2.words) - 1): + # Multiply by 10^9 for each word position + carry_biguint = multiply( + carry_biguint, BigUInt(1_000_000_000) + ) + remainder = add(remainder, carry_biguint) + else: + remainder.words.append(carry) + + # Remove leading zeros + result.remove_trailing_zeros() + remainder.remove_trailing_zeros() + + return Tuple(result^, remainder^) + + # CASE: division of very, very large numbers + # Use Newton-Raphson division for large numbers? + + # CASE: all other situations + # Normalize divisor to improve quotient estimation + var normalized_x1 = x1 + var normalized_x2 = x2 + var normalization_factor: UInt32 = 1 + + # Calculate normalization factor to make leading digit of divisor large + var msw = x2.words[len(x2.words) - 1] + if msw < 500_000_000: + while msw < 100_000_000: # Ensure leading digit is significant + msw *= 10 + normalization_factor *= 10 + + # Apply normalization + if normalization_factor > 1: + normalized_x1 = multiply(x1, BigUInt(normalization_factor)) + normalized_x2 = multiply(x2, BigUInt(normalization_factor)) + + return divmod_general(normalized_x1, normalized_x2) + + fn floor_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: """Returns the quotient of two BigUInt numbers, truncating toward zero. @@ -552,6 +705,76 @@ fn multiply_toom_cook_3(x1: BigUInt, x2: BigUInt) raises -> BigUInt: # ===----------------------------------------------------------------------=== # +fn divmod_general(x1: BigUInt, x2: BigUInt) raises -> Tuple[BigUInt, BigUInt]: + """General divmod algorithm for BigInt numbers. + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The quotient of x1 // x2 and the remainder of x1 % x2. + + Raises: + ValueError: If the divisor is zero. + """ + + if x2.is_zero(): + raise Error("Error in `divmod_general`: Division by zero") + + # Initialize result and remainder + var result = BigUInt(empty=True, capacity=len(x1.words)) + var remainder = x1 + + # Calculate significant digits + var n = len(remainder.words) + var m = len(x2.words) + + # Shift and initialize + var d = n - m + for _ in range(d + 1): + result.words.append(0) + + # Main division loop + var j = d + while j >= 0: + # OPTIMIZATION: Better quotient estimation + var q = estimate_quotient(remainder, x2, j, m) + + # Calculate trial product + var trial_product = x2 * BigUInt(UInt32(q)) + var shifted_product = shift_words_left(trial_product, j) + + # OPTIMIZATION: Binary search for adjustment + if shifted_product.compare(remainder) > 0: + var low: UInt64 = 0 + var high: UInt64 = q - 1 + + while low <= high: + var mid = (low + high) / 2 + + # Recalculate with new q + trial_product = x2 * BigUInt(UInt32(mid)) + shifted_product = shift_words_left(trial_product, j) + + if shifted_product.compare(remainder) <= 0: + q = mid # This works + low = mid + 1 + else: + high = mid - 1 + + # Final recalculation with best q + trial_product = x2 * BigUInt(UInt32(q)) + shifted_product = shift_words_left(trial_product, j) + + result.words[j] = UInt32(q) + remainder = subtract(remainder, shifted_product) + j -= 1 + + result.remove_trailing_zeros() + return Tuple(result, remainder) + + fn floor_divide_general(x1: BigUInt, x2: BigUInt) raises -> BigUInt: """General division algorithm for BigInt numbers. From 8d168ddd946d3b6daf5587037bb17a275238c8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZHU=20Yuhao=20=E6=9C=B1=E5=AE=87=E6=B5=A9?= Date: Wed, 26 Mar 2025 10:53:08 +0100 Subject: [PATCH 3/8] Update --- src/decimojo/biguint/arithmetics.mojo | 72 +++++++++++++++++++++++++++ src/decimojo/biguint/biguint.mojo | 25 +++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index b3c5b5b7..26097341 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -82,6 +82,53 @@ fn add(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return result^ +fn add_inplace(mut x1: BigUInt, x2: BigUInt) raises: + """Increments a BigUInt number by another BigUInt number in place. + + Args: + x1: The first unsigned integer operand. + x2: The second unsigned integer operand. + """ + + # If one of the numbers is zero, return the other number + if x1.is_zero(): + x1.words = x2.words.copy() + return + if x2.is_zero(): + return + + var carry: UInt32 = 0 + var ith: Int = 0 + var sum_of_words: UInt32 = 0 + var x1_len = len(x1.words) + + while ith < x1_len 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) + if ith < len(x1.words): + x1.words[ith] = UInt32(sum_of_words % 1_000_000_000) + else: + x1.words.append(UInt32(sum_of_words % 1_000_000_000)) + + ith += 1 + + # Handle final carry if it exists + if carry > 0: + x1.words.append(carry) + + return + + fn subtract(x1: BigUInt, x2: BigUInt) raises -> BigUInt: """Returns the difference of two unsigned integers. @@ -511,6 +558,31 @@ fn truncate_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return floor_divide(x1, x2) +fn ceil_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: + """Returns the quotient of two BigUInt numbers, rounding up. + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The quotient of x1 / x2, rounded up. + + Raises: + ValueError: If the divisor is zero. + """ + + # CASE: Division by zero + if x2.is_zero(): + raise Error("Error in `ceil_divide`: Division by zero") + + # Apply floor division and check if there is a remainder + var quotient = floor_divide(x1, x2) + if quotient * x2 < x1: + quotient += BigUInt(UInt32(1)) + return quotient^ + + fn modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: """Returns the remainder of two BigUInt numbers, truncating toward zero. The remainder has the same sign as the dividend and satisfies: diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index 940c274e..1f7c8ac9 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -563,7 +563,7 @@ struct BigUInt(Absable, IntableRaising, Writable): @always_inline fn __iadd__(mut self, other: Self) raises: - self = decimojo.biguint.arithmetics.add(self, other) + decimojo.biguint.arithmetics.add_inplace(self, other) @always_inline fn __isub__(mut self, other: Self) raises: @@ -635,6 +635,29 @@ struct BigUInt(Absable, IntableRaising, Writable): """ return decimojo.biguint.comparison.compare(self, other) + @always_inline + fn ceil_divide(self, other: Self) raises -> Self: + """Returns the result of ceil dividing this number by `other`. + See `ceil_divide()` for more information. + """ + return decimojo.biguint.arithmetics.ceil_divide(self, other) + + @always_inline + fn floor_divide(self, other: Self) raises -> Self: + """Returns the result of floor dividing this number by `other`. + It is equal to `self // other`. + See `floor_divide()` for more information. + """ + return decimojo.biguint.arithmetics.floor_divide(self, other) + + @always_inline + fn truncate_divide(self, other: Self) raises -> Self: + """Returns the result of truncate dividing this number by `other`. + It is equal to `self // other`. + See `truncate_divide()` for more information. + """ + return decimojo.biguint.arithmetics.truncate_divide(self, other) + fn power(self, exponent: Int) raises -> Self: """Returns the result of raising this number to the power of `exponent`. From d979479fb178190f09d3ecf4b7a8e4b47069a154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZHU=20Yuhao=20=E6=9C=B1=E5=AE=87=E6=B5=A9?= Date: Wed, 26 Mar 2025 11:01:56 +0100 Subject: [PATCH 4/8] Update --- src/decimojo/biguint/arithmetics.mojo | 44 ++++++++++++++++++++++++++- src/decimojo/biguint/biguint.mojo | 7 +++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index 26097341..bb681de3 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -593,7 +593,7 @@ fn modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: x2: The divisor. Returns: - The remainder of x1 being divided by x2, with the same sign as x1. + The remainder of x1 being divided by x2. Raises: ValueError: If the divisor is zero. @@ -626,6 +626,48 @@ fn modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return remainder^ +fn ceil_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: + """Returns the remainder of two BigUInt numbers, rounding up. + The remainder has the same sign as the dividend and satisfies: + x1 = ceil_divide(x1, x2) * x2 + ceil_modulo(x1, x2). + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The remainder of x1 being ceil-divided by x2. + + Raises: + ValueError: If the divisor is zero. + """ + # CASE: Division by zero + if x2.is_zero(): + raise Error("Error in `truncate_modulo`: Division by zero") + + # CASE: Dividend is zero + if x1.is_zero(): + return BigUInt() # Return zero + + # CASE: Divisor is one - no remainder + if x2.is_one(): + return BigUInt() # Always divisible with no remainder + + # CASE: |dividend| < |divisor| - the remainder is the dividend itself + if x1.compare(x2) < 0: + return x1 + + # Calculate quotient with truncation + var quotient = floor_divide(x1, x2) + # Calculate remainder: dividend - (divisor * quotient) + var remainder = subtract(x1, multiply(x2, quotient)) + + if remainder.is_zero(): + return BigUInt() # No remainder + else: + return subtract(x2, remainder) + + # ===----------------------------------------------------------------------=== # # Multiplication Algorithms # ===----------------------------------------------------------------------=== # diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index 1f7c8ac9..e3534058 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -658,6 +658,13 @@ struct BigUInt(Absable, IntableRaising, Writable): """ return decimojo.biguint.arithmetics.truncate_divide(self, other) + @always_inline + fn ceil_modulo(self, other: Self) raises -> Self: + """Returns the result of ceil modulo this number by `other`. + See `ceil_modulo()` for more information. + """ + return decimojo.biguint.arithmetics.ceil_modulo(self, other) + fn power(self, exponent: Int) raises -> Self: """Returns the result of raising this number to the power of `exponent`. From 85761108b5cb05cd32d233b752be5590e66f200c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZHU=20Yuhao=20=E6=9C=B1=E5=AE=87=E6=B5=A9?= Date: Wed, 26 Mar 2025 12:57:10 +0100 Subject: [PATCH 5/8] Update --- benches/bigint/bench.mojo | 32 +- benches/bigint/bench_bigint_floor_divide.mojo | 876 ++++++++++++++++ benches/bigint/bench_bigint_multiply.mojo | 0 benches/biguint/bench.mojo | 32 +- src/decimojo/bigint/arithmetics.mojo | 57 +- src/decimojo/bigint/bigint.mojo | 41 + src/decimojo/biguint/arithmetics.mojo | 89 +- src/decimojo/biguint/biguint.mojo | 81 +- tests/bigint/test_bigint_floor_divide.mojo | 954 ++++++++++++++++++ tests/bigint/test_bigint_truncate_divide.mojo | 443 -------- 10 files changed, 2095 insertions(+), 510 deletions(-) create mode 100644 benches/bigint/bench_bigint_floor_divide.mojo delete mode 100644 benches/bigint/bench_bigint_multiply.mojo create mode 100644 tests/bigint/test_bigint_floor_divide.mojo delete mode 100644 tests/bigint/test_bigint_truncate_divide.mojo diff --git a/benches/bigint/bench.mojo b/benches/bigint/bench.mojo index 9e27f9b9..5b93ee4a 100644 --- a/benches/bigint/bench.mojo +++ b/benches/bigint/bench.mojo @@ -1,7 +1,35 @@ from bench_bigint_add import main as bench_add from bench_bigint_truncate_divide import main as bench_truncate_divide +from bench_bigint_floor_divide import main as bench_floor_divide fn main() raises: - bench_add() - bench_truncate_divide() + print( + """ +========================================= +This is the BigInt Benchmarks +========================================= +1. add: Add +2. truncdiv: Truncate Divide +3. floordiv: Floor Divide +4. all: Run all benchmarks +5. quit: Exit +========================================= +""" + ) + var command = input("Type the bench you want to run:") + if command == "add": + bench_add() + elif command == "truncdiv": + bench_truncate_divide() + elif command == "floordiv": + bench_floor_divide() + elif command == "all": + bench_add() + bench_truncate_divide() + bench_floor_divide() + elif command == "quit": + return + else: + print("Invalid input") + main() diff --git a/benches/bigint/bench_bigint_floor_divide.mojo b/benches/bigint/bench_bigint_floor_divide.mojo new file mode 100644 index 00000000..6384f66c --- /dev/null +++ b/benches/bigint/bench_bigint_floor_divide.mojo @@ -0,0 +1,876 @@ +""" +Comprehensive benchmarks for BigInt floor_divide operation. +Compares performance against Python's built-in int floor division with 50 diverse test cases. +""" + +from decimojo.bigint.bigint import BigInt +import decimojo.bigint.arithmetics +from python import Python, PythonObject +from time import perf_counter_ns +import time +import os +from collections import List + + +fn open_log_file() raises -> PythonObject: + """ + Creates and opens a log file with a timestamp in the filename. + + Returns: + A file object opened for writing. + """ + var python = Python.import_module("builtins") + var datetime = Python.import_module("datetime") + + # Create logs directory if it doesn't exist + var log_dir = "./logs" + if not os.path.exists(log_dir): + os.makedirs(log_dir) + + # Generate a timestamp for the filename + var timestamp = String(datetime.datetime.now().isoformat()) + var log_filename = log_dir + "/benchmark_bigint_floor_divide_" + timestamp + ".log" + + print("Saving benchmark results to:", log_filename) + return python.open(log_filename, "w") + + +fn log_print(msg: String, log_file: PythonObject) raises: + """ + Prints a message to both the console and the log file. + + Args: + msg: The message to print. + log_file: The file object to write to. + """ + print(msg) + log_file.write(msg + "\n") + log_file.flush() # Ensure the message is written immediately + + +fn run_benchmark_floor_divide( + name: String, + dividend: String, + divisor: String, + iterations: Int, + log_file: PythonObject, + mut speedup_factors: List[Float64], +) raises: + """ + Run a benchmark comparing Mojo BigInt floor_divide with Python int floor division. + + Args: + name: Name of the benchmark case. + dividend: String representation of the dividend. + divisor: String representation of the divisor. + iterations: Number of iterations to run. + log_file: File object for logging results. + speedup_factors: Mojo List to store speedup factors for averaging. + """ + log_print("\nBenchmark: " + name, log_file) + log_print("Dividend: " + dividend, log_file) + log_print("Divisor: " + divisor, log_file) + + # Set up Mojo and Python values + var mojo_dividend = BigInt(dividend) + var mojo_divisor = BigInt(divisor) + var py = Python.import_module("builtins") + var py_dividend = py.int(dividend) + var py_divisor = py.int(divisor) + + # Execute the operations once to verify correctness + try: + var mojo_result = mojo_dividend // mojo_divisor + var py_result = py_dividend // py_divisor + + # Display results for verification + log_print("Mojo result: " + String(mojo_result), log_file) + log_print("Python result: " + String(py_result), log_file) + + # Benchmark Mojo implementation + var t0 = perf_counter_ns() + for _ in range(iterations): + _ = mojo_dividend // mojo_divisor + var mojo_time = (perf_counter_ns() - t0) / iterations + if mojo_time == 0: + mojo_time = 1 # Prevent division by zero + + # Benchmark Python implementation + t0 = perf_counter_ns() + for _ in range(iterations): + _ = py_dividend // py_divisor + var python_time = (perf_counter_ns() - t0) / iterations + + # Calculate speedup factor + var speedup = python_time / mojo_time + speedup_factors.append(Float64(speedup)) + + # Print results with speedup comparison + log_print( + "Mojo division: " + String(mojo_time) + " ns per iteration", + log_file, + ) + log_print( + "Python division: " + String(python_time) + " ns per iteration", + log_file, + ) + log_print("Speedup factor: " + String(speedup), log_file) + except e: + log_print("Error occurred during benchmark: " + String(e), log_file) + log_print("Skipping this benchmark case", log_file) + + +fn main() raises: + # Open log file + var log_file = open_log_file() + var datetime = Python.import_module("datetime") + + # Create a Mojo List to store speedup factors for averaging later + var speedup_factors = List[Float64]() + + # Display benchmark header with system information + log_print("=== DeciMojo BigInt Floor Division Benchmark ===", log_file) + log_print("Time: " + String(datetime.datetime.now().isoformat()), log_file) + + # Try to get system info + try: + var platform = Python.import_module("platform") + log_print( + "System: " + + String(platform.system()) + + " " + + String(platform.release()), + log_file, + ) + log_print("Processor: " + String(platform.processor()), log_file) + log_print( + "Python version: " + String(platform.python_version()), log_file + ) + except: + log_print("Could not retrieve system information", log_file) + + var iterations = 100 + + # Define benchmark cases + log_print( + "\nRunning floor division benchmarks with " + + String(iterations) + + " iterations each", + log_file, + ) + + # === BASIC TESTS === + + # Case 1: Simple division with no remainder (positive/positive) + run_benchmark_floor_divide( + "Simple division with no remainder (positive/positive)", + "100", + "10", + iterations, + log_file, + speedup_factors, + ) + + # Case 2: Division with remainder (positive/positive) + run_benchmark_floor_divide( + "Division with remainder (positive/positive)", + "10", + "3", + iterations, + log_file, + speedup_factors, + ) + + # Case 3: Division resulting in zero (positive/positive) + run_benchmark_floor_divide( + "Division resulting in zero (positive/positive)", + "5", + "10", + iterations, + log_file, + speedup_factors, + ) + + # Case 4: Division by one (positive/positive) + run_benchmark_floor_divide( + "Division by one (positive/positive)", + "12345", + "1", + iterations, + log_file, + speedup_factors, + ) + + # === SIGN COMBINATION TESTS === + + # Case 5: Negative dividend, positive divisor + run_benchmark_floor_divide( + "Negative dividend, positive divisor", + "-10", + "3", + iterations, + log_file, + speedup_factors, + ) + + # Case 6: Negative dividend with remainder, positive divisor + run_benchmark_floor_divide( + "Negative dividend with remainder, positive divisor", + "-11", + "3", + iterations, + log_file, + speedup_factors, + ) + + # Case 7: Positive dividend, negative divisor + run_benchmark_floor_divide( + "Positive dividend, negative divisor", + "10", + "-3", + iterations, + log_file, + speedup_factors, + ) + + # Case 8: Positive dividend with remainder, negative divisor + run_benchmark_floor_divide( + "Positive dividend with remainder, negative divisor", + "11", + "-3", + iterations, + log_file, + speedup_factors, + ) + + # Case 9: Negative dividend, negative divisor + run_benchmark_floor_divide( + "Negative dividend, negative divisor", + "-10", + "-3", + iterations, + log_file, + speedup_factors, + ) + + # Case 10: Negative dividend with remainder, negative divisor + run_benchmark_floor_divide( + "Negative dividend with remainder, negative divisor", + "-11", + "-3", + iterations, + log_file, + speedup_factors, + ) + + # === ZERO TESTS === + + # Case 11: Zero dividend, positive divisor + run_benchmark_floor_divide( + "Zero dividend, positive divisor", + "0", + "5", + iterations, + log_file, + speedup_factors, + ) + + # Case 12: Zero dividend, negative divisor + run_benchmark_floor_divide( + "Zero dividend, negative divisor", + "0", + "-5", + iterations, + log_file, + speedup_factors, + ) + + # === LARGE NUMBER TESTS === + + # Case 13: Large number division (positive/positive) + run_benchmark_floor_divide( + "Large number division (positive/positive)", + "9999999999", # 10 billion + "333", + iterations, + log_file, + speedup_factors, + ) + + # Case 14: Large number division (negative/positive) + run_benchmark_floor_divide( + "Large number division (negative/positive)", + "-9999999999", # -10 billion + "333", + iterations, + log_file, + speedup_factors, + ) + + # Case 15: Large number division (positive/negative) + run_benchmark_floor_divide( + "Large number division (positive/negative)", + "9999999999", # 10 billion + "-333", + iterations, + log_file, + speedup_factors, + ) + + # Case 16: Large number division (negative/negative) + run_benchmark_floor_divide( + "Large number division (negative/negative)", + "-9999999999", # -10 billion + "-333", + iterations, + log_file, + speedup_factors, + ) + + # === VERY LARGE NUMBER TESTS === + + # Case 17: Very large number division (positive/positive) + run_benchmark_floor_divide( + "Very large number division (positive/positive)", + "1" + "0" * 50, # 10^50 + "7", + iterations, + log_file, + speedup_factors, + ) + + # Case 18: Very large number division (negative/positive) + run_benchmark_floor_divide( + "Very large number division (negative/positive)", + "-" + "1" + "0" * 50, # -10^50 + "7", + iterations, + log_file, + speedup_factors, + ) + + # Case 19: Very large number division (positive/negative) + run_benchmark_floor_divide( + "Very large number division (positive/negative)", + "1" + "0" * 50, # 10^50 + "-7", + iterations, + log_file, + speedup_factors, + ) + + # Case 20: Very large number division (negative/negative) + run_benchmark_floor_divide( + "Very large number division (negative/negative)", + "-" + "1" + "0" * 50, # -10^50 + "-7", + iterations, + log_file, + speedup_factors, + ) + + # === EXACT DIVISION WITH LARGE NUMBERS === + + # Case 21: Exact large division (positive/positive) + run_benchmark_floor_divide( + "Exact large division (positive/positive)", + "1" + "0" * 30, # 10^30 + "1" + "0" * 10, # 10^10 + iterations, + log_file, + speedup_factors, + ) + + # Case 22: Exact large division (negative/positive) + run_benchmark_floor_divide( + "Exact large division (negative/positive)", + "-" + "1" + "0" * 30, # -10^30 + "1" + "0" * 10, # 10^10 + iterations, + log_file, + speedup_factors, + ) + + # Case 23: Exact large division (positive/negative) + run_benchmark_floor_divide( + "Exact large division (positive/negative)", + "1" + "0" * 30, # 10^30 + "-" + "1" + "0" * 10, # -10^10 + iterations, + log_file, + speedup_factors, + ) + + # Case 24: Exact large division (negative/negative) + run_benchmark_floor_divide( + "Exact large division (negative/negative)", + "-" + "1" + "0" * 30, # -10^30 + "-" + "1" + "0" * 10, # -10^10 + iterations, + log_file, + speedup_factors, + ) + + # === SPECIAL CASES === + + # Case 25: Small dividend, very large divisor + run_benchmark_floor_divide( + "Small dividend, very large divisor", + "12345", + "9" * 20, # 20 nines + iterations, + log_file, + speedup_factors, + ) + + # Case 26: Small negative dividend, very large divisor + run_benchmark_floor_divide( + "Small negative dividend, very large divisor", + "-12345", + "9" * 20, # 20 nines + iterations, + log_file, + speedup_factors, + ) + + # Case 27: Fibonacci number division + run_benchmark_floor_divide( + "Fibonacci number division", + "6765", # Fib(20) + "4181", # Fib(19) + iterations, + log_file, + speedup_factors, + ) + + # Case 28: Prime number division + run_benchmark_floor_divide( + "Prime number division", + "2147483647", # Mersenne prime (2^31 - 1) + "997", # Prime + iterations, + log_file, + speedup_factors, + ) + + # Case 29: Division near Int64 limit + run_benchmark_floor_divide( + "Division near Int64 limit", + "9223372036854775807", # Int64.MAX + "2", + iterations, + log_file, + speedup_factors, + ) + + # Case 30: Division near negative Int64 limit + run_benchmark_floor_divide( + "Division near negative Int64 limit", + "-9223372036854775807", # Near Int64.MIN + "2", + iterations, + log_file, + speedup_factors, + ) + + # === CASES WITH SPECIFIC REMAINDERS (FLOOR VS TRUNCATE DIFFERENCES) === + + # Case 31: Division where floor differs from truncate (negative/positive) + run_benchmark_floor_divide( + "Division where floor differs from truncate (negative/positive)", + "-10", + "3", # -10 ÷ 3 = -3.33... => -4 (floor) vs -3 (truncate) + iterations, + log_file, + speedup_factors, + ) + + # Case 32: Division where floor differs from truncate (positive/negative) + run_benchmark_floor_divide( + "Division where floor differs from truncate (positive/negative)", + "10", + "-3", # 10 ÷ -3 = -3.33... => -4 (floor) vs -3 (truncate) + iterations, + log_file, + speedup_factors, + ) + + # Case 33: Large division where floor differs from truncate + run_benchmark_floor_divide( + "Large division where floor differs from truncate", + "-" + "9" * 50, + "3", + iterations, + log_file, + speedup_factors, + ) + + # === BOUNDARY CASES === + + # Case 34: Division with divisor just below dividend + run_benchmark_floor_divide( + "Division with divisor just below dividend", + "1000", + "999", + iterations, + log_file, + speedup_factors, + ) + + # Case 35: Division with negative divisor just below negative dividend + run_benchmark_floor_divide( + "Division with negative divisor just below negative dividend", + "-1000", + "-999", + iterations, + log_file, + speedup_factors, + ) + + # Case 36: Division where dividend is one less than divisor multiple + run_benchmark_floor_divide( + "Division where dividend is one less than divisor multiple", + "11", # 11 = 3*4 - 1 + "3", # 11 ÷ 3 = 3.67 => 3 (floor) + iterations, + log_file, + speedup_factors, + ) + + # Case 37: Division where negative dividend is one more than divisor multiple + run_benchmark_floor_divide( + "Division where negative dividend is one more than divisor multiple", + "-11", # -11 = -3*4 + 1 + "3", # -11 ÷ 3 = -3.67 => -4 (floor) + iterations, + log_file, + speedup_factors, + ) + + # === POWERS AND PATTERNS === + + # Case 38: Division with exact powers of 10 + run_benchmark_floor_divide( + "Division with exact powers of 10", + "1" + "0" * 20, # 10^20 + "1" + "0" * 5, # 10^5 + iterations, + log_file, + speedup_factors, + ) + + # Case 39: Division of repeated digits (positive) + run_benchmark_floor_divide( + "Division of repeated digits (positive)", + "9" * 30, # 30 nines + "9" * 15, # 15 nines + iterations, + log_file, + speedup_factors, + ) + + # Case 40: Division of repeated digits (negative dividend) + run_benchmark_floor_divide( + "Division of repeated digits (negative dividend)", + "-" + "9" * 30, # -30 nines + "9" * 15, # 15 nines + iterations, + log_file, + speedup_factors, + ) + + # Case 41: Division with extremely large dividend and small divisor + run_benchmark_floor_divide( + "Extreme large dividend and small divisor", + "9" * 100, # 100 nines + "3", + iterations, + log_file, + speedup_factors, + ) + + # Case 42: Division with extremely large negative dividend and small divisor + run_benchmark_floor_divide( + ( + "Extreme large negative dividend and small divisor (100 digits vs 1" + " digit)" + ), + "-" + "9" * 100, # -100 nines + "3", + iterations, + log_file, + speedup_factors, + ) + + # Case 43: Division with powers of 2 + run_benchmark_floor_divide( + "Division with powers of 2", + "1" + "0" * 50, # 10^50 + "256", # 2^8 + iterations, + log_file, + speedup_factors, + ) + + # === MORE COMPLEX CASES === + + # Case 44: Division yielding a single-digit result + run_benchmark_floor_divide( + "Division yielding a single-digit result", + "123456789", + "123456780", + iterations, + log_file, + speedup_factors, + ) + + # Case 45: Division with random large numbers + run_benchmark_floor_divide( + "Division with random large numbers (52 digits vs 20 digits)", + "8675309123456789098765432112345678909876543211234567", + "12345678901234567890", + iterations, + log_file, + speedup_factors, + ) + + # Case 46: Division of very large numbers with different signs + run_benchmark_floor_divide( + ( + "Division of very large numbers with different signs (52 digits vs" + " 20 digits)" + ), + "-8675309123456789098765432112345678909876543211234567", + "12345678901234567890", + iterations, + log_file, + speedup_factors, + ) + + # Case 47: Division with around 50 digits and divisor just below dividend + run_benchmark_floor_divide( + ( + "Division with around 50 digits divisor just below dividend (50" + " digits vs 48 digits)" + ), + "12345" * 10, + "6789" * 12, + iterations, + log_file, + speedup_factors, + ) + + # Case 48: Division of very large repeated digits + run_benchmark_floor_divide( + "Division of very large repeated digits (300 digits vs 200 digits)", + "990132857498314692374162398217" * 10, # 30 * 10 = 300 digits + "85172390413429847239" * 10, # 20 * 10 = 200 digits + iterations, + log_file, + speedup_factors, + ) + + # Case 49: Division of large numbers with different signs + run_benchmark_floor_divide( + ( + "Division of large numbers with different signs (270 digits vs 135" + " digits)" + ), + "-" + "123456789" * 30, + "987654321" * 15, + iterations, + log_file, + speedup_factors, + ) + + # Case 50: Division of very large numbers with different signs + run_benchmark_floor_divide( + ( + "Division of very large numbers with different signs (2250 digits" + " vs 900 digits)" + ), + "123456789" * 250, + "-" + "987654321" * 100, + iterations, + log_file, + speedup_factors, + ) + + # Case 51: Division with 500-digit dividend and 200-digit divisor (positive/positive) + run_benchmark_floor_divide( + ( + "Division with 500-digit dividend and 200-digit divisor" + " (positive/positive)" + ), + "1" + "7" * 499, # 500 digits + "9" + "8" * 199, # 200 digits + iterations, + log_file, + speedup_factors, + ) + + # Case 52: Division with 500-digit dividend and 200-digit divisor (negative/positive) + run_benchmark_floor_divide( + ( + "Division with 500-digit dividend and 200-digit divisor" + " (negative/positive)" + ), + "-" + "1" + "7" * 499, # 500 digits + "9" + "8" * 199, # 200 digits + iterations, + log_file, + speedup_factors, + ) + + # Case 53: Division with 500-digit dividend and 200-digit divisor (positive/negative) + run_benchmark_floor_divide( + ( + "Division with 500-digit dividend and 200-digit divisor" + " (positive/negative)" + ), + "1" + "7" * 499, # 500 digits + "-" + "9" + "8" * 199, # 200 digits + iterations, + log_file, + speedup_factors, + ) + + # Case 54: Division with 500-digit dividend and 200-digit divisor (negative/negative) + run_benchmark_floor_divide( + ( + "Division with 500-digit dividend and 200-digit divisor" + " (negative/negative)" + ), + "-" + "1" + "7" * 499, # 500 digits + "-" + "9" + "8" * 199, # 200 digits + iterations, + log_file, + speedup_factors, + ) + + # Case 55: Division with alternating pattern (600 digits / 300 digits) + run_benchmark_floor_divide( + "Division with alternating pattern (600 digits / 300 digits)", + "1010101010" * 60, # Alternating 1s and 0s, 600 digits + "9090909090" * 30, # Alternating 9s and 0s, 300 digits + iterations, + log_file, + speedup_factors, + ) + + # Case 56: Division with repeating pattern of primes (700 digits / 350 digits) + run_benchmark_floor_divide( + "Division with repeating pattern of primes (700 digits / 350 digits)", + "2357111317" * 70, # Primes pattern, 700 digits + "1931374143" * 35, # Another primes pattern, 350 digits + iterations, + log_file, + speedup_factors, + ) + + # Case 57: Division with random-like 800-digit number by 400-digit number (negative) + run_benchmark_floor_divide( + "Division with random-like 800-digit by 400-digit number (negative)", + ( # 800 digits, π & e digits + "-314159265358979323846264338327950288419716939937510" + "582097494459230781640628620899862803482534211706798214808651" + "328230664709384460955058223172535940812848111745028410270193" + "852110555964462294895493038196442881097566593344612847564823" + "378678316527120190914564856692346034861045432664821339360726" + "024914127372458700660631558817488152092096282925409171536436" + "789259036001133053054882046652138414695194151160943305727036" + "575959195309218611738193261179310511854807446237996274956735" + "188575272489122793818301194912983367336244065664308602139494" + "639522473719070217986094370277053921717629317675238467481846" + "766940513200056812714526356082778577134275778960917363717872" + "146844090122495343014654958537105079227968925892354201995611" + ), + ( # 400 digits, e digits + "271828182845904523536028747135266249775724709369995" + "95749669676277240766303535475945713821785251664274" + "27466391932003059921817413596629043572900334295260" + "59563073813232862794349076323382988075319525101901" + "15738341879307021540891499348841675092447614606680" + "82264800168477411853742345442437107539077744992069" + "55170276183860626133138458300075204493382656029760" + "67371132007093287091274437470472306969772093101416" + "92836819025515108657463772111252389784425056953696" + "77078544996996794686445490598793163688923009879312" + "77361782154249992295763514822082698951936680331825" + "28869398496465105820939239829488793320362509443117" + ), + iterations, + log_file, + speedup_factors, + ) + + # Case 58: Division with 1000-digit dividend near power of 10 and 300-digit divisor + run_benchmark_floor_divide( + ( + "Division with 1000-digit dividend near power of 10 and 300-digit" + " divisor" + ), + "9" * 999 + "1", # 10^1000 - 9...9 + 1 (1000 digits) + "3" * 300, # 300 threes + iterations, + log_file, + speedup_factors, + ) + + # Case 59: Division with 1200-digit by 400-digit (exact division, power of 2) + run_benchmark_floor_divide( + "Division with 1200-digit by 400-digit (exact division, power of 2)", + "2" * 1200, # 1200 twos + "2" * 400, # 400 twos + iterations, + log_file, + speedup_factors, + ) + + # Case 60: Division with extremely large 1500-digit number by large 500-digit number + run_benchmark_floor_divide( + "Division with extremely large 1500-digit by large 500-digit number", + "7" + "3" * 1499, # 1500 digits + "5" + "7" * 499, # 500 digits + iterations, + log_file, + speedup_factors, + ) + + # Calculate average speedup factor (ignoring any cases that might have failed) + if len(speedup_factors) > 0: + var sum_speedup: Float64 = 0.0 + for i in range(len(speedup_factors)): + sum_speedup += speedup_factors[i] + var average_speedup = sum_speedup / Float64(len(speedup_factors)) + + # Display summary + log_print("\n=== BigInt Floor Division Benchmark Summary ===", log_file) + log_print( + "Benchmarked: " + + String(len(speedup_factors)) + + " different division cases", + log_file, + ) + log_print( + "Each case ran: " + String(iterations) + " iterations", log_file + ) + log_print( + "Average speedup: " + String(average_speedup) + "×", log_file + ) + + # List all speedup factors + log_print("\nIndividual speedup factors:", log_file) + for i in range(len(speedup_factors)): + log_print( + String("Case {}: {}×").format( + i + 1, round(speedup_factors[i], 2) + ), + log_file, + ) + else: + log_print("\nNo valid benchmark cases were completed", log_file) + + # Close the log file + log_file.close() + print("Benchmark completed. Log file closed.") diff --git a/benches/bigint/bench_bigint_multiply.mojo b/benches/bigint/bench_bigint_multiply.mojo deleted file mode 100644 index e69de29b..00000000 diff --git a/benches/biguint/bench.mojo b/benches/biguint/bench.mojo index 38e276a6..7dbe64d4 100644 --- a/benches/biguint/bench.mojo +++ b/benches/biguint/bench.mojo @@ -4,6 +4,32 @@ from bench_biguint_truncate_divide import main as bench_truncate_divide fn main() raises: - bench_add() - bench_multiply() - bench_truncate_divide() + print( + """ +========================================= +This is the BigUInt Benchmarks +========================================= +1. add: Add +2. multiply: Multiply +2. truncdiv: Truncate Divide +4. all: Run all benchmarks +5. quit: Exit +========================================= +""" + ) + var command = input("Type the bench you want to run:") + if command == "add": + bench_add() + elif command == "multiply": + bench_multiply() + elif command == "truncdiv": + bench_truncate_divide() + elif command == "all": + bench_add() + bench_multiply() + bench_truncate_divide() + elif command == "quit": + return + else: + print("Invalid input") + main() diff --git a/src/decimojo/bigint/arithmetics.mojo b/src/decimojo/bigint/arithmetics.mojo index 98feb687..af0180e4 100644 --- a/src/decimojo/bigint/arithmetics.mojo +++ b/src/decimojo/bigint/arithmetics.mojo @@ -275,8 +275,38 @@ fn multiply(x1: BigInt, x2: BigInt) raises -> BigInt: return result^ +fn floor_divide(x1: BigInt, x2: BigInt) raises -> BigInt: + """Returns the quotient of two numbers, rounding toward negative infinity. + The modulo has the same sign as the divisor and satisfies: + x1 = floor_divide(x1, x2) * x2 + floor_divide(x1, x2). + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The quotient of x1 // x2, rounded toward negative infinity. + """ + + if x2.is_zero(): + raise Error("Error in `floor_divide`: Division by zero") + + if x1.is_zero(): + return BigInt() + + if x1.sign == x2.sign: + # Use floor (truncate) division between magnitudes + return BigInt(x1.magnitude.floor_divide(x2.magnitude), sign=False) + + else: + # Use ceil division of the magnitudes + return BigInt(x1.magnitude.ceil_divide(x2.magnitude), sign=True) + + fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: """Returns the quotient of two BigInt numbers, truncating toward zero. + The modulo has the same sign as the divisor and satisfies: + x1 = truncate_divide(x1, x2) * x2 + truncate_modulo(x1, x2). Args: x1: The dividend. @@ -517,8 +547,33 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: return result +fn floor_modulo(x1: BigInt, x2: BigInt) raises -> BigInt: + """Returns the remainder of two numbers, truncating toward negative infinity. + The remainder has the same sign as the divisor and satisfies: + x1 = floor_divide(x1, x2) * x2 + floor_modulo(x1, x2). + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The remainder of x1 being divided by x2, with the same sign as x2. + """ + + if x2.is_zero(): + raise Error("Error in `floor_modulo`: Division by zero") + + if x1.sign == x2.sign: + # Use floor (truncate) division between magnitudes + return BigInt(x1.magnitude.floor_modulo(x2.magnitude), sign=x2.sign) + + else: + # Use ceil division of the magnitudes + return BigInt(x1.magnitude.ceil_modulo(x2.magnitude), sign=x2.sign) + + fn truncate_modulo(x1: BigInt, x2: BigInt) raises -> BigInt: - """Returns the remainder of two BigInt numbers, truncating toward zero. + """Returns the remainder of two numbers, truncating toward zero. The remainder has the same sign as the dividend and satisfies: x1 = truncate_divide(x1, x2) * x2 + truncate_modulo(x1, x2). diff --git a/src/decimojo/bigint/bigint.mojo b/src/decimojo/bigint/bigint.mojo index 17171fe1..bce29b97 100644 --- a/src/decimojo/bigint/bigint.mojo +++ b/src/decimojo/bigint/bigint.mojo @@ -98,6 +98,17 @@ struct BigInt(Absable, IntableRaising, Writable): self.magnitude = BigUInt(empty=empty, capacity=capacity) self.sign = sign + fn __init__(out self, magnitude: BigUInt, sign: Bool): + """Initializes a BigInt from a BigUInt and a sign. + + Args: + magnitude: The magnitude of the BigInt. + sign: The sign of the BigInt. + """ + + self.magnitude = magnitude + self.sign = sign + fn __init__(out self, owned *words: UInt32, sign: Bool) raises: """Initializes a BigInt from raw components. See `from_words()` for safer initialization. @@ -388,6 +399,14 @@ struct BigInt(Absable, IntableRaising, Writable): fn __mul__(self, other: Self) raises -> Self: return decimojo.bigint.arithmetics.multiply(self, other) + @always_inline + fn __floordiv__(self, other: Self) raises -> Self: + return decimojo.bigint.arithmetics.floor_divide(self, other) + + @always_inline + fn __mod__(self, other: Self) raises -> Self: + return decimojo.bigint.arithmetics.floor_modulo(self, other) + # ===------------------------------------------------------------------=== # # Basic binary augmented arithmetic assignments dunders # These methods are called to implement the binary augmented arithmetic @@ -407,6 +426,14 @@ struct BigInt(Absable, IntableRaising, Writable): fn __imul__(mut self, other: Self) raises: self = decimojo.bigint.arithmetics.multiply(self, other) + @always_inline + fn __ifloordiv__(mut self, other: Self) raises: + self = decimojo.bigint.arithmetics.floor_divide(self, other) + + @always_inline + fn __imod__(mut self, other: Self) raises: + self = decimojo.bigint.arithmetics.floor_modulo(self, other) + # ===------------------------------------------------------------------=== # # Mathematical methods that do not implement a trait (not a dunder) # ===------------------------------------------------------------------=== # @@ -418,6 +445,13 @@ struct BigInt(Absable, IntableRaising, Writable): """ return decimojo.bigint.comparison.compare_absolute(self, other) + @always_inline + fn floor_divide(self, other: Self) raises -> Self: + """Performs a floor division of two BigInts. + See `floor_divide()` for more information. + """ + return decimojo.bigint.arithmetics.floor_divide(self, other) + @always_inline fn truncate_divide(self, other: Self) raises -> Self: """Performs a truncated division of two BigInts. @@ -425,6 +459,13 @@ struct BigInt(Absable, IntableRaising, Writable): """ return decimojo.bigint.arithmetics.truncate_divide(self, other) + @always_inline + fn floor_modulo(self, other: Self) raises -> Self: + """Performs a floor modulo of two BigInts. + See `floor_modulo()` for more information. + """ + return decimojo.bigint.arithmetics.floor_modulo(self, other) + @always_inline fn truncate_modulo(self, other: Self) raises -> Self: """Performs a truncated modulo of two BigInts. diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index bb681de3..06c0db19 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -583,10 +583,10 @@ fn ceil_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return quotient^ -fn modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: +fn floor_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: """Returns the remainder of two BigUInt numbers, truncating toward zero. The remainder has the same sign as the dividend and satisfies: - x1 = floor_divide(x1, x2) * x2 + modulo(x1, x2). + x1 = floor_divide(x1, x2) * x2 + floor_modulo(x1, x2). Args: x1: The dividend. @@ -626,6 +626,14 @@ fn modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return remainder^ +fn truncate_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: + """Returns the remainder of two BigUInt numbers, truncating toward zero. + It is equal to floored modulo for unsigned numbers. + See `floor_modulo` for more details. + """ + return floor_modulo(x1, x2) + + fn ceil_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: """Returns the remainder of two BigUInt numbers, rounding up. The remainder has the same sign as the dividend and satisfies: @@ -696,37 +704,44 @@ fn multiply_toom_cook_3(x1: BigUInt, x2: BigUInt) raises -> BigUInt: if x2.is_one(): return x1 - # Basic multiplication is faster for small numbers - if len(x1.words) < 10 or len(x2.words) < 10: - return multiply(x1, x2) + # # Basic multiplication is faster for small numbers + # if len(x1.words) < 10 or len(x2.words) < 10: + # return multiply(x1, x2) # Determine size for splitting var max_len = max(len(x1.words), len(x2.words)) var k = (max_len + 2) // 3 # Split into thirds # Split the numbers into three parts each: a = a₂·β² + a₁·β + a₀ - var a0 = BigUInt(empty=True) - var a1 = BigUInt(empty=True) - var a2 = BigUInt(empty=True) - var b0 = BigUInt(empty=True) - var b1 = BigUInt(empty=True) - var b2 = BigUInt(empty=True) + var a0_words = List[UInt32]() + var a1_words = List[UInt32]() + var a2_words = List[UInt32]() + var b0_words = List[UInt32]() + var b1_words = List[UInt32]() + var b2_words = List[UInt32]() # Extract parts from x1 for i in range(min(k, len(x1.words))): - a0.words.append(x1.words[i]) + a0_words.append(x1.words[i]) for i in range(k, min(2 * k, len(x1.words))): - a1.words.append(x1.words[i]) + a1_words.append(x1.words[i]) for i in range(2 * k, len(x1.words)): - a2.words.append(x1.words[i]) + a2_words.append(x1.words[i]) # Extract parts from x2 for i in range(min(k, len(x2.words))): - b0.words.append(x2.words[i]) + b0_words.append(x2.words[i]) for i in range(k, min(2 * k, len(x2.words))): - b1.words.append(x2.words[i]) + b1_words.append(x2.words[i]) for i in range(2 * k, len(x2.words)): - b2.words.append(x2.words[i]) + b2_words.append(x2.words[i]) + + a0 = BigUInt(a0_words) + a1 = BigUInt(a1_words) + a2 = BigUInt(a2_words) + b0 = BigUInt(b0_words) + b1 = BigUInt(b1_words) + b2 = BigUInt(b2_words) # Remove trailing zeros a0.remove_trailing_zeros() @@ -736,17 +751,22 @@ fn multiply_toom_cook_3(x1: BigUInt, x2: BigUInt) raises -> BigUInt: b1.remove_trailing_zeros() b2.remove_trailing_zeros() + print("DEBUG: a0 =", a0) + print("DEBUG: a1 =", a1) + print("DEBUG: a2 =", a2) + print("DEBUG: b0 =", b0) + print("DEBUG: b1 =", b1) + print("DEBUG: b2 =", b2) + # Evaluate at points 0, 1, -1, 2, ∞ # p₀ = a₀ var p0_a = a0 # p₁ = a₀ + a₁ + a₂ - var p1_a = add(add(a0, a1), a2) + var p1_a = a0 + a1 + a2 # p₂ = a₀ - a₁ + a₂ - var p2_a = add(subtract(a0, a1), a2) + var p2_a = a0 + a2 - a1 # p₃ = a₀ + 2a₁ + 4a₂ - var a1_times2 = add(a1, a1) - var a2_times4 = add(add(a2, a2), add(a2, a2)) - var p3_a = add(add(a0, a1_times2), a2_times4) + var p3_a = a0 + a1 * BigUInt(UInt32(2)) + a2 * BigUInt(UInt32(4)) # p₄ = a₂ var p4_a = a2 @@ -775,40 +795,35 @@ fn multiply_toom_cook_3(x1: BigUInt, x2: BigUInt) raises -> BigUInt: # TODO: The subtraction can be underflowed. Use signed integers for the subtraction # c₃ = (r₃ - r₁)/3 - (r₄ - r₂)/2 + r₄·5/6 - var t1 = subtract(r3, r1) - for _ in range(2): # Division by 3 (approximation: divide by 6 and double) - floor_divide_inplace_by_2(t1) - var t2 = subtract(r4, r2) - floor_divide_inplace_by_2(t2) - var t3 = add(add(r4, r4), r4) # 3*r4 - floor_divide_inplace_by_2(t3) # 3*r4/2 - var c3 = add(subtract(t1, t2), t3) + var t1 = (r3 - r1) // BigUInt(UInt32(3)) + var t2 = (r4 - r2) // BigUInt(UInt32(2)) + var t3 = r4 * BigUInt(UInt32(5)) // BigUInt(UInt32(6)) + var c3 = t1 + t3 - t2 # c₂ = (r₂ - r₀)/2 - r₄ - var c2 = subtract(subtract(r2, r0), add(r4, r4)) - floor_divide_inplace_by_2(c2) + var c2 = (r2 - r0) // BigUInt(UInt32(2)) - r4 # c₁ = r₁ - r₀ - c₃ - c₄ - c₂ - var c1 = subtract(subtract(r1, r0), add(add(c2, c3), c4)) + var c1 = r1 - r0 - c3 - c4 - c2 # Combine the coefficients to get the result var result = c0 # c₁ * β var c1_shifted = shift_words_left(c1, k) - result = add(result, c1_shifted) + result = result + c1_shifted # c₂ * β² var c2_shifted = shift_words_left(c2, 2 * k) - result = add(result, c2_shifted) + result = result + c2_shifted # c₃ * β³ var c3_shifted = shift_words_left(c3, 3 * k) - result = add(result, c3_shifted) + result = result + c3_shifted # c₄ * β⁴ var c4_shifted = shift_words_left(c4, 4 * k) - result = add(result, c4_shifted) + result = result + c4_shifted return result diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index e3534058..23f11ce4 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -76,6 +76,8 @@ struct BigUInt(Absable, IntableRaising, Writable): """Initializes a BigUInt with value 0.""" self.words = List[UInt32](UInt32(0)) + # FIXME: This is a temporary solution + # A unitialized BigUInt is not a good idea fn __init__(out self, empty: Bool): """Initializes an empty BigUInt. @@ -101,6 +103,23 @@ struct BigUInt(Absable, IntableRaising, Writable): if not empty: self.words.append(UInt32(0)) + fn __init__(out self, owned words: List[UInt32]): + """Initializes a BigUInt from a list of UInt32 words. + + Args: + words: A list of 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. + + Notes: + + This method does not check whether the words are smaller than + `999_999_999`. + """ + if len(words) == 0: + words.append(UInt32(0)) + self.words = words^ + fn __init__(out self, owned *words: UInt32): """Initializes a BigUInt from raw words without validating the words. See `from_words()` for safer initialization. @@ -164,7 +183,7 @@ struct BigUInt(Absable, IntableRaising, Writable): End of examples. """ - result = Self(empty=True, capacity=len(words)) + var list_of_words = List[UInt32](capacity=len(words)) # Check if the words are valid for word in words: @@ -174,9 +193,9 @@ struct BigUInt(Absable, IntableRaising, Writable): " value of 999_999_999" ) else: - result.words.append(word) + list_of_words.append(word) - return result^ + return Self(list_of_words^) @staticmethod fn from_int(value: Int) raises -> Self: @@ -187,17 +206,17 @@ struct BigUInt(Absable, IntableRaising, Writable): if value < 0: raise Error("Error in `from_int()`: The value is negative") - var result = Self(empty=True) + var list_of_words = List[UInt32]() var remainder: Int = value var quotient: Int while remainder != 0: quotient = remainder // 1_000_000_000 remainder = remainder % 1_000_000_000 - result.words.append(UInt32(remainder)) + list_of_words.append(UInt32(remainder)) remainder = quotient - return result^ + return Self(list_of_words^) @staticmethod fn from_uint64(value: UInt64) raises -> Self: @@ -212,16 +231,16 @@ struct BigUInt(Absable, IntableRaising, Writable): if value == 0: return Self() - var result = Self(empty=True) + var list_of_words = List[UInt32]() var remainder: UInt64 = value var quotient: UInt64 while remainder != 0: quotient = remainder // 1_000_000_000 remainder = remainder % 1_000_000_000 - result.words.append(UInt32(remainder)) + list_of_words.append(UInt32(remainder)) remainder = quotient - return result^ + return Self(list_of_words^) @staticmethod fn from_uint128(value: UInt128) -> Self: @@ -236,16 +255,16 @@ struct BigUInt(Absable, IntableRaising, Writable): if value == 0: return Self() - var result = Self(empty=True) + var list_of_words = List[UInt32]() var remainder: UInt128 = value var quotient: UInt128 while remainder != 0: quotient = remainder // 1_000_000_000 remainder = remainder % 1_000_000_000 - result.words.append(UInt32(remainder)) + list_of_words.append(UInt32(remainder)) remainder = quotient - return result^ + return Self(list_of_words^) @staticmethod fn from_uint256(value: UInt256) -> Self: @@ -260,16 +279,16 @@ struct BigUInt(Absable, IntableRaising, Writable): if value == 0: return Self() - var result = Self(empty=True) + var list_of_words = List[UInt32]() var remainder: UInt256 = value var quotient: UInt256 while remainder != 0: quotient = remainder // 1_000_000_000 remainder = remainder % 1_000_000_000 - result.words.append(UInt32(remainder)) + list_of_words.append(UInt32(remainder)) remainder = quotient - return result^ + return Self(list_of_words^) @staticmethod fn from_string(value: String, ignore_sign: Bool = False) raises -> BigUInt: @@ -552,7 +571,7 @@ struct BigUInt(Absable, IntableRaising, Writable): @always_inline fn __mod__(self, other: Self) raises -> Self: - return decimojo.biguint.arithmetics.modulo(self, other) + return decimojo.biguint.arithmetics.floor_modulo(self, other) # ===------------------------------------------------------------------=== # # Basic binary augmented arithmetic assignments dunders @@ -579,7 +598,7 @@ struct BigUInt(Absable, IntableRaising, Writable): @always_inline fn __imod__(mut self, other: Self) raises: - self = decimojo.biguint.arithmetics.modulo(self, other) + self = decimojo.biguint.arithmetics.floor_modulo(self, other) @always_inline fn __pow__(self, exponent: Self) raises -> Self: @@ -635,13 +654,6 @@ struct BigUInt(Absable, IntableRaising, Writable): """ return decimojo.biguint.comparison.compare(self, other) - @always_inline - fn ceil_divide(self, other: Self) raises -> Self: - """Returns the result of ceil dividing this number by `other`. - See `ceil_divide()` for more information. - """ - return decimojo.biguint.arithmetics.ceil_divide(self, other) - @always_inline fn floor_divide(self, other: Self) raises -> Self: """Returns the result of floor dividing this number by `other`. @@ -658,6 +670,27 @@ struct BigUInt(Absable, IntableRaising, Writable): """ return decimojo.biguint.arithmetics.truncate_divide(self, other) + @always_inline + fn ceil_divide(self, other: Self) raises -> Self: + """Returns the result of ceil dividing this number by `other`. + See `ceil_divide()` for more information. + """ + return decimojo.biguint.arithmetics.ceil_divide(self, other) + + @always_inline + fn floor_modulo(self, other: Self) raises -> Self: + """Returns the result of floor modulo this number by `other`. + See `floor_modulo()` for more information. + """ + return decimojo.biguint.arithmetics.floor_modulo(self, other) + + @always_inline + fn truncate_modulo(self, other: Self) raises -> Self: + """Returns the result of truncate modulo this number by `other`. + See `truncate_modulo()` for more information. + """ + return decimojo.biguint.arithmetics.truncate_modulo(self, other) + @always_inline fn ceil_modulo(self, other: Self) raises -> Self: """Returns the result of ceil modulo this number by `other`. diff --git a/tests/bigint/test_bigint_floor_divide.mojo b/tests/bigint/test_bigint_floor_divide.mojo new file mode 100644 index 00000000..56663751 --- /dev/null +++ b/tests/bigint/test_bigint_floor_divide.mojo @@ -0,0 +1,954 @@ +""" +Comprehensive tests for the floor_divide operation of the BigInt type. +BigInt is a signed integer type, so these tests focus on division with both positive and negative numbers. +Tests also compare results with Python's built-in int type for verification. +""" + +import testing +from decimojo.bigint.bigint import BigInt +import decimojo.bigint.arithmetics +from python import Python, PythonObject + + +fn test_basic_floor_division_positive() raises: + """Test basic floor division cases with positive numbers.""" + print("Testing basic floor division with positive numbers...") + + # Get Python's built-in int module + var py = Python.import_module("builtins") + + # Test case 1: Simple division with no remainder + var a1 = BigInt("10") + var b1 = BigInt("2") + var result1 = a1 // b1 + var py_result1 = py.int("10") // py.int("2") + testing.assert_equal( + String(result1), "5", "10 // 2 should equal 5, got " + String(result1) + ) + testing.assert_equal( + String(result1), + String(py_result1), + "Result doesn't match Python's int result", + ) + + # Test case 2: Division with remainder (floor towards negative infinity) + var a2 = BigInt("10") + var b2 = BigInt("3") + var result2 = a2 // b2 + var py_result2 = py.int("10") // py.int("3") + testing.assert_equal( + String(result2), "3", "10 // 3 should equal 3, got " + String(result2) + ) + testing.assert_equal( + String(result2), + String(py_result2), + "Result doesn't match Python's int result", + ) + + # Test case 3: Division results in zero (smaller // larger) + var a3 = BigInt("3") + var b3 = BigInt("10") + var result3 = a3 // b3 + var py_result3 = py.int("3") // py.int("10") + testing.assert_equal( + String(result3), "0", "3 // 10 should equal 0, got " + String(result3) + ) + testing.assert_equal( + String(result3), + String(py_result3), + "Result doesn't match Python's int result", + ) + + # Test case 4: Division by 1 + var a4 = BigInt("42") + var b4 = BigInt("1") + var result4 = a4 // b4 + var py_result4 = py.int("42") // py.int("1") + testing.assert_equal( + String(result4), "42", "42 // 1 should equal 42, got " + String(result4) + ) + testing.assert_equal( + String(result4), + String(py_result4), + "Result doesn't match Python's int result", + ) + + # Test case 5: Large number division + var a5 = BigInt("1000000000000") + var b5 = BigInt("1000000") + var result5 = a5 // b5 + var py_result5 = py.int("1000000000000") // py.int("1000000") + testing.assert_equal( + String(result5), + "1000000", + "1000000000000 // 1000000 should equal 1000000, got " + String(result5), + ) + testing.assert_equal( + String(result5), + String(py_result5), + "Result doesn't match Python's int result", + ) + + print("✓ Basic floor division with positive numbers tests passed!") + + +fn test_basic_floor_division_negative() raises: + """Test basic floor division cases with negative numbers.""" + print("Testing basic floor division with negative numbers...") + + # Get Python's built-in int module + var py = Python.import_module("builtins") + + # Test case 1: Negative dividend, positive divisor + var a1 = BigInt("-10") + var b1 = BigInt("2") + var result1 = a1 // b1 + var py_result1 = py.int("-10") // py.int("2") + testing.assert_equal( + String(result1), + "-5", + "-10 // 2 should equal -5, got " + String(result1), + ) + testing.assert_equal( + String(result1), + String(py_result1), + "Result doesn't match Python's int result", + ) + + # Test case 2: Negative dividend, negative divisor + var a2 = BigInt("-10") + var b2 = BigInt("-2") + var result2 = a2 // b2 + var py_result2 = py.int("-10") // py.int("-2") + testing.assert_equal( + String(result2), "5", "-10 // -2 should equal 5, got " + String(result2) + ) + testing.assert_equal( + String(result2), + String(py_result2), + "Result doesn't match Python's int result", + ) + + # Test case 3: Positive dividend, negative divisor + var a3 = BigInt("10") + var b3 = BigInt("-2") + var result3 = a3 // b3 + var py_result3 = py.int("10") // py.int("-2") + testing.assert_equal( + String(result3), + "-5", + "10 // -2 should equal -5, got " + String(result3), + ) + testing.assert_equal( + String(result3), + String(py_result3), + "Result doesn't match Python's int result", + ) + + # Test case 4: Negative dividend with remainder (floor division special case) + var a4 = BigInt("-7") + var b4 = BigInt("3") + var result4 = a4 // b4 + var py_result4 = py.int("-7") // py.int("3") + testing.assert_equal( + String(result4), "-3", "-7 // 3 should equal -3, got " + String(result4) + ) + testing.assert_equal( + String(result4), + String(py_result4), + "Result doesn't match Python's int result", + ) + + # Test case 5: Key test for floor division (negative numbers) + var a5 = BigInt("-5") + var b5 = BigInt("2") + var result5 = a5 // b5 + var py_result5 = py.int("-5") // py.int("2") + testing.assert_equal( + String(result5), "-3", "-5 // 2 should equal -3, got " + String(result5) + ) + testing.assert_equal( + String(result5), + String(py_result5), + "Result doesn't match Python's int result", + ) + + print("✓ Basic floor division with negative numbers tests passed!") + + +fn test_mixed_sign_floor_division() raises: + """Test floor division cases with mixed signs.""" + print("Testing floor division with mixed signs...") + + # Get Python's built-in int module + var py = Python.import_module("builtins") + + # Test case 1: Negative // positive with exact division + var a1 = BigInt("-6") + var b1 = BigInt("3") + var result1 = a1 // b1 + var py_result1 = py.int("-6") // py.int("3") + testing.assert_equal( + String(result1), "-2", "-6 // 3 should equal -2, got " + String(result1) + ) + testing.assert_equal( + String(result1), + String(py_result1), + "Result doesn't match Python's int result", + ) + + # Test case 2: Negative // negative with exact division + var a2 = BigInt("-6") + var b2 = BigInt("-3") + var result2 = a2 // b2 + var py_result2 = py.int("-6") // py.int("-3") + testing.assert_equal( + String(result2), "2", "-6 // -3 should equal 2, got " + String(result2) + ) + testing.assert_equal( + String(result2), + String(py_result2), + "Result doesn't match Python's int result", + ) + + # Test case 3: Positive // negative with exact division + var a3 = BigInt("6") + var b3 = BigInt("-3") + var result3 = a3 // b3 + var py_result3 = py.int("6") // py.int("-3") + testing.assert_equal( + String(result3), "-2", "6 // -3 should equal -2, got " + String(result3) + ) + testing.assert_equal( + String(result3), + String(py_result3), + "Result doesn't match Python's int result", + ) + + # Test case 4: Negative // positive with remainder (critical floor division case) + var a4 = BigInt("-7") + var b4 = BigInt("4") + var result4 = a4 // b4 + var py_result4 = py.int("-7") // py.int("4") + testing.assert_equal( + String(result4), "-2", "-7 // 4 should equal -2, got " + String(result4) + ) + testing.assert_equal( + String(result4), + String(py_result4), + "Result doesn't match Python's int result", + ) + + # Test case 5: Positive // negative with remainder (critical floor division case) + var a5 = BigInt("7") + var b5 = BigInt("-4") + var result5 = a5 // b5 + var py_result5 = py.int("7") // py.int("-4") + testing.assert_equal( + String(result5), "-2", "7 // -4 should equal -2, got " + String(result5) + ) + testing.assert_equal( + String(result5), + String(py_result5), + "Result doesn't match Python's int result", + ) + + print("✓ Floor division with mixed signs tests passed!") + + +fn test_zero_handling() raises: + """Test floor division cases involving zero.""" + print("Testing zero handling in floor division...") + + # Get Python's built-in int module + var py = Python.import_module("builtins") + + # Test case 1: Zero dividend, positive divisor + var a1 = BigInt("0") + var b1 = BigInt("5") + var result1 = a1 // b1 + var py_result1 = py.int("0") // py.int("5") + testing.assert_equal( + String(result1), "0", "0 // 5 should equal 0, got " + String(result1) + ) + testing.assert_equal( + String(result1), + String(py_result1), + "Result doesn't match Python's int result", + ) + + # Test case 2: Zero dividend, negative divisor + var a2 = BigInt("0") + var b2 = BigInt("-5") + var result2 = a2 // b2 + var py_result2 = py.int("0") // py.int("-5") + testing.assert_equal( + String(result2), "0", "0 // -5 should equal 0, got " + String(result2) + ) + testing.assert_equal( + String(result2), + String(py_result2), + "Result doesn't match Python's int result", + ) + + # Test case 3: Division by zero should raise an error + var a3 = BigInt("10") + var b3 = BigInt("0") + var exception_caught = False + try: + var _result3 = a3 // b3 + except: + exception_caught = True + testing.assert_true( + exception_caught, "Division by zero should raise an error" + ) + + # Test case 4: Negative number division by zero should raise an error + var a4 = BigInt("-10") + var b4 = BigInt("0") + exception_caught = False + try: + var _result4 = a4 // b4 + except: + exception_caught = True + testing.assert_true( + exception_caught, "Division by zero should raise an error" + ) + + print("✓ Zero handling tests passed!") + + +fn test_large_number_division() raises: + """Test floor division with very large numbers.""" + print("Testing floor division with large numbers...") + + # Get Python's built-in int module + var py = Python.import_module("builtins") + + # Test case 1: Large positive number divided by small number + var a1 = BigInt("1" + "0" * 50) # 10^50 + var b1 = BigInt("7") + var result1 = a1 // b1 + var py_result1 = py.int("1" + "0" * 50) // py.int("7") + testing.assert_equal( + String(result1), + String(py_result1), + "Large positive number division gave incorrect result", + ) + print("passed: {} // {} = {}".format(a1, b1, result1)) + + # Test case 2: Large negative number divided by small number + var a2 = BigInt("-" + "1" + "0" * 50) # -10^50 + var b2 = BigInt("7") + var result2 = a2 // b2 + var py_result2 = py.int("-" + "1" + "0" * 50) // py.int("7") + testing.assert_equal( + String(result2), + String(py_result2), + "Large negative number division gave incorrect result", + ) + print("passed: {} // {} = {}".format(a2, b2, result2)) + + # Test case 3: Large positive number divided by small negative number + var a3 = BigInt("1" + "0" * 50) # 10^50 + var b3 = BigInt("-7") + var result3 = a3 // b3 + var py_result3 = py.int("1" + "0" * 50) // py.int("-7") + testing.assert_equal( + String(result3), + String(py_result3), + "Large positive // small negative gave incorrect result", + ) + print("passed: {} // {} = {}".format(a3, b3, result3)) + + # Test case 4: Large negative number divided by small negative number + var a4 = BigInt("-" + "1" + "0" * 50) # -10^50 + var b4 = BigInt("-7") + var result4 = a4 // b4 + var py_result4 = py.int("-" + "1" + "0" * 50) // py.int("-7") + testing.assert_equal( + String(result4), + String(py_result4), + "Large negative // small negative gave incorrect result", + ) + print("passed: {} // {} = {}".format(a4, b4, result4)) + + # Test case 5: Large number divided by large number (same sign) + var a5 = BigInt("9" * 30) # 30 nines + var b5 = BigInt("9" * 15) # 15 nines + var result5 = a5 // b5 + var py_result5 = py.int("9" * 30) // py.int("9" * 15) + testing.assert_equal( + String(result5), + String(py_result5), + "Large // large (same sign) gave incorrect result", + ) + print("passed: {} // {} = {}".format(a5, b5, result5)) + + # Test case 6: Large number divided by large number (opposite sign) + var a6 = BigInt("9" * 30) # 30 nines + var b6 = BigInt("-" + "9" * 15) # -15 nines + var result6 = a6 // b6 + var py_result6 = py.int("9" * 30) // py.int("-" + "9" * 15) + testing.assert_equal( + String(result6), + String(py_result6), + "Large // large (opposite sign) gave incorrect result", + ) + print("passed: {} // {} = {}".format(a6, b6, result6)) + + # Test case 7: Very large number divisible by power of 10 (positive) + var a7 = BigInt("1" + "0" * 100) # 10^100 + var b7 = BigInt("1" + "0" * 40) # 10^40 + var result7 = a7 // b7 + var py_result7 = py.int("1" + "0" * 100) // py.int("1" + "0" * 40) + testing.assert_equal( + String(result7), + String(py_result7), + "Power of 10 division gave incorrect result", + ) + print("passed: {} // {} = {}".format(a7, b7, result7)) + + # Test case 8: Very large number divisible by power of 10 (negative dividend) + var a8 = BigInt("-" + "1" + "0" * 100) # -10^100 + var b8 = BigInt("1" + "0" * 40) # 10^40 + var result8 = a8 // b8 + var py_result8 = py.int("-" + "1" + "0" * 100) // py.int("1" + "0" * 40) + testing.assert_equal( + String(result8), + String(py_result8), + "Negative power of 10 division gave incorrect result", + ) + print("passed: {} // {} = {}".format(a8, b8, result8)) + + # Test case 9: Very large complex numbers + stra = "123456789" * 50 + strb = "987654321" * 20 + var a9 = BigInt(stra) + var b9 = BigInt(strb) + var result9 = a9 // b9 + var py_result9 = py.int(stra) // py.int(strb) + testing.assert_equal( + String(result9), + String(py_result9), + "Complex large number division incorrect", + ) + print("passed: {} // {} = {}".format(a9, b9, result9)) + + # Test case 10: Very large negative complex numbers + var a10 = BigInt("-" + stra) + var b10 = BigInt("-" + strb) + var result10 = a10 // b10 + var py_result10 = py.int("-" + stra) // py.int("-" + strb) + testing.assert_equal( + String(result10), + String(py_result10), + "Complex large negative number division incorrect", + ) + print("passed: {} // {} = {}".format(a10, b10, result10)) + + print("✓ Large number division tests passed!") + + +fn test_floor_division_rounding() raises: + """Test that floor division correctly rounds toward negative infinity.""" + print("Testing floor division rounding behavior...") + + # Get Python's built-in int module + var py = Python.import_module("builtins") + + # Test case 1: Positive // positive with remainder + var a1 = BigInt("7") + var b1 = BigInt("2") + var result1 = a1 // b1 + var py_result1 = py.int("7") // py.int("2") + testing.assert_equal( + String(result1), "3", "7 // 2 should equal 3, got " + String(result1) + ) + testing.assert_equal( + String(result1), + String(py_result1), + "Result doesn't match Python's int result", + ) + + # Test case 2: Negative // positive with remainder (key floor division case) + var a2 = BigInt("-7") + var b2 = BigInt("2") + var result2 = a2 // b2 + var py_result2 = py.int("-7") // py.int("2") + testing.assert_equal( + String(result2), "-4", "-7 // 2 should equal -4, got " + String(result2) + ) + testing.assert_equal( + String(result2), + String(py_result2), + "Result doesn't match Python's int result", + ) + + # Test case 3: Positive // negative with remainder (key floor division case) + var a3 = BigInt("7") + var b3 = BigInt("-2") + var result3 = a3 // b3 + var py_result3 = py.int("7") // py.int("-2") + testing.assert_equal( + String(result3), "-4", "7 // -2 should equal -4, got " + String(result3) + ) + testing.assert_equal( + String(result3), + String(py_result3), + "Result doesn't match Python's int result", + ) + + # Test case 4: Negative // negative with remainder + var a4 = BigInt("-7") + var b4 = BigInt("-2") + var result4 = a4 // b4 + var py_result4 = py.int("-7") // py.int("-2") + testing.assert_equal( + String(result4), "3", "-7 // -2 should equal 3, got " + String(result4) + ) + testing.assert_equal( + String(result4), + String(py_result4), + "Result doesn't match Python's int result", + ) + + # Test case 5: Different dividend/divisor patterns + var a5 = BigInt("1") + var b5 = BigInt("4") + var result5 = a5 // b5 + var py_result5 = py.int("1") // py.int("4") + testing.assert_equal( + String(result5), "0", "1 // 4 should equal 0, got " + String(result5) + ) + testing.assert_equal( + String(result5), + String(py_result5), + "Result doesn't match Python's int result", + ) + + # Test case 6: Negative small // positive large + var a6 = BigInt("-1") + var b6 = BigInt("4") + var result6 = a6 // b6 + var py_result6 = py.int("-1") // py.int("4") + testing.assert_equal( + String(result6), "-1", "-1 // 4 should equal -1, got " + String(result6) + ) + testing.assert_equal( + String(result6), + String(py_result6), + "Result doesn't match Python's int result", + ) + + # Test case 7: Borderline case + var a7 = BigInt("-9") + var b7 = BigInt("5") + var result7 = a7 // b7 + var py_result7 = py.int("-9") // py.int("5") + testing.assert_equal( + String(result7), "-2", "-9 // 5 should equal -2, got " + String(result7) + ) + testing.assert_equal( + String(result7), + String(py_result7), + "Result doesn't match Python's int result", + ) + + # Test case 8: Another borderline case + var a8 = BigInt("9") + var b8 = BigInt("-5") + var result8 = a8 // b8 + var py_result8 = py.int("9") // py.int("-5") + testing.assert_equal( + String(result8), "-2", "9 // -5 should equal -2, got " + String(result8) + ) + testing.assert_equal( + String(result8), + String(py_result8), + "Result doesn't match Python's int result", + ) + + # Test case 9: Close to zero negative + var a9 = BigInt("-1") + var b9 = BigInt("3") + var result9 = a9 // b9 + var py_result9 = py.int("-1") // py.int("3") + testing.assert_equal( + String(result9), "-1", "-1 // 3 should equal -1, got " + String(result9) + ) + testing.assert_equal( + String(result9), + String(py_result9), + "Result doesn't match Python's int result", + ) + + # Test case 10: Close to zero positive with negative divisor + var a10 = BigInt("1") + var b10 = BigInt("-3") + var result10 = a10 // b10 + var py_result10 = py.int("1") // py.int("-3") + testing.assert_equal( + String(result10), + "-1", + "1 // -3 should equal -1, got " + String(result10), + ) + testing.assert_equal( + String(result10), + String(py_result10), + "Result doesn't match Python's int result", + ) + + print("✓ Floor division rounding tests passed!") + + +fn test_division_identity() raises: + """Test mathematical properties of floor division.""" + print("Testing mathematical properties of floor division...") + + # Get Python's built-in int module + var py = Python.import_module("builtins") + + # Test property: (a // b) * b + (a % b) = a + # Test case 1: Positive dividend, positive divisor + var a1 = BigInt("17") + var b1 = BigInt("5") + var quotient1 = a1 // b1 + var remainder1 = a1 % b1 + var reconstructed1 = quotient1 * b1 + remainder1 + + # Python equivalent + var py_a1 = py.int("17") + var py_b1 = py.int("5") + var py_quotient1 = py_a1 // py_b1 + var py_remainder1 = py_a1 % py_b1 + + testing.assert_equal( + String(reconstructed1), + String(a1), + "(a // b) * b + (a % b) should equal a for positive numbers", + ) + testing.assert_equal( + String(quotient1), + String(py_quotient1), + "Quotient doesn't match Python's int result", + ) + testing.assert_equal( + String(remainder1), + String(py_remainder1), + "Remainder doesn't match Python's int result", + ) + + # Test case 2: Negative dividend, positive divisor + var a2 = BigInt("-17") + var b2 = BigInt("5") + var quotient2 = a2 // b2 + var remainder2 = a2 % b2 + var reconstructed2 = quotient2 * b2 + remainder2 + + # Python equivalent + var py_a2 = py.int("-17") + var py_b2 = py.int("5") + var py_quotient2 = py_a2 // py_b2 + var py_remainder2 = py_a2 % py_b2 + + testing.assert_equal( + String(reconstructed2), + String(a2), + "(a // b) * b + (a % b) should equal a for negative dividend", + ) + testing.assert_equal( + String(quotient2), + String(py_quotient2), + "Quotient doesn't match Python's int result", + ) + testing.assert_equal( + String(remainder2), + String(py_remainder2), + "Remainder doesn't match Python's int result", + ) + + # Test case 3: Positive dividend, negative divisor + var a3 = BigInt("17") + var b3 = BigInt("-5") + var quotient3 = a3 // b3 + var remainder3 = a3 % b3 + var reconstructed3 = quotient3 * b3 + remainder3 + + # Python equivalent + var py_a3 = py.int("17") + var py_b3 = py.int("-5") + var py_quotient3 = py_a3 // py_b3 + var py_remainder3 = py_a3 % py_b3 + + testing.assert_equal( + String(reconstructed3), + String(a3), + "(a // b) * b + (a % b) should equal a for negative divisor", + ) + testing.assert_equal( + String(quotient3), + String(py_quotient3), + "Quotient doesn't match Python's int result", + ) + testing.assert_equal( + String(remainder3), + String(py_remainder3), + "Remainder doesn't match Python's int result", + ) + + # Test case 4: Negative dividend, negative divisor + var a4 = BigInt("-17") + var b4 = BigInt("-5") + var quotient4 = a4 // b4 + var remainder4 = a4 % b4 + var reconstructed4 = quotient4 * b4 + remainder4 + + # Python equivalent + var py_a4 = py.int("-17") + var py_b4 = py.int("-5") + var py_quotient4 = py_a4 // py_b4 + var py_remainder4 = py_a4 % py_b4 + + testing.assert_equal( + String(reconstructed4), + String(a4), + ( + "(a // b) * b + (a % b) should equal a for negative dividend and" + " divisor" + ), + ) + testing.assert_equal( + String(quotient4), + String(py_quotient4), + "Quotient doesn't match Python's int result", + ) + testing.assert_equal( + String(remainder4), + String(py_remainder4), + "Remainder doesn't match Python's int result", + ) + + # Test case 5: With large numbers + var a5 = BigInt("12345678901234567890") + var b5 = BigInt("987654321") + var quotient5 = a5 // b5 + var remainder5 = a5 % b5 + var reconstructed5 = quotient5 * b5 + remainder5 + var py_a5 = py.int("12345678901234567890") + var py_b5 = py.int("987654321") + var py_quotient5 = py_a5 // py_b5 + var py_remainder5 = py_a5 % py_b5 + testing.assert_equal( + String(reconstructed5), + String(a5), + "(a // b) * b + (a % b) should equal a for large numbers", + ) + testing.assert_equal( + String(quotient5), + String(py_quotient5), + "Quotient doesn't match Python's int result", + ) + testing.assert_equal( + String(remainder5), + String(py_remainder5), + "Remainder doesn't match Python's int result", + ) + + print("✓ Mathematical identity tests passed!") + + +fn test_edge_cases() raises: + """Test edge cases for floor division.""" + print("Testing edge cases for floor division...") + + # Get Python's built-in int module + var py = Python.import_module("builtins") + + # Test case 1: Maximum divisor (just below dividend) + var a1 = BigInt("1000") + var b1 = BigInt("999") + var result1 = a1 // b1 + var py_result1 = py.int("1000") // py.int("999") + testing.assert_equal( + String(result1), + String(py_result1), + "1000 // 999 doesn't match Python's result", + ) + + # Test case 2: Maximum negative divisor (just below dividend in magnitude) + var a2 = BigInt("1000") + var b2 = BigInt("-999") + var result2 = a2 // b2 + var py_result2 = py.int("1000") // py.int("-999") + testing.assert_equal( + String(result2), + String(py_result2), + "1000 // -999 doesn't match Python's result", + ) + + # Test case 3: Consecutive numbers (positive) + var a3 = BigInt("101") + var b3 = BigInt("100") + var result3 = a3 // b3 + var py_result3 = py.int("101") // py.int("100") + testing.assert_equal( + String(result3), + String(py_result3), + "101 // 100 doesn't match Python's result", + ) + + # Test case 4: Consecutive numbers (negative) + var a4 = BigInt("-101") + var b4 = BigInt("100") + var result4 = a4 // b4 + var py_result4 = py.int("-101") // py.int("100") + testing.assert_equal( + String(result4), + String(py_result4), + "-101 // 100 doesn't match Python's result", + ) + + # Test case 5: Equal numbers (positive) + var a5 = BigInt("9" * 100) + var b5 = BigInt("9" * 100) + var result5 = a5 // b5 + var py_result5 = py.int("9" * 100) // py.int("9" * 100) + testing.assert_equal( + String(result5), + "1", + "Equal large positive numbers division should equal 1", + ) + testing.assert_equal( + String(result5), + String(py_result5), + "Result doesn't match Python's int result", + ) + + # Test case 6: Equal numbers (negative) + var a6 = BigInt("-" + "9" * 100) + var b6 = BigInt("-" + "9" * 100) + var result6 = a6 // b6 + var py_result6 = py.int("-" + "9" * 100) // py.int("-" + "9" * 100) + testing.assert_equal( + String(result6), + "1", + "Equal large negative numbers division should equal 1", + ) + testing.assert_equal( + String(result6), + String(py_result6), + "Result doesn't match Python's int result", + ) + + # Test case 7: Very small remainder (positive numbers) + var a7 = BigInt("10000000001") + var b7 = BigInt("10000000000") + var result7 = a7 // b7 + var py_result7 = py.int("10000000001") // py.int("10000000000") + testing.assert_equal( + String(result7), + String(py_result7), + "Floor division with small remainder incorrect", + ) + + # Test case 8: Very small remainder (negative dividend) + var a8 = BigInt("-10000000001") + var b8 = BigInt("10000000000") + var result8 = a8 // b8 + var py_result8 = py.int("-10000000001") // py.int("10000000000") + testing.assert_equal( + String(result8), + String(py_result8), + "Floor division with small negative remainder incorrect", + ) + + # Test case 9: Different input formats - integer init + var a9 = BigInt.from_int(123) + var b9 = BigInt.from_int(45) + var result9 = a9 // b9 + var py_result9 = py.int("123") // py.int("45") + testing.assert_equal( + String(result9), + String(py_result9), + "Division with BigInt.from_int constructor mismatch", + ) + + # Test case 10: Power of 2 divisions (positive) + var a10 = BigInt("128") + var b10 = BigInt("2") + var result10 = a10 // b10 + var py_result10 = py.int("128") // py.int("2") + testing.assert_equal( + String(result10), String(py_result10), "Power of 2 division mismatch" + ) + + # Test case 11: Power of 2 divisions (negative dividend) + var a11 = BigInt("-128") + var b11 = BigInt("2") + var result11 = a11 // b11 + var py_result11 = py.int("-128") // py.int("2") + testing.assert_equal( + String(result11), + String(py_result11), + "Negative power of 2 division mismatch", + ) + + # Test case 12: Power of 10 divisions + var a12 = BigInt("1000") + var b12 = BigInt("10") + var result12 = a12 // b12 + var py_result12 = py.int("1000") // py.int("10") + testing.assert_equal( + String(result12), String(py_result12), "Power of 10 division mismatch" + ) + + print("✓ Edge cases tests passed!") + + +fn run_test_with_error_handling( + test_fn: fn () raises -> None, test_name: String +) raises: + """Helper function to run a test function with error handling and reporting. + """ + try: + print("\n" + "=" * 50) + print("RUNNING: " + test_name) + print("=" * 50) + test_fn() + print("\n✓ " + test_name + " passed\n") + except e: + print("\n✗ " + test_name + " FAILED!") + print("Error message: " + String(e)) + raise e + + +fn main() raises: + print("=========================================") + print("Running BigInt Floor Division Tests") + print("=========================================") + + run_test_with_error_handling( + test_basic_floor_division_positive, + "Basic floor division with positive numbers test", + ) + run_test_with_error_handling( + test_basic_floor_division_negative, + "Basic floor division with negative numbers test", + ) + run_test_with_error_handling( + test_mixed_sign_floor_division, "Mixed sign floor division test" + ) + run_test_with_error_handling(test_zero_handling, "Zero handling test") + run_test_with_error_handling( + test_large_number_division, "Large number division test" + ) + run_test_with_error_handling( + test_floor_division_rounding, "Floor division rounding behavior test" + ) + run_test_with_error_handling( + test_division_identity, "Mathematical identity test" + ) + run_test_with_error_handling(test_edge_cases, "Edge cases test") + + print("All BigInt floor division tests passed!") diff --git a/tests/bigint/test_bigint_truncate_divide.mojo b/tests/bigint/test_bigint_truncate_divide.mojo deleted file mode 100644 index 442da772..00000000 --- a/tests/bigint/test_bigint_truncate_divide.mojo +++ /dev/null @@ -1,443 +0,0 @@ -""" -Comprehensive tests for the truncate_divide operation of the BigInt type. -Truncate division divides toward zero (truncates the fractional part). -""" - -import testing -from decimojo.bigint.bigint import BigInt -import decimojo.bigint.arithmetics - - -fn test_basic_truncate_division() raises: - """Test basic truncate division cases with positive numbers.""" - print("Testing basic truncate division...") - - # Test case 1: Division with no remainder - var a1 = BigInt(10) - var b1 = BigInt(2) - var result1 = a1.truncate_divide(b1) - testing.assert_equal( - String(result1), "5", "10 / 2 should equal 5, got " + String(result1) - ) - - # Test case 2: Division with remainder (truncate toward zero) - var a2 = BigInt(10) - var b2 = BigInt(3) - var result2 = a2.truncate_divide(b2) - testing.assert_equal( - String(result2), "3", "10 / 3 should equal 3, got " + String(result2) - ) - - # Test case 3: Division results in zero (smaller / larger) - var a3 = BigInt(3) - var b3 = BigInt(10) - var result3 = a3.truncate_divide(b3) - testing.assert_equal( - String(result3), "0", "3 / 10 should equal 0, got " + String(result3) - ) - - # Test case 4: Division by 1 - var a4 = BigInt(42) - var b4 = BigInt(1) - var result4 = a4.truncate_divide(b4) - testing.assert_equal( - String(result4), "42", "42 / 1 should equal 42, got " + String(result4) - ) - - # Test case 5: Large number division - var a5 = BigInt("1000000000000") - var b5 = BigInt("1000000") - var result5 = a5.truncate_divide(b5) - testing.assert_equal( - String(result5), - "1000000", - "1000000000000 / 1000000 should equal 1000000, got " + String(result5), - ) - - print("✓ Basic truncate division tests passed!") - - -fn test_negative_truncate_division() raises: - """Test truncate division involving negative numbers.""" - print("Testing truncate division with negative numbers...") - - # Test case 1: Negative dividend, positive divisor (truncate toward zero) - var a1 = BigInt(-10) - var b1 = BigInt(3) - var result1 = a1.truncate_divide(b1) - testing.assert_equal( - String(result1), "-3", "-10 / 3 should equal -3, got " + String(result1) - ) - - # Test case 2: Positive dividend, negative divisor (truncate toward zero) - var a2 = BigInt(10) - var b2 = BigInt(-3) - var result2 = a2.truncate_divide(b2) - testing.assert_equal( - String(result2), "-3", "10 / -3 should equal -3, got " + String(result2) - ) - - # Test case 3: Negative dividend, negative divisor (truncate toward zero) - var a3 = BigInt(-10) - var b3 = BigInt(-3) - var result3 = a3.truncate_divide(b3) - testing.assert_equal( - String(result3), "3", "-10 / -3 should equal 3, got " + String(result3) - ) - - # Test case 4: Exact division with negative numbers - var a4 = BigInt(-12) - var b4 = BigInt(-4) - var result4 = a4.truncate_divide(b4) - testing.assert_equal( - String(result4), "3", "-12 / -4 should equal 3, got " + String(result4) - ) - - # Test case 5: Negative number division with remainder - var a5 = BigInt(-11) - var b5 = BigInt(4) - var result5 = a5.truncate_divide(b5) - testing.assert_equal( - String(result5), "-2", "-11 / 4 should equal -2, got " + String(result5) - ) - - print("✓ Negative number truncate division tests passed!") - - -fn test_zero_handling() raises: - """Test truncate division cases involving zero.""" - print("Testing zero handling in truncate division...") - - # Test case 1: Zero dividend - var a1 = BigInt(0) - var b1 = BigInt(5) - var result1 = a1.truncate_divide(b1) - testing.assert_equal( - String(result1), "0", "0 / 5 should equal 0, got " + String(result1) - ) - - # Test case 2: Zero dividend with negative divisor - var a2 = BigInt(0) - var b2 = BigInt(-5) - var result2 = a2.truncate_divide(b2) - testing.assert_equal( - String(result2), "0", "0 / -5 should equal 0, got " + String(result2) - ) - - # Test case 3: Division by zero should raise an error - var a3 = BigInt(10) - var b3 = BigInt(0) - var exception_caught = False - try: - var _result3 = a3.truncate_divide(b3) - except: - exception_caught = True - testing.assert_true( - exception_caught, "Division by zero should raise an error" - ) - - print("✓ Zero handling tests passed!") - - -fn test_large_number_division() raises: - """Test truncate division with very large numbers.""" - print("Testing truncate division with large numbers...") - - # Test case 1: Large number divided by small number - var a1 = BigInt("1" + "0" * 50) # 10^50 - var b1 = BigInt(7) - var expected1 = BigInt( - "14285714285714285714285714285714285714285714285714" - ) # 10^50 / 7 = 14285714285714285714285714... - var result1 = a1.truncate_divide(b1) - testing.assert_equal( - String(result1), - String(expected1), - "Large number division gave incorrect result", - ) - print("passed: {} / {} = {}".format(a1, b1, result1)) - - # Test case 2: Large number divided by large number - var a2 = BigInt("9" * 30) # 30 nines - var b2 = BigInt("9" * 15) # 15 nines - var expected2 = BigInt("1" + "0" * 14 + "1") # 10^15 + 1 - var result2 = a2.truncate_divide(b2) - testing.assert_equal( - String(result2), - String(expected2), - "Large / large division gave incorrect result", - ) - print("passed: {} / {} = {}".format(a2, b2, result2)) - - # Test case 3: Very large number divisible by power of 10 - var a3 = BigInt("1" + "0" * 100) # 10^100 - var b3 = BigInt("1" + "0" * 40) # 10^40 - var expected3 = BigInt("1" + "0" * 60) # 10^60 - var result3 = a3.truncate_divide(b3) - testing.assert_equal( - String(result3), - String(expected3), - "Power of 10 division gave incorrect result", - ) - print("passed: {} / {} = {}".format(a3, b3, result3)) - - # Test case 4: Large number with large divisor resulting in small quotient - var a4 = BigInt("9" * 50) # 50 nines - var b4 = BigInt("3" * 49 + "4") # slightly less than a third of a4 - var result4 = a4.truncate_divide(b4) - testing.assert_equal( - String(result4), - "2", - ( - "Large numbers division resulting in small quotient gave incorrect" - " result" - ), - ) - print("passed: {} / {} = {}".format(a4, b4, result4)) - - print("✓ Large number division tests passed!") - - -fn test_division_rounding() raises: - """Test that truncate division correctly truncates toward zero.""" - print("Testing truncate division rounding behavior...") - - # Positive / Positive - should truncate toward zero - - # Test case 1: 7/2 = 3.5 -> 3 - var a1 = BigInt(7) - var b1 = BigInt(2) - var expected1 = BigInt(3) - var result1 = a1.truncate_divide(b1) - testing.assert_equal( - String(result1), - String(expected1), - "7 / 2 should equal 3, got " + String(result1), - ) - - # Test case 2: 1/3 = 0.333... -> 0 - var a2 = BigInt(1) - var b2 = BigInt(3) - var expected2 = BigInt(0) - var result2 = a2.truncate_divide(b2) - testing.assert_equal( - String(result2), - String(expected2), - "1 / 3 should equal 0, got " + String(result2), - ) - - # Test case 3: 5/4 = 1.25 -> 1 - var a3 = BigInt(5) - var b3 = BigInt(4) - var expected3 = BigInt(1) - var result3 = a3.truncate_divide(b3) - testing.assert_equal( - String(result3), - String(expected3), - "5 / 4 should equal 1, got " + String(result3), - ) - - # Test case 4: 99/100 = 0.99 -> 0 - var a4 = BigInt(99) - var b4 = BigInt(100) - var expected4 = BigInt(0) - var result4 = a4.truncate_divide(b4) - testing.assert_equal( - String(result4), - String(expected4), - "99 / 100 should equal 0, got " + String(result4), - ) - - # Negative / Positive - should truncate toward zero - - # Test case 5: -7/2 = -3.5 -> -3 - var a5 = BigInt(-7) - var b5 = BigInt(2) - var expected5 = BigInt(-3) - var result5 = a5.truncate_divide(b5) - testing.assert_equal( - String(result5), - String(expected5), - "-7 / 2 should equal -3, got " + String(result5), - ) - - # Test case 6: -1/3 = -0.333... -> 0 - var a6 = BigInt(-1) - var b6 = BigInt(3) - var expected6 = BigInt("0") - var result6 = a6.truncate_divide(b6) - testing.assert_equal( - String(result6), - String(expected6), - "-1 / 3 should equal 0, got " + String(result6), - ) - - # Test case 7: -5/4 = -1.25 -> -1 - var a7 = BigInt(-5) - var b7 = BigInt(4) - var expected7 = BigInt(-1) - var result7 = a7.truncate_divide(b7) - testing.assert_equal( - String(result7), - String(expected7), - "-5 / 4 should equal -1, got " + String(result7), - ) - - # Test case 8: -99/100 = -0.99 -> 0 - var a8 = BigInt(-99) - var b8 = BigInt(100) - var expected8 = BigInt(0) - var result8 = a8.truncate_divide(b8) - testing.assert_equal( - String(result8), - String(expected8), - "-99 / 100 should equal 0, got " + String(result8), - ) - - # Positive / Negative - should truncate toward zero - - # Test case 9: 7/-2 = -3.5 -> -3 - var a9 = BigInt(7) - var b9 = BigInt(-2) - var expected9 = BigInt(-3) - var result9 = a9.truncate_divide(b9) - testing.assert_equal( - String(result9), - String(expected9), - "7 / -2 should equal -3, got " + String(result9), - ) - - # Test case 10: 1/-3 = -0.333... -> 0 - var a10 = BigInt(1) - var b10 = BigInt(-3) - var expected10 = BigInt(0) - var result10 = a10.truncate_divide(b10) - testing.assert_equal( - String(result10), - String(expected10), - "1 / -3 should equal 0, got " + String(result10), - ) - - # Negative / Negative - should truncate toward zero - - # Test case 11: -7/-2 = 3.5 -> 3 - var a11 = BigInt(-7) - var b11 = BigInt(-2) - var expected11 = BigInt(3) - var result11 = a11.truncate_divide(b11) - testing.assert_equal( - String(result11), - String(expected11), - "-7 / -2 should equal 3, got " + String(result11), - ) - - # Test case 12: -99/-100 = 0.99 -> 0 - var a12 = BigInt(-99) - var b12 = BigInt(-100) - var expected12 = BigInt(0) - var result12 = a12.truncate_divide(b12) - testing.assert_equal( - String(result12), - String(expected12), - "-99 / -100 should equal 0, got " + String(result12), - ) - - print("✓ Division rounding tests passed!") - - -fn test_division_identity() raises: - """Test mathematical properties of truncate division.""" - print("Testing mathematical properties of truncate division...") - - # Test property: (a / b) * b + (a % b) = a - var a1 = BigInt(17) - var b1 = BigInt(5) - var quotient1 = a1.truncate_divide(b1) # 3 - var remainder1 = a1.truncate_modulo(b1) # 2 - var reconstructed1 = quotient1 * b1 + remainder1 # 3*5 + 2 = 17 - testing.assert_equal( - String(reconstructed1), - String(a1), - "(a / b) * b + (a % b) should equal a for positive numbers", - ) - - # Same test with negative dividend - var a2 = BigInt(-17) - var b2 = BigInt(5) - var quotient2 = a2.truncate_divide(b2) # -3 - var remainder2 = a2.truncate_modulo(b2) # -2 - var reconstructed2 = quotient2 * b2 + remainder2 # -3*5 + (-2) = -17 - testing.assert_equal( - String(reconstructed2), - String(a2), - "(a / b) * b + (a % b) should equal a for negative dividend", - ) - - # Same test with negative divisor - var a3 = BigInt(17) - var b3 = BigInt(-5) - var quotient3 = a3.truncate_divide(b3) # -3 - var remainder3 = a3.truncate_modulo(b3) # 2 - var reconstructed3 = quotient3 * b3 + remainder3 # -3*(-5) + 2 = 17 - testing.assert_equal( - String(reconstructed3), - String(a3), - "(a / b) * b + (a % b) should equal a for negative divisor", - ) - - # Same test with both negative - var a4 = BigInt(-17) - var b4 = BigInt(-5) - var quotient4 = a4.truncate_divide(b4) # 3 - var remainder4 = a4.truncate_modulo(b4) # -2 - var reconstructed4 = quotient4 * b4 + remainder4 # 3*(-5) + (-2) = -17 - testing.assert_equal( - String(reconstructed4), - String(a4), - "(a / b) * b + (a % b) should equal a for both negative", - ) - - print("✓ Mathematical identity tests passed!") - - -fn run_test_with_error_handling( - test_fn: fn () raises -> None, test_name: String -) raises: - """Helper function to run a test function with error handling and reporting. - """ - try: - print("\n" + "=" * 50) - print("RUNNING: " + test_name) - print("=" * 50) - test_fn() - print("\n✓ " + test_name + " passed\n") - except e: - print("\n✗ " + test_name + " FAILED!") - print("Error message: " + String(e)) - raise e - - -fn main() raises: - print("=========================================") - print("Running BigInt Truncate Division Tests") - print("=========================================") - - run_test_with_error_handling( - test_basic_truncate_division, "Basic truncate division test" - ) - run_test_with_error_handling( - test_negative_truncate_division, - "Negative number truncate division test", - ) - run_test_with_error_handling(test_zero_handling, "Zero handling test") - run_test_with_error_handling( - test_large_number_division, "Large number division test" - ) - run_test_with_error_handling( - test_division_rounding, "Division rounding behavior test" - ) - run_test_with_error_handling( - test_division_identity, "Mathematical identity test" - ) - - print("All BigInt truncate division tests passed!") From c16caa481d469eb624d16820078602d5ed40a752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZHU=20Yuhao=20=E6=9C=B1=E5=AE=87=E6=B5=A9?= Date: Wed, 26 Mar 2025 13:09:56 +0100 Subject: [PATCH 6/8] divided by single word --- src/decimojo/biguint/arithmetics.mojo | 402 ++++++++++++++------------ 1 file changed, 216 insertions(+), 186 deletions(-) diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index 06c0db19..b972705d 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -277,159 +277,6 @@ fn multiply(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return result^ -fn divmod(x1: BigUInt, x2: BigUInt) raises -> Tuple[BigUInt, BigUInt]: - """Returns the quotient and remainder of two numbers, truncating toward zero. - - Args: - x1: The dividend. - x2: The divisor. - - Returns: - The quotient of x1 / x2, truncated toward zero and the remainder. - - Raises: - ValueError: If the divisor is zero. - - Notes: - It is equal to truncated division for positive numbers. - """ - - # CASE: x2 is single word - if len(x2.words) == 1: - # SUB-CASE: Division by zero - if x2.words[0] == 0: - raise Error("Error in `truncate_divide`: Division by zero") - - # SUB-CASE: Division by one - if x2.words[0] == 1: - return Tuple(x1, BigUInt(UInt32(0))) - - # SUB-CASE: Division by two - if x2.words[0] == 2: - var result = x1 - floor_divide_inplace_by_2(result) - if x2.words[0] & 1 == 0: - return Tuple(result, BigUInt(UInt32(0))) - else: - return Tuple(result^, BigUInt(UInt32(1))) - - # SUB-CASE: Single words division - if len(x1.words) == 1: - var result = BigUInt(UInt32(x1.words[0] // x2.words[0])) - var remainder = BigUInt(UInt32(x1.words[0] % x2.words[0])) - return Tuple(result^, remainder^) - - # SUB-CASE: Divisor is single word and is power of 2 - if (x2.words[0] & (x2.words[0] - 1)) == 0: - var quotient = x1 # Make a copy - var divisor = x2.words[0] - # Calculate quotient by repeated division by 2 - while divisor > 1: - floor_divide_inplace_by_2(quotient) - divisor >>= 1 - # Calculate remainder: remainder = x1 - quotient * x2 - var remainder = subtract(x1, multiply(quotient, x2)) - return Tuple(quotient^, remainder^) - - # CASE: Dividend is zero - if x1.is_zero(): - return Tuple(BigUInt(), BigUInt()) # Return zero quotient and remainder - - var comparison_result: Int8 = x1.compare(x2) - # CASE: dividend < divisor - if comparison_result < 0: - return Tuple( - BigUInt(), x1 - ) # Return zero quotient and dividend as remainder - # CASE: dividend == divisor - if comparison_result == 0: - return ( - BigUInt(UInt32(1)), - BigUInt(), - ) # Return one quotient and zero remainder - - # CASE: Duo words division by means of UInt64 - if len(x1.words) <= 2 and len(x2.words) <= 2: - var result = BigUInt.from_uint64(x1.to_uint64() // x2.to_uint64()) - var remainder = BigUInt.from_uint64(x1.to_uint64() % x2.to_uint64()) - return Tuple(result^, remainder^) - - # CASE: Divisor is 10^n - # First remove the last words (10^9) and then shift the rest - if BigUInt.is_power_of_10(x2): - var result: BigUInt - var remainder = BigUInt(empty=True) - - if len(x2.words) == 1: - result = x1 - else: - var word_shift = len(x2.words) - 1 - # If we need to drop more words than exists, result is zero - if word_shift >= len(x1.words): - return Tuple(BigUInt(), x1) - # Create result with the remaining words - result = BigUInt(empty=True) - for i in range(word_shift, len(x1.words)): - result.words.append(x1.words[i]) - for i in range(min(word_shift, len(x1.words))): - remainder.words.append(x1.words[i]) - - # Get the last word of the divisor - var x2_word = x2.words[len(x2.words) - 1] - var carry = UInt32(0) - var power_of_carry = UInt32(1_000_000_000) // x2_word - for i in range(len(result.words) - 1, -1, -1): - var quot = result.words[i] // x2_word - var rem = result.words[i] % x2_word - result.words[i] = quot + carry * power_of_carry - carry = rem - - # Add the final remainder from the digit-wise division - if carry > 0: - # If we already have words in the remainder, we need to add this carry - if len(remainder.words) > 0: - # Create a BigUInt with the carry and multiply by appropriate power of 10 - var carry_biguint = BigUInt(carry) - if len(x2.words) > 1: - for _ in range(len(x2.words) - 1): - # Multiply by 10^9 for each word position - carry_biguint = multiply( - carry_biguint, BigUInt(1_000_000_000) - ) - remainder = add(remainder, carry_biguint) - else: - remainder.words.append(carry) - - # Remove leading zeros - result.remove_trailing_zeros() - remainder.remove_trailing_zeros() - - return Tuple(result^, remainder^) - - # CASE: division of very, very large numbers - # Use Newton-Raphson division for large numbers? - - # CASE: all other situations - # Normalize divisor to improve quotient estimation - var normalized_x1 = x1 - var normalized_x2 = x2 - var normalization_factor: UInt32 = 1 - - # Calculate normalization factor to make leading digit of divisor large - var msw = x2.words[len(x2.words) - 1] - if msw < 500_000_000: - while msw < 100_000_000: # Ensure leading digit is significant - msw *= 10 - normalization_factor *= 10 - - # Apply normalization - if normalization_factor > 1: - normalized_x1 = multiply(x1, BigUInt(normalization_factor)) - normalized_x2 = multiply(x2, BigUInt(normalization_factor)) - - return divmod_general(normalized_x1, normalized_x2) - - fn floor_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: """Returns the quotient of two BigUInt numbers, truncating toward zero. @@ -463,7 +310,7 @@ fn floor_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: floor_divide_inplace_by_2(result) return result^ - # SUB-CASE: Single words division + # SUB-CASE: Single word // single word if len(x1.words) == 1: var result = BigUInt(UInt32(x1.words[0] // x2.words[0])) return result^ @@ -477,6 +324,12 @@ fn floor_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: remainder >>= 1 return result^ + # SUB-CASE: Multi words // single word + else: + var result = x1 + floor_divide_inplace_by_single_word(result, x2) + return result^ + # CASE: Dividend is zero if x1.is_zero(): return BigUInt() # Return zero @@ -676,6 +529,159 @@ fn ceil_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return subtract(x2, remainder) +fn divmod(x1: BigUInt, x2: BigUInt) raises -> Tuple[BigUInt, BigUInt]: + """Returns the quotient and remainder of two numbers, truncating toward zero. + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The quotient of x1 / x2, truncated toward zero and the remainder. + + Raises: + ValueError: If the divisor is zero. + + Notes: + It is equal to truncated division for positive numbers. + """ + + # CASE: x2 is single word + if len(x2.words) == 1: + # SUB-CASE: Division by zero + if x2.words[0] == 0: + raise Error("Error in `truncate_divide`: Division by zero") + + # SUB-CASE: Division by one + if x2.words[0] == 1: + return Tuple(x1, BigUInt(UInt32(0))) + + # SUB-CASE: Division by two + if x2.words[0] == 2: + var result = x1 + floor_divide_inplace_by_2(result) + if x2.words[0] & 1 == 0: + return Tuple(result, BigUInt(UInt32(0))) + else: + return Tuple(result^, BigUInt(UInt32(1))) + + # SUB-CASE: Single word // single word + if len(x1.words) == 1: + var result = BigUInt(UInt32(x1.words[0] // x2.words[0])) + var remainder = BigUInt(UInt32(x1.words[0] % x2.words[0])) + return Tuple(result^, remainder^) + + # SUB-CASE: Divisor is single word and is power of 2 + if (x2.words[0] & (x2.words[0] - 1)) == 0: + var quotient = x1 # Make a copy + var divisor = x2.words[0] + # Calculate quotient by repeated division by 2 + while divisor > 1: + floor_divide_inplace_by_2(quotient) + divisor >>= 1 + # Calculate remainder: remainder = x1 - quotient * x2 + var remainder = subtract(x1, multiply(quotient, x2)) + return Tuple(quotient^, remainder^) + + # CASE: Dividend is zero + if x1.is_zero(): + return Tuple(BigUInt(), BigUInt()) # Return zero quotient and remainder + + var comparison_result: Int8 = x1.compare(x2) + # CASE: dividend < divisor + if comparison_result < 0: + return Tuple( + BigUInt(), x1 + ) # Return zero quotient and dividend as remainder + # CASE: dividend == divisor + if comparison_result == 0: + return ( + BigUInt(UInt32(1)), + BigUInt(), + ) # Return one quotient and zero remainder + + # CASE: Duo words division by means of UInt64 + if len(x1.words) <= 2 and len(x2.words) <= 2: + var result = BigUInt.from_uint64(x1.to_uint64() // x2.to_uint64()) + var remainder = BigUInt.from_uint64(x1.to_uint64() % x2.to_uint64()) + return Tuple(result^, remainder^) + + # CASE: Divisor is 10^n + # First remove the last words (10^9) and then shift the rest + if BigUInt.is_power_of_10(x2): + var result: BigUInt + var remainder = BigUInt(empty=True) + + if len(x2.words) == 1: + result = x1 + else: + var word_shift = len(x2.words) - 1 + # If we need to drop more words than exists, result is zero + if word_shift >= len(x1.words): + return Tuple(BigUInt(), x1) + # Create result with the remaining words + result = BigUInt(empty=True) + for i in range(word_shift, len(x1.words)): + result.words.append(x1.words[i]) + for i in range(min(word_shift, len(x1.words))): + remainder.words.append(x1.words[i]) + + # Get the last word of the divisor + var x2_word = x2.words[len(x2.words) - 1] + var carry = UInt32(0) + var power_of_carry = UInt32(1_000_000_000) // x2_word + for i in range(len(result.words) - 1, -1, -1): + var quot = result.words[i] // x2_word + var rem = result.words[i] % x2_word + result.words[i] = quot + carry * power_of_carry + carry = rem + + # Add the final remainder from the digit-wise division + if carry > 0: + # If we already have words in the remainder, we need to add this carry + if len(remainder.words) > 0: + # Create a BigUInt with the carry and multiply by appropriate power of 10 + var carry_biguint = BigUInt(carry) + if len(x2.words) > 1: + for _ in range(len(x2.words) - 1): + # Multiply by 10^9 for each word position + carry_biguint = multiply( + carry_biguint, BigUInt(1_000_000_000) + ) + remainder = add(remainder, carry_biguint) + else: + remainder.words.append(carry) + + # Remove leading zeros + result.remove_trailing_zeros() + remainder.remove_trailing_zeros() + + return Tuple(result^, remainder^) + + # CASE: division of very, very large numbers + # Use Newton-Raphson division for large numbers? + + # CASE: all other situations + # Normalize divisor to improve quotient estimation + var normalized_x1 = x1 + var normalized_x2 = x2 + var normalization_factor: UInt32 = 1 + + # Calculate normalization factor to make leading digit of divisor large + var msw = x2.words[len(x2.words) - 1] + if msw < 500_000_000: + while msw < 100_000_000: # Ensure leading digit is significant + msw *= 10 + normalization_factor *= 10 + + # Apply normalization + if normalization_factor > 1: + normalized_x1 = multiply(x1, BigUInt(normalization_factor)) + normalized_x2 = multiply(x2, BigUInt(normalization_factor)) + + return divmod_general(normalized_x1, normalized_x2) + + # ===----------------------------------------------------------------------=== # # Multiplication Algorithms # ===----------------------------------------------------------------------=== # @@ -834,22 +840,22 @@ fn multiply_toom_cook_3(x1: BigUInt, x2: BigUInt) raises -> BigUInt: # ===----------------------------------------------------------------------=== # -fn divmod_general(x1: BigUInt, x2: BigUInt) raises -> Tuple[BigUInt, BigUInt]: - """General divmod algorithm for BigInt numbers. +fn floor_divide_general(x1: BigUInt, x2: BigUInt) raises -> BigUInt: + """General division algorithm for BigInt numbers. Args: x1: The dividend. x2: The divisor. Returns: - The quotient of x1 // x2 and the remainder of x1 % x2. + The quotient of x1 / x2. Raises: ValueError: If the divisor is zero. """ if x2.is_zero(): - raise Error("Error in `divmod_general`: Division by zero") + raise Error("Error in `floor_divide_general`: Division by zero") # Initialize result and remainder var result = BigUInt(empty=True, capacity=len(x1.words)) @@ -901,25 +907,71 @@ fn divmod_general(x1: BigUInt, x2: BigUInt) raises -> Tuple[BigUInt, BigUInt]: j -= 1 result.remove_trailing_zeros() - return Tuple(result, remainder) + return result^ -fn floor_divide_general(x1: BigUInt, x2: BigUInt) raises -> BigUInt: - """General division algorithm for BigInt numbers. +fn floor_divide_inplace_by_single_word(mut x1: BigUInt, x2: BigUInt) raises: + """Divides a BigUInt by a single word divisor in-place. + + Args: + x1: The BigUInt value to divide by the divisor. + x2: The single word divisor. + """ + if x2.is_zero(): + raise Error( + "Error in `floor_divide_inplace_by_single_word`: Division by zero" + ) + + # CASE: all other situations + var x2_value = UInt64(x2.words[0]) + var carry = UInt32(0) + for i in range(len(x1.words) - 1, -1, -1): + var dividend = UInt64(carry) * UInt64(1_000_000_000) + UInt64( + x1.words[i] + ) + x1.words[i] = UInt32(dividend // x2_value) + carry = UInt32(dividend % x2_value) + x1.remove_trailing_zeros() + + +fn floor_divide_inplace_by_2(mut x: BigUInt): + """Divides a BigUInt by 2 in-place. + + Args: + x: The BigUInt value to divide by 2. + """ + if x.is_zero(): + return + + var carry: UInt32 = 0 + + # Process from most significant to least significant word + for ith in range(len(x.words) - 1, -1, -1): + x.words[ith] += carry + carry = UInt32(1_000_000_000) if (x.words[ith] & 1) else 0 + x.words[ith] >>= 1 + + # Remove leading zeros + while len(x.words) > 1 and x.words[len(x.words) - 1] == 0: + x.words.resize(len(x.words) - 1) + + +fn divmod_general(x1: BigUInt, x2: BigUInt) raises -> Tuple[BigUInt, BigUInt]: + """General divmod algorithm for BigInt numbers. Args: x1: The dividend. x2: The divisor. Returns: - The quotient of x1 / x2. + The quotient of x1 // x2 and the remainder of x1 % x2. Raises: ValueError: If the divisor is zero. """ if x2.is_zero(): - raise Error("Error in `floor_divide_general`: Division by zero") + raise Error("Error in `divmod_general`: Division by zero") # Initialize result and remainder var result = BigUInt(empty=True, capacity=len(x1.words)) @@ -971,29 +1023,7 @@ fn floor_divide_general(x1: BigUInt, x2: BigUInt) raises -> BigUInt: j -= 1 result.remove_trailing_zeros() - return result^ - - -fn floor_divide_inplace_by_2(mut x: BigUInt): - """Divides a BigUInt by 2 in-place. - - Args: - x: The BigUInt value to divide by 2. - """ - if x.is_zero(): - return - - var carry: UInt32 = 0 - - # Process from most significant to least significant word - for ith in range(len(x.words) - 1, -1, -1): - x.words[ith] += carry - carry = UInt32(1_000_000_000) if (x.words[ith] & 1) else 0 - x.words[ith] >>= 1 - - # Remove leading zeros - while len(x.words) > 1 and x.words[len(x.words) - 1] == 0: - x.words.resize(len(x.words) - 1) + return Tuple(result, remainder) # ===----------------------------------------------------------------------=== # From 2b2f188e9ffbb4cdd36ad518b58938bd5f82b42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZHU=20Yuhao=20=E6=9C=B1=E5=AE=87=E6=B5=A9?= Date: Wed, 26 Mar 2025 18:22:39 +0100 Subject: [PATCH 7/8] Update --- benches/bigint/bench.mojo | 22 ++--- benches/biguint/bench.mojo | 22 ++--- src/decimojo/biguint/arithmetics.mojo | 118 +++++++++++++++++++++++--- src/decimojo/biguint/biguint.mojo | 42 +++++++-- 4 files changed, 162 insertions(+), 42 deletions(-) diff --git a/benches/bigint/bench.mojo b/benches/bigint/bench.mojo index 5b93ee4a..2042dff0 100644 --- a/benches/bigint/bench.mojo +++ b/benches/bigint/bench.mojo @@ -9,26 +9,26 @@ fn main() raises: ========================================= This is the BigInt Benchmarks ========================================= -1. add: Add -2. truncdiv: Truncate Divide -3. floordiv: Floor Divide -4. all: Run all benchmarks -5. quit: Exit +1: Add +2: Truncate Divide +3: Floor Divide +4: Run all benchmarks +5: Exit ========================================= """ ) - var command = input("Type the bench you want to run:") - if command == "add": + var command = input("Type the number of the bench you want to run: ") + if command == "1": bench_add() - elif command == "truncdiv": + elif command == "2": bench_truncate_divide() - elif command == "floordiv": + elif command == "3": bench_floor_divide() - elif command == "all": + elif command == "4": bench_add() bench_truncate_divide() bench_floor_divide() - elif command == "quit": + elif command == "5": return else: print("Invalid input") diff --git a/benches/biguint/bench.mojo b/benches/biguint/bench.mojo index 7dbe64d4..e3df019f 100644 --- a/benches/biguint/bench.mojo +++ b/benches/biguint/bench.mojo @@ -9,26 +9,26 @@ fn main() raises: ========================================= This is the BigUInt Benchmarks ========================================= -1. add: Add -2. multiply: Multiply -2. truncdiv: Truncate Divide -4. all: Run all benchmarks -5. quit: Exit +1: Add +2: Multiply +3: Truncate Divide +4: Run all benchmarks +5: Exit ========================================= """ ) - var command = input("Type the bench you want to run:") - if command == "add": + var command = input("Type the number of bench you want to run: ") + if command == "1": bench_add() - elif command == "multiply": + elif command == "2": bench_multiply() - elif command == "truncdiv": + elif command == "3": bench_truncate_divide() - elif command == "all": + elif command == "4": bench_add() bench_multiply() bench_truncate_divide() - elif command == "quit": + elif command == "5": return else: print("Invalid input") diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index b972705d..76233771 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -330,6 +330,12 @@ fn floor_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: floor_divide_inplace_by_single_word(result, x2) return result^ + # CASE: Divisor is double-word + if len(x2.words) == 2: + var result = x1 + floor_divide_inplace_by_double_words(result, x2) + return result^ + # CASE: Dividend is zero if x1.is_zero(): return BigUInt() # Return zero @@ -742,12 +748,12 @@ fn multiply_toom_cook_3(x1: BigUInt, x2: BigUInt) raises -> BigUInt: for i in range(2 * k, len(x2.words)): b2_words.append(x2.words[i]) - a0 = BigUInt(a0_words) - a1 = BigUInt(a1_words) - a2 = BigUInt(a2_words) - b0 = BigUInt(b0_words) - b1 = BigUInt(b1_words) - b2 = BigUInt(b2_words) + a0 = BigUInt.from_list(a0_words^) + a1 = BigUInt.from_list(a1_words^) + a2 = BigUInt.from_list(a2_words^) + b0 = BigUInt.from_list(b0_words^) + b1 = BigUInt.from_list(b1_words^) + b2 = BigUInt.from_list(b2_words^) # Remove trailing zeros a0.remove_trailing_zeros() @@ -848,7 +854,7 @@ fn floor_divide_general(x1: BigUInt, x2: BigUInt) raises -> BigUInt: x2: The divisor. Returns: - The quotient of x1 / x2. + The quotient of x1 // x2. Raises: ValueError: If the divisor is zero. @@ -910,6 +916,74 @@ fn floor_divide_general(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return result^ +fn floor_divide_partition(x1: BigUInt, x2: BigUInt) raises -> BigUInt: + """Partition division algorithm for BigInt numbers. + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The quotient of x1 // x2. + + Raises: + ValueError: If the divisor is zero. + + Notes: + + If words of x1 is more than 2 times the words of x2, then partition x1 into + several parts and divide x2 sequentially using general division. + + words of x1: m + words of x2: n + number of partitions: m // n + words of first partition: n + m % n + the remainder is appended to the next partition. + """ + + if x2.is_zero(): + raise Error("Error in `floor_divide_partition`: Division by zero") + + # Initialize result and remainder + var number_of_partitions = len(x1.words) // len(x2.words) + var number_of_words_remainder = len(x1.words) % len(x2.words) + var number_of_words_dividend: Int + var result = x1 + result.words.resize(len(x1.words) - number_of_words_remainder) + var remainder = BigUInt(List[UInt32](capacity=len(x2.words))) + for i in range(len(x1.words) - number_of_words_remainder, len(x1.words)): + remainder.words.append(x1.words[i]) + + for ith in range(number_of_partitions): + number_of_words_dividend = len(x2.words) + number_of_words_remainder + var dividend_list_of_words = List[UInt32]( + capacity=number_of_words_dividend + ) + for i in range(len(x2.words)): + dividend_list_of_words.append( + x1.words[(number_of_partitions - ith - 1) * len(x2.words) + i] + ) + for i in range(number_of_words_remainder): + dividend_list_of_words.append(remainder.words[i]) + + var dividend = BigUInt(dividend_list_of_words) + var quotient = floor_divide_general(dividend, x2) + for i in range(len(x2.words)): + if i < len(quotient.words): + result.words[ + (number_of_partitions - ith - 1) * len(x2.words) + i + ] = quotient.words[i] + else: + result.words[ + (number_of_partitions - ith - 1) * len(x2.words) + i + ] = UInt32(0) + remainder = subtract(dividend, multiply(quotient, x2)) + number_of_words_remainder = len(remainder.words) + + result.remove_trailing_zeros() + return result^ + + fn floor_divide_inplace_by_single_word(mut x1: BigUInt, x2: BigUInt) raises: """Divides a BigUInt by a single word divisor in-place. @@ -924,13 +998,35 @@ fn floor_divide_inplace_by_single_word(mut x1: BigUInt, x2: BigUInt) raises: # CASE: all other situations var x2_value = UInt64(x2.words[0]) - var carry = UInt32(0) + var carry = UInt64(0) for i in range(len(x1.words) - 1, -1, -1): - var dividend = UInt64(carry) * UInt64(1_000_000_000) + UInt64( - x1.words[i] + var dividend = carry * UInt64(1_000_000_000) + UInt64(x1.words[i]) + x1.words[i] = UInt32(dividend // x2_value) + carry = dividend % x2_value + x1.remove_trailing_zeros() + + +fn floor_divide_inplace_by_double_words(mut x1: BigUInt, x2: BigUInt) raises: + """Divides a BigUInt by double-word divisor in-place. + + Args: + x1: The BigUInt value to divide by the divisor. + x2: The double-word divisor. + """ + if x2.is_zero(): + raise Error( + "Error in `floor_divide_inplace_by_double_words`: Division by zero" ) + + # CASE: all other situations + var x2_value = UInt128(x2.words[0]) + UInt128(x2.words[1]) * UInt128( + 1_000_000_000 + ) + var carry = UInt128(0) + for i in range(len(x1.words) - 1, -1, -1): + var dividend = carry * UInt128(1_000_000_000) + UInt128(x1.words[i]) x1.words[i] = UInt32(dividend // x2_value) - carry = UInt32(dividend % x2_value) + carry = dividend % x2_value x1.remove_trailing_zeros() diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index 23f11ce4..baf57602 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -105,6 +105,8 @@ struct BigUInt(Absable, IntableRaising, Writable): fn __init__(out self, owned words: List[UInt32]): """Initializes a BigUInt from a list of UInt32 words. + It does not check whether the list is empty or the words are invalid. + See `from_list()` for safer initialization. Args: words: A list of UInt32 words representing the coefficient. @@ -113,11 +115,10 @@ struct BigUInt(Absable, IntableRaising, Writable): Notes: - This method does not check whether the words are smaller than - `999_999_999`. + This method does not check whether + (1) the list is empty. + (2) the words are smaller than `999_999_999`. """ - if len(words) == 0: - words.append(UInt32(0)) self.words = words^ fn __init__(out self, owned *words: UInt32): @@ -163,9 +164,36 @@ struct BigUInt(Absable, IntableRaising, Writable): # from_string(value: String) -> Self # ===------------------------------------------------------------------=== # + @staticmethod + fn from_list(owned words: List[UInt32]) raises -> Self: + """Initializes a BigUInt from a list of UInt32 words safely. + If the list is empty, the BigUInt is initialized with value 0. + + Args: + words: A list of 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. + + Returns: + The BigUInt representation of the list of UInt32 words. + """ + # Return 0 if the list is empty + if len(words) == 0: + return Self() + + # Check if the words are valid + for word in words: + if word[] > Self.MAX_OF_WORD: + raise Error( + "Error in `BigUInt.from_list()`: Word value exceeds maximum" + " value of 999_999_999" + ) + + return Self(words^) + @staticmethod fn from_words(*words: UInt32) raises -> Self: - """Initializes a BigUInt from raw words. + """Initializes a BigUInt from raw words safely. Args: words: The UInt32 words representing the coefficient. @@ -778,10 +806,6 @@ struct BigUInt(Absable, IntableRaising, Writable): return True return False - # ===------------------------------------------------------------------=== # - # Internal methods - # ===------------------------------------------------------------------=== # - fn internal_representation(value: BigUInt): """Prints the internal representation details of a BigUInt.""" print("\nInternal Representation Details of BigUInt") From 3eb981beb26adbbc35b6515578d38b8644c206c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZHU=20Yuhao=20=E6=9C=B1=E5=AE=87=E6=B5=A9?= Date: Wed, 26 Mar 2025 19:25:20 +0100 Subject: [PATCH 8/8] Update --- src/decimojo/biguint/arithmetics.mojo | 114 +++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 12 deletions(-) diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index 76233771..b9062d88 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -324,18 +324,24 @@ fn floor_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: remainder >>= 1 return result^ - # SUB-CASE: Multi words // single word + # SUB-CASE: Divisor is single word (<= 10 digits) else: var result = x1 floor_divide_inplace_by_single_word(result, x2) return result^ - # CASE: Divisor is double-word + # CASE: Divisor is double-word (<= 20 digits) if len(x2.words) == 2: var result = x1 floor_divide_inplace_by_double_words(result, x2) return result^ + # CASE: Dividend is quadruple-word (<= 40 digits) + if len(x2.words) == 4: + var result = x1 + floor_divide_inplace_by_quad_words(result, x2) + return result^ + # CASE: Dividend is zero if x1.is_zero(): return BigUInt() # Return zero @@ -348,11 +354,6 @@ fn floor_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: if comparison_result == 0: return BigUInt(UInt32(1)) - # CASE: Duo words division by means of UInt64 - if len(x1.words) <= 2 and len(x2.words) <= 2: - var result = BigUInt.from_uint64(x1.to_uint64() // x2.to_uint64()) - return result^ - # CASE: Divisor is 10^n # First remove the last words (10^9) and then shift the rest if BigUInt.is_power_of_10(x2): @@ -1019,15 +1020,104 @@ fn floor_divide_inplace_by_double_words(mut x1: BigUInt, x2: BigUInt) raises: ) # CASE: all other situations - var x2_value = UInt128(x2.words[0]) + UInt128(x2.words[1]) * UInt128( - 1_000_000_000 + var x2_value = UInt128(x2.words[1]) * UInt128(1_000_000_000) + UInt128( + x2.words[0] ) + var carry = UInt128(0) - for i in range(len(x1.words) - 1, -1, -1): - var dividend = carry * UInt128(1_000_000_000) + UInt128(x1.words[i]) - x1.words[i] = UInt32(dividend // x2_value) + if len(x1.words) % 2 == 1: + carry = UInt128(x1.words[-1]) + x1.words.resize(len(x1.words) - 1) + + for i in range(len(x1.words) - 1, -1, -2): + var dividend = carry * UInt128(1_000_000_000_000_000_000) + UInt128( + x1.words[i] + ) * UInt128(1_000_000_000) + UInt128(x1.words[i - 1]) + var quotient = dividend // x2_value + x1.words[i] = UInt32(quotient // UInt128(1_000_000_000)) + x1.words[i - 1] = UInt32(quotient % UInt128(1_000_000_000)) carry = dividend % x2_value + x1.remove_trailing_zeros() + return + + +fn floor_divide_inplace_by_quad_words(mut x1: BigUInt, x2: BigUInt) raises: + """Divides a BigUInt by quad-word divisor in-place. + + Args: + x1: The BigUInt value to divide by the divisor. + x2: The double-word divisor. + + Notes: + + The improvement in this algorithm is marginal. + """ + if x2.is_zero(): + raise Error( + "Error in `floor_divide_inplace_by_double_words`: Division by zero" + ) + + # CASE: all other situations + var x2_value = UInt256(x2.words[3]) * UInt256( + 1_000_000_000_000_000_000_000_000_000 + ) + UInt256(x2.words[2]) * UInt256(1_000_000_000_000_000_000) + UInt256( + x2.words[1] + ) * UInt256( + 1_000_000_000 + ) + UInt256( + x2.words[0] + ) + + var carry = UInt256(0) + if len(x1.words) % 4 == 1: + carry = UInt256(x1.words[-1]) + x1.words.resize(len(x1.words) - 1) + if len(x1.words) % 4 == 2: + carry = UInt256(x1.words[-1]) * UInt256(1_000_000_000) + UInt256( + x1.words[-2] + ) + x1.words.resize(len(x1.words) - 2) + if len(x1.words) % 4 == 3: + carry = ( + UInt256(x1.words[-1]) * UInt256(1_000_000_000_000_000_000) + + UInt256(x1.words[-2]) * UInt256(1_000_000_000) + + UInt256(x1.words[-3]) + ) + x1.words.resize(len(x1.words) - 3) + + for i in range(len(x1.words) - 1, -1, -4): + var dividend = carry * UInt256( + 1_000_000_000_000_000_000_000_000_000_000_000_000 + ) + UInt256(x1.words[i]) * UInt256( + 1_000_000_000_000_000_000_000_000_000 + ) + UInt256( + x1.words[i - 1] + ) * UInt256( + 1_000_000_000_000_000_000 + ) + UInt256( + x1.words[i - 2] + ) * UInt256( + 1_000_000_000 + ) + UInt256( + x1.words[i - 3] + ) + var quotient = dividend // x2_value + carry = dividend % x2_value + + var quot = quotient // (UInt256(1_000_000_000_000_000_000_000_000_000)) + x1.words[i] = UInt32(quot) + var rem = quotient % (UInt256(1_000_000_000_000_000_000_000_000_000)) + quot = rem // UInt256(1_000_000_000_000_000_000) + x1.words[i - 1] = UInt32(quot) + rem = rem % UInt256(1_000_000_000_000_000_000) + quot = rem // UInt256(1_000_000_000) + x1.words[i - 2] = UInt32(quot) + rem = rem % UInt256(1_000_000_000) + x1.words[i - 3] = UInt32(rem) + + x1.remove_trailing_zeros() + return fn floor_divide_inplace_by_2(mut x: BigUInt):