diff --git a/.gitignore b/.gitignore index 35fbccf..4d788aa 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ magic.lock # Temporary files tempCodeRunnerFile.mojo /temp*.mojo +kgen.trace.json* # macOS environments .DS_Store # log files diff --git a/README.md b/README.md index 09765ea..31a5486 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An arbitrary-precision decimal and integer mathematics library for [Mojo](https://www.modular.com/mojo). -**[中文·漢字»](https://zhuyuhao.com/decimojo/docs/readme_zht.html)** | **[Repository on GitHub»](https://github.com/forfudan/decimojo)** | **[Changelog](https://zhuyuhao.com/decimojo/docs/changelog.html)** +**[中文·漢字](https://zhuyuhao.com/decimojo/docs/readme_zht.html)** | **[Changelog](https://zhuyuhao.com/decimojo/docs/changelog.html)** | **[Repository on GitHub»](https://github.com/forfudan/decimojo)** | **[Discord channel»](https://discord.gg/3rGH87uZTk)** - [Overview](#overview) - [Installation](#installation) diff --git a/src/decimojo/bigdecimal/bigdecimal.mojo b/src/decimojo/bigdecimal/bigdecimal.mojo index 26f8bdc..f4cb08f 100644 --- a/src/decimojo/bigdecimal/bigdecimal.mojo +++ b/src/decimojo/bigdecimal/bigdecimal.mojo @@ -938,7 +938,7 @@ struct BigDecimal( Examples: ``` - print(BigDecimal("123.456).extend_precision(5)) # Output: 123.45600000 + print(BigDecimal("123.456").extend_precision(5)) # Output: 123.45600000 print(BigDecimal("123456").extend_precision(3)) # Output: 123456.000 print(BigDecimal("123456").extend_precision(-1)) # Output: 123456 (no change) ``` diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index ef890c2..eec2089 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -106,7 +106,7 @@ fn negative(x: BigUInt) raises -> BigUInt: previous_error=None, ) ) - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero fn absolute(x: BigUInt) -> BigUInt: @@ -284,13 +284,13 @@ fn add_slices_simd( var n_words_x_slice = bounds_x[1] - bounds_x[0] var n_words_y_slice = bounds_y[1] - bounds_y[0] - var words = List[UInt32]( + var result = BigUInt( unsafe_uninit_length=max(n_words_x_slice, n_words_y_slice) ) @parameter fn vector_add[simd_width: Int](i: Int): - words._data.store[width=simd_width]( + result.words._data.store[width=simd_width]( i, x.words._data.load[width=simd_width](i + bounds_x[0]) + y.words._data.load[width=simd_width](i + bounds_y[0]), @@ -318,7 +318,7 @@ fn add_slices_simd( @parameter fn vector_copy_rest_from_longer[simd_width: Int](i: Int): - words._data.store[width=simd_width]( + result.words._data.store[width=simd_width]( n_words_shorter_slice + i, longer[].words._data.load[width=simd_width]( longer_start + n_words_shorter_slice + i @@ -329,8 +329,8 @@ fn add_slices_simd( n_words_longer_slice - n_words_shorter_slice ) - var result = BigUInt(words=words^) normalize_carries_lt_2_bases(result) + result.remove_leading_empty_words() return result^ @@ -379,6 +379,7 @@ fn add_inplace(mut x: BigUInt, y: BigUInt) -> None: # Normalize carries after addition normalize_carries_lt_2_bases(x) + x.remove_leading_empty_words() return @@ -397,13 +398,16 @@ fn add_inplace_by_slice( # Short circuit cases if x.is_zero(): debug_assert[assert_mode="none"]( - len(x.words) == 1, "add_inplace_by_slice(): leading zero words" + len(x.words) == 1, "add_inplace_by_slice(): leading zero words in x" ) - x.words = BigUInt( - y.words[bounds_y[0] : bounds_y[1]] - ).words # Copy the words from y + x = BigUInt.from_slice( + y, bounds=(bounds_y[0], bounds_y[1]) + ) # Copy the words from y return - if y.is_zero(bounds=bounds_y): + if y.is_zero_in_bounds(bounds=bounds_y): + debug_assert[assert_mode="none"]( + len(y.words) == 1, "add_inplace_by_slice(): leading zero words in y" + ) return var n_words_y_slice = bounds_y[1] - bounds_y[0] @@ -508,7 +512,7 @@ fn subtract_school(x: BigUInt, y: BigUInt) raises -> BigUInt: var comparison_result = x.compare(y) if comparison_result == 0: # |x| = |y| - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero if comparison_result < 0: raise Error( OverflowError( @@ -524,15 +528,15 @@ fn subtract_school(x: BigUInt, y: BigUInt) raises -> BigUInt: # Now it is safe to subtract the smaller number from the larger one # The result will have no more words than the first number - var words = List[UInt32](capacity=len(x.words)) + var result = BigUInt(uninitialized_capacity=len(x.words)) var borrow: UInt32 = 0 # Can either be 0 or 1 for i in range(len(y.words)): if x.words[i] < borrow + y.words[i]: - words.append(x.words[i] + BigUInt.BASE - borrow - y.words[i]) + result.words.append(x.words[i] + BigUInt.BASE - borrow - y.words[i]) borrow = 1 # Set borrow for the next word else: - words.append(x.words[i] - borrow - y.words[i]) + result.words.append(x.words[i] - borrow - y.words[i]) borrow = 0 # No borrow for the next word # If x has more words than y, we need to handle the remaining words @@ -540,23 +544,22 @@ fn subtract_school(x: BigUInt, y: BigUInt) raises -> BigUInt: if borrow == 0: # If there is no borrow, we can just copy the remaining words for i in range(len(y.words), len(x.words)): - words.append(x.words[i]) + result.words.append(x.words[i]) else: var no_borrow_idx: Int = 0 # At this stage, borrow can only be 0 or 1 for i in range(len(y.words), len(x.words)): if x.words[i] >= borrow: - words.append(x.words[i] - borrow) + result.words.append(x.words[i] - borrow) no_borrow_idx = i + 1 break # No more borrow, we can stop early else: # x.words[i] == 0, borrow == 1 - words.append(BigUInt.BASE - borrow) + result.words.append(BigUInt.BASE - borrow) for i in range(no_borrow_idx, len(x.words)): - words.append(x.words[i]) # Copy the remaining words + result.words.append(x.words[i]) # Copy the remaining words - var result = BigUInt(words=words^) result.remove_leading_empty_words() return result^ @@ -605,7 +608,7 @@ fn subtract_simd(x: BigUInt, y: BigUInt) raises -> BigUInt: var comparison_result = x.compare(y) if comparison_result == 0: # |x| = |y| - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero if comparison_result < 0: raise Error( OverflowError( @@ -621,7 +624,7 @@ fn subtract_simd(x: BigUInt, y: BigUInt) raises -> BigUInt: # Now it is safe to subtract the smaller number from the larger one # The result will have no more words than the first number - var words = List[UInt32](unsafe_uninit_length=len(x.words)) + var result = BigUInt(unsafe_uninit_length=len(x.words)) # Yuhao ZHU: # We will make use of SIMD operations to subtract the words in parallel. @@ -630,7 +633,7 @@ fn subtract_simd(x: BigUInt, y: BigUInt) raises -> BigUInt: # but we will take advantage of that. @parameter fn vector_subtract[simd_width: Int](i: Int): - words._data.store[width=simd_width]( + result.words._data.store[width=simd_width]( i, x.words._data.load[width=simd_width](i) - y.words._data.load[width=simd_width](i), @@ -640,7 +643,7 @@ fn subtract_simd(x: BigUInt, y: BigUInt) raises -> BigUInt: @parameter fn vector_copy_rest[simd_width: Int](i: Int): - words._data.store[width=simd_width]( + result.words._data.store[width=simd_width]( len(y.words) + i, x.words._data.load[width=simd_width](len(y.words) + i), ) @@ -649,7 +652,6 @@ fn subtract_simd(x: BigUInt, y: BigUInt) raises -> BigUInt: len(x.words) - len(y.words) ) - var result = BigUInt(words=words^) normalize_borrows(result) result.remove_leading_empty_words() @@ -788,6 +790,7 @@ fn subtract_inplace_by_uint32(mut x: BigUInt, y: UInt32) -> None: else: # word >= 3294967297 or word == 0, overflowed value word = (word + BigUInt.BASE) - 1 # borrow = 1 + x.remove_leading_empty_words() return @@ -828,7 +831,7 @@ fn multiply(x: BigUInt, y: BigUInt) -> BigUInt: if len(x.words) == 1: var x_word = x.words[0] if x_word == 0: - return BigUInt(UInt32(0)) + return BigUInt.ZERO elif x_word == 1: return y else: @@ -839,7 +842,7 @@ fn multiply(x: BigUInt, y: BigUInt) -> BigUInt: if len(y.words) == 1: var y_word = y.words[0] if y_word == 0: - return BigUInt(UInt32(0)) + return BigUInt.ZERO if y_word == 1: return x else: @@ -932,7 +935,7 @@ fn multiply_slices_school( if n_words_x_slice == 1: var x_word = x.words[bounds_x[0]] if x_word == 0: - return BigUInt(UInt32(0)) + return BigUInt.ZERO elif x_word == 1: return BigUInt.from_slice(y, (bounds_y[0], bounds_y[1])) else: @@ -942,7 +945,7 @@ fn multiply_slices_school( if n_words_y_slice == 1: var y_word = y.words[bounds_y[0]] if y_word == 0: - return BigUInt(UInt32(0)) + return BigUInt.ZERO elif y_word == 1: return BigUInt.from_slice(x, (bounds_x[0], bounds_x[1])) else: @@ -952,7 +955,10 @@ fn multiply_slices_school( # The max number of words in the result is the sum of the words in the operands var max_result_len = n_words_x_slice + n_words_y_slice - var words = List[UInt32](length=max_result_len, fill=0) + # Allocate the result of zero words with the maximum length + # The leading zeros need to be removed before returning the result + var result = BigUInt(unsafe_uninit_length=max_result_len) + memset_zero(ptr=result.words._data, count=max_result_len) # Perform the multiplication word by word (from least significant to most significant) # x = x[start_x] + x[start_x + 1] * 10^9 @@ -977,19 +983,18 @@ fn multiply_slices_school( UInt64(x.words[bounds_x[0] + i]) * UInt64(y.words[bounds_y[0] + j]) + carry - + UInt64(words[i + j]) + + UInt64(result.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 - words[i + j] = UInt32(product % UInt64(BigUInt.BASE)) + result.words[i + j] = UInt32(product % UInt64(BigUInt.BASE)) carry = product // UInt64(BigUInt.BASE) # If there is a carry left, add it to the next position if carry > 0: - words[i + n_words_y_slice] += UInt32(carry) + result.words[i + n_words_y_slice] += UInt32(carry) - var result = BigUInt(words=words^) result.remove_leading_empty_words() return result^ @@ -1021,8 +1026,10 @@ fn multiply_slices_karatsuba( We just need to consider the slices of x and y by using the indices. """ - if x.is_zero(bounds=bounds_x) or y.is_zero(bounds=bounds_y): - return BigUInt(UInt32(0)) + if x.is_zero_in_bounds(bounds=bounds_x) or y.is_zero_in_bounds( + bounds=bounds_y + ): + return BigUInt.ZERO # Number of words in the slice 1: end_x - start_x # Number of words in the slice 2: end_y - start_y @@ -1081,6 +1088,7 @@ fn multiply_slices_karatsuba( z1.multiply_inplace_by_power_of_billion(m) z1 += z0 + z1.remove_leading_empty_words() return z1^ elif n_words_y_slice <= m: @@ -1108,6 +1116,7 @@ fn multiply_slices_karatsuba( # z2 = 0 z1.multiply_inplace_by_power_of_billion(m) z1 += z0 + z1.remove_leading_empty_words() return z1^ else: @@ -1167,6 +1176,7 @@ fn multiply_slices_karatsuba( z2 += z1 z2 += z0 + z2.remove_leading_empty_words() return z2^ @@ -1294,19 +1304,21 @@ fn multiply_by_power_of_ten(x: BigUInt, n: Int) -> BigUInt: debug_assert[assert_mode="none"]( len(x.words) == 1, "multiply_by_power_of_ten(): leading zero words" ) - return BigUInt(UInt32(0)) # Multiplying zero by anything is still zero + return BigUInt.ZERO # Multiplying zero by anything is still zero var number_of_zero_words = n // 9 var number_of_remaining_digits = n % 9 - var words = List[UInt32](capacity=number_of_zero_words + len(x.words) + 1) + var result = BigUInt( + uninitialized_capacity=number_of_zero_words + len(x.words) + 1 + ) # Add zero words for _ in range(number_of_zero_words): - words.append(UInt32(0)) + result.words.append(UInt32(0)) # Add the original words times 10^number_of_remaining_digits if number_of_remaining_digits == 0: for i in range(len(x.words)): - words.append(x.words[i]) + result.words.append(x.words[i]) else: # number_of_remaining_digits > 0 var carry = UInt64(0) var multiplier: UInt64 @@ -1331,13 +1343,14 @@ fn multiply_by_power_of_ten(x: BigUInt, n: Int) -> BigUInt: for i in range(len(x.words)): product = UInt64(x.words[i]) * multiplier + carry - words.append(UInt32(product % UInt64(BigUInt.BASE))) + result.words.append(UInt32(product % UInt64(BigUInt.BASE))) carry = product // UInt64(BigUInt.BASE) # Add the last carry if it exists if carry > 0: - words.append(UInt32(carry)) + result.words.append(UInt32(carry)) - return BigUInt(words=words^) + result.remove_leading_empty_words() + return result^ fn multiply_inplace_by_power_of_ten(mut x: BigUInt, n: Int): @@ -1460,15 +1473,16 @@ fn multiply_by_power_of_billion(x: BigUInt, n: Int) -> BigUInt: ) # If x is zero, we can just return # No need to add zeros, it will still be zero - return BigUInt() + return BigUInt.ZERO - var words = List[UInt32](unsafe_uninit_length=len(x.words) + n) + var res = BigUInt(unsafe_uninit_length=len(x.words) + n) # Fill the first n words with zeros - memset_zero(ptr=words._data, count=n) + memset_zero(ptr=res.words._data, count=n) # Copy the original words to the end of the new list - memcpy(dest=words._data + n, src=x.words._data, count=len(x.words)) + memcpy(dest=res.words._data + n, src=x.words._data, count=len(x.words)) - return BigUInt(words=words^) + res.remove_leading_empty_words() + return res^ fn multiply_inplace_by_power_of_billion(mut x: BigUInt, n: Int): @@ -1513,6 +1527,8 @@ fn multiply_inplace_by_power_of_billion(mut x: BigUInt, n: Int): # Fill the first n words with zeros for i in range(n): x.words[i] = UInt32(0) + + x.remove_leading_empty_words() return @@ -1543,13 +1559,26 @@ fn floor_divide(x: BigUInt, y: BigUInt) raises -> BigUInt: debug_assert[assert_mode="none"]( (len(x.words) != 0) and (len(y.words) != 0), - "biguint.arithmetics.floor_divide(): BigUInt ", + "biguint.arithmetics.floor_divide(): BigUInt x ", x, " and / or ", y, " is uninitialized!", ) + debug_assert[assert_mode="none"]( + (len(x.words) == 1) or (x.words[-1] != 0), + "biguint.arithmetics.floor_divide(): BigUInt x ", + x, + " has leading zero words!", + ) + debug_assert[assert_mode="none"]( + (len(y.words) == 1) or (y.words[-1] != 0), + "biguint.arithmetics.floor_divide(): BigUInt y ", + y, + " has leading zero words!", + ) + # CASE: y is zero if y.is_zero(): raise Error( @@ -1567,16 +1596,16 @@ fn floor_divide(x: BigUInt, y: BigUInt) raises -> BigUInt: len(x.words) == 1, "biguint.arithmetics.floor_divide(): x has leading zero words", ) - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero # CASE: x is not greater than y var comparison_result: Int8 = x.compare(y) # SUB-CASE: dividend < divisor if comparison_result < 0: - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero # SUB-CASE: dividend == divisor if comparison_result == 0: - return BigUInt(UInt32(1)) + return BigUInt.ONE # Return one # CASE: y is single word if len(y.words) == 1: @@ -1585,7 +1614,7 @@ fn floor_divide(x: BigUInt, y: BigUInt) raises -> BigUInt: return x # SUB-CASE: Single word // single word if len(x.words) == 1: - var result = BigUInt(List[UInt32](x.words[0] // y.words[0])) + var result = BigUInt.from_uint32_unsafe(x.words[0] // y.words[0]) return result^ # SUB-CASE: Divisor is single word (<= 9 digits) else: @@ -1663,16 +1692,16 @@ fn floor_divide_school(x: BigUInt, y: BigUInt) raises -> BigUInt: len(x.words) == 1, "biguint.arithmetics.floor_divide(): x has leading zero words", ) - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero # CASE: x is not greater than y var comparison_result: Int8 = x.compare(y) # SUB-CASE: dividend < divisor if comparison_result < 0: - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero # SUB-CASE: dividend == divisor if comparison_result == 0: - return BigUInt(UInt32(1)) + return BigUInt.ONE # CASE: y is single word if len(y.words) == 1: @@ -1681,7 +1710,7 @@ fn floor_divide_school(x: BigUInt, y: BigUInt) raises -> BigUInt: return x # SUB-CASE: Single word // single word if len(x.words) == 1: - var result = BigUInt(List[UInt32](x.words[0] // y.words[0])) + var result = BigUInt.from_uint32_unsafe(x.words[0] // y.words[0]) return result^ # SUB-CASE: Divisor is single word (<= 9 digits) else: @@ -1701,7 +1730,7 @@ fn floor_divide_school(x: BigUInt, y: BigUInt) raises -> BigUInt: # ALL OTHER CASES # Use the schoolbook division algorithm # Initialize result and remainder - var result = BigUInt(List[UInt32](capacity=len(x.words))) + var result = BigUInt(uninitialized_capacity=len(x.words)) var remainder = x # Shift and initialize @@ -1853,6 +1882,12 @@ fn floor_divide_by_uint32(x: BigUInt, y: UInt32) -> BigUInt: dividend = carry * UInt64(BigUInt.BASE) + UInt64(x.words[i]) result.words[i] = UInt32(dividend // y_uint64) carry = dividend % y_uint64 + + debug_assert[assert_mode="none"]( + (len(result.words) == 1) or (result.words[-1] != 0), + "biguint.arithmetics.floor_divide_by_uint32(): ", + "Result has leading zero words", + ) return result^ @@ -2103,10 +2138,9 @@ fn floor_divide_by_power_of_ten(x: BigUInt, n: Int) -> BigUInt: if word_shift >= len(x.words): return BigUInt.ZERO # Create result with the remaining words - words = List[UInt32]() + result = BigUInt(uninitialized_capacity=len(x.words) - word_shift) for i in range(word_shift, len(x.words)): - words.append(x.words[i]) - result = BigUInt(words=words^) + result.words.append(x.words[i]) # Then shift the remaining words right # Get the last word of the divisor @@ -2115,6 +2149,7 @@ fn floor_divide_by_power_of_ten(x: BigUInt, n: Int) -> BigUInt: var divisor: UInt32 if digit_shift == 0: # No need to shift, just return the result + result.remove_leading_empty_words() return result^ elif digit_shift == 1: divisor = UInt32(10) @@ -2231,8 +2266,6 @@ fn floor_divide_burnikel_ziegler( var normalized_a = a var ndigits_to_shift: Int - if normalized_b.words[-1] == 0: - normalized_b.remove_leading_empty_words() if normalized_b.words[-1] < 500_000_000: ndigits_to_shift = ( decimojo.biguint.arithmetics.calculate_ndigits_for_normalization( @@ -2295,8 +2328,8 @@ fn floor_divide_burnikel_ziegler( if normalized_a.words[-1] >= 500_000_000: t += 1 - var z = BigUInt(0) # Remainder of the division - var q = BigUInt(0) + var z = BigUInt.ZERO # Remainder of the division + var q = BigUInt.ZERO var q_i: BigUInt for i in range(t - 2, -1, -1): @@ -2342,6 +2375,8 @@ fn floor_divide_burnikel_ziegler( normalized_a, bounds_y=((i - 1) * n, i * n), ) + + q.remove_leading_empty_words() return q^ @@ -2376,19 +2411,19 @@ fn floor_divide_two_by_one( return (q^, r^) else: - var a0 = BigUInt(a.words[0 : n // 2]) - var a1 = BigUInt(a.words[n // 2 : n]) - var a2 = BigUInt(a.words[n : n + n // 2]) - var a3 = BigUInt(a.words[n + n // 2 : n + n]) + var a0 = BigUInt.from_slice(a, bounds=(0, n // 2)) + var a1 = BigUInt.from_slice(a, bounds=(n // 2, n)) + var a2 = BigUInt.from_slice(a, bounds=(n, n + n // 2)) + var a3 = BigUInt.from_slice(a, bounds=(n + n // 2, n + n)) - var b0 = BigUInt(b.words[0 : n // 2]) - var b1 = BigUInt(b.words[n // 2 : n]) + var b0 = BigUInt.from_slice(b, bounds=(0, n // 2)) + var b1 = BigUInt.from_slice(b, bounds=(n // 2, n)) var q, r = floor_divide_three_by_two( a3, a2, a1, b1, b0, n // 2, cut_off ) # q is q1 - var r0 = BigUInt(r.words[0 : n // 2]) - var r1 = BigUInt(r.words[n // 2 : n]) + var r0 = BigUInt.from_slice(r, bounds=(0, n // 2)) + var r1 = BigUInt.from_slice(r, bounds=(n // 2, n)) var q0, s = floor_divide_three_by_two( r1, r0, a0, b1, b0, n // 2, cut_off ) @@ -2527,7 +2562,7 @@ fn floor_divide_slices_two_by_one( a_slice -= multiply_slices(q, b, (0, len(q.words)), bounds_b) return (q^, a_slice^) - elif (bounds_a[0] + n + n // 2 >= bounds_a[1]) or a.is_zero( + elif (bounds_a[0] + n + n // 2 >= bounds_a[1]) or a.is_zero_in_bounds( bounds=(bounds_a[0] + n + n // 2, bounds_a[1]) ): # If a3 is empty or zero @@ -2637,6 +2672,8 @@ fn floor_divide_slices_three_by_two( add_inplace_by_slice(r, b, bounds_y=bounds_b) r -= d + q.remove_leading_empty_words() + r.remove_leading_empty_words() return (q^, r^) @@ -2776,7 +2813,7 @@ fn ceil_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: # Apply floor division and check if there is a remainder var quotient = floor_divide(x1, x2) if quotient * x2 < x1: - quotient += BigUInt(UInt32(1)) + add_inplace_by_uint32(quotient, 1) return quotient^ @@ -2820,11 +2857,11 @@ fn floor_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: debug_assert[assert_mode="none"]( len(x1.words) == 1, "truncate_modulo(): leading zero words" ) - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero # CASE: Divisor is one - no remainder if x2.is_one(): - return BigUInt() # Always divisible with no remainder + return BigUInt.ZERO # Always divisible with no remainder # CASE: |dividend| < |divisor| - the remainder is the dividend itself if x1.compare(x2) < 0: @@ -2917,11 +2954,11 @@ fn ceil_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: debug_assert[assert_mode="none"]( len(x1.words) == 1, "ceil_modulo(): leading zero words" ) - return BigUInt() # Return zero + return BigUInt.ZERO # Return zero # CASE: Divisor is one - no remainder if x2.is_one(): - return BigUInt() # Always divisible with no remainder + return BigUInt.ZERO # Always divisible with no remainder # CASE: |dividend| < |divisor| - the remainder is the dividend itself if x1.compare(x2) < 0: @@ -2936,7 +2973,7 @@ fn ceil_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: debug_assert[assert_mode="none"]( len(remainder.words) == 1, "ceil_modulo(): leading zero words" ) - return BigUInt() # No remainder + return BigUInt.ZERO # No remainder else: return subtract(x2, remainder) @@ -3154,20 +3191,20 @@ fn power_of_10(n: Int) raises -> BigUInt: ) if n == 0: - return BigUInt(1) + return BigUInt.ONE # Handle small powers directly if n < 9: var value: UInt32 = 1 for _ in range(n): value *= 10 - return BigUInt(value) + return BigUInt.from_uint32_unsafe(value) # For larger powers, split into groups of 9 digits var words = n // 9 var remainder = n % 9 - var result = BigUInt(List[UInt32]()) + var result = BigUInt.ZERO # Add leading zeros for full power-of-billion words for _ in range(words): diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index d7477c7..a9aae22 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -23,7 +23,7 @@ operation dunders, and other dunders that implement traits, as well as mathematical methods that do not implement a trait. """ -from memory import UnsafePointer, memcpy +from memory import UnsafePointer, memcpy, memcmp import decimojo.biguint.arithmetics import decimojo.biguint.comparison @@ -151,8 +151,10 @@ struct BigUInt( fn __init__(out self, var words: List[UInt32]): """Initializes a BigUInt from a list of UInt32 words. - It does not verify whether the words are within the valid range - See `from_list()` for safer initialization. + If the list is empty, the BigUInt is initialized with value 0. + If there are trailing empty words, they are NOT removed. + This method does NOT check whether the words are smaller than + `999_999_999`. Args: words: A list of UInt32 words representing the coefficient. @@ -161,8 +163,10 @@ struct BigUInt( Notes: - This method does not check whether the words are smaller than - `999_999_999`. + If you want to remove trailing empty words and validate the words, + use `BigUInt.from_list_unsafe()`. + If you also want to validate the words and remove trailing empty words, + use `BigUInt.from_list()`. """ if len(words) == 0: self.words = List[UInt32](UInt32(0)) @@ -270,6 +274,7 @@ struct BigUInt( fn from_list(var 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. + If there are trailing empty words, they are removed. The words are validated to ensure they are smaller than `999_999_999`. Args: @@ -303,7 +308,28 @@ struct BigUInt( ) ) - return Self(words^) + var res = Self(words^) + res.remove_leading_empty_words() + return res^ + + @staticmethod + fn from_list_unsafe(var words: List[UInt32]) -> Self: + """Initializes a BigUInt from a list of UInt32 words without checks. + If the list is empty, the BigUInt is initialized with value 0. + If there are trailing empty words, they are removed. + The words are not validated to ensure they are smaller than a billion. + + 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. + """ + var result = Self(words=words^) + result.remove_leading_empty_words() + return result^ @staticmethod fn from_words(*words: UInt32) raises -> Self: @@ -381,7 +407,7 @@ struct BigUInt( return Self() # Now we can safely copy the words - result = BigUInt(words=List[UInt32](unsafe_uninit_length=n_words)) + result = BigUInt(unsafe_uninit_length=n_words) memcpy( dest=result.words._data, src=value.words._data + start_index, @@ -455,7 +481,8 @@ struct BigUInt( """Creates a BigUInt from an `UInt32` object. Notes: - UInt32 is special, so we have a separate method for it. + + UInt32 is special, so we have a separate method for it. """ # One word is enough if value <= 999_999_999: @@ -470,6 +497,12 @@ struct BigUInt( ) ) + @staticmethod + fn from_uint32_unsafe(unsafe_value: UInt32) -> Self: + """Creates a BigUInt from an `UInt32` object without checking the value. + """ + return Self(words=List[UInt32](unsafe_value)) + @staticmethod fn from_unsigned_integral_scalar[ dtype: DType, // @@ -961,6 +994,9 @@ struct BigUInt( return String("Unitilialized BigUInt") if self.is_zero(): + debug_assert( + len(self.words) == 1, "There are trailing empty words." + ) return String("0") var result = String("") @@ -1528,39 +1564,30 @@ struct BigUInt( fn is_zero(self) -> Bool: """Returns True if this BigUInt represents zero.""" # Yuhao ZHU: - # We should by design not have leading zero words so that we only need - # to check words[0] for zero. + # BigUInt are desgined to have no leading zero words, + # so that we only need to check words[0] for zero. # If there are leading zero words, it means that we have to loop over # all words to check if the number is zero. - # TODO: - # Currently, the BigUInt sub-package by design ensures no leading zeros. - # However, the BigDecimal sub-package may still lead to leading zeros - # When we refine the BigDecimal sub-package, we should ensure that - # BigUInt does not have leading zeros. - # - # debug_assert[assert_mode="none"]( - # len(self.words) == 1, - # "BigUInt should not contain leading zero words.", - # ) # 0 should have only one word by design + debug_assert[assert_mode="none"]( + (len(self.words) == 1) or (self.words[-1] != 0), + "biguint.BigUInt.is_zero(): ", + "BigUInt should not contain leading zero words.", + ) # 0 should have only one word by design - if self.words[0] != 0: - # Least significant word is not zero - return False - elif len(self.words) == 1: - # Least significant word is zero and there is no other word - return True - else: - # Least significant word is zero and there are other words - # Check if all other words are zero - for word in self.words[1:]: - if word != 0: - return False - else: - # All words are zero - return True + return len(self.words) == 1 and self.words._data[] == 0 + + # Yuhao ZHU: + # The following code is commented out because BigUInt is designed + # to have no leading zero words. + # We only need to check the first word. + # They are left here for reference. + # return (self.words._data[] == 0) and ( + # memcmp(self.words._data, self.words._data + 1, len(self.words) - 1) + # == 0 + # ) @always_inline - fn is_zero(self, bounds: Tuple[Int, Int]) -> Bool: + fn is_zero_in_bounds(self, bounds: Tuple[Int, Int]) -> Bool: """Returns True if this BigUInt slice represents zero. Args: @@ -1718,7 +1745,6 @@ struct BigUInt( var digit = word % 10 return UInt8(digit) - @always_inline fn number_of_digits(self) -> Int: """Returns the number of digits in the BigUInt. @@ -1727,6 +1753,9 @@ struct BigUInt( Zero has 1 digit. """ if self.is_zero(): + debug_assert( + len(self.words) == 1, "There are trailing empty words." + ) return 1 var result: Int = (len(self.words) - 1) * 9 @@ -1736,12 +1765,10 @@ struct BigUInt( last_word = last_word // 10 return result - @always_inline fn number_of_words(self) -> Int: """Returns the number of words in the BigInt.""" return len(self.words) - @always_inline fn number_of_trailing_zeros(self) -> Int: """Returns the number of trailing zeros in the BigUInt.""" var result: Int = 0 @@ -1778,7 +1805,7 @@ struct BigUInt( n_empty_words += 1 else: break - self.words.resize(len(self.words) - n_empty_words, UInt32(0)) + self.words.shrink(len(self.words) - n_empty_words) @always_inline fn remove_trailing_digits_with_rounding( diff --git a/src/decimojo/biguint/comparison.mojo b/src/decimojo/biguint/comparison.mojo index 5117bb9..d9730a2 100644 --- a/src/decimojo/biguint/comparison.mojo +++ b/src/decimojo/biguint/comparison.mojo @@ -34,17 +34,28 @@ fn compare(x1: BigUInt, x2: BigUInt) -> Int8: (2) 0 if x1 = x2. (3) -1 if x1 < x2. """ + debug_assert[assert_mode="none"]( + (len(x1.words) == 1) or (x1.words[-1] != 0), + "biguint.comparison.compare(): ", + "BigUInt x1 contains leading zero words.", + ) + debug_assert[assert_mode="none"]( + (len(x2.words) == 1) or (x2.words[-1] != 0), + "biguint.comparison.compare(): ", + "BigUInt x2 contains leading zero words.", + ) + # Compare the number of words if len(x1.words) > len(x2.words): - for i in range(len(x2.words), len(x1.words)): - # Check if the extra words in x1 are non-zero - if x1.words[i] != 0: - return Int8(1) + # for i in range(len(x2.words), len(x1.words)): + # # Check if the extra words in x1 are non-zero + # if x1.words[i] != 0: + return Int8(1) if len(x1.words) < len(x2.words): - for i in range(len(x1.words), len(x2.words)): - # Check if the extra words in x2 are non-zero - if x2.words[i] != 0: - return Int8(-1) + # for i in range(len(x1.words), len(x2.words)): + # # Check if the extra words in x2 are non-zero + # if x2.words[i] != 0: + return Int8(-1) # If the number of words that are not leading zeros are equal, # compare the words from the most significant to the least significant. diff --git a/src/decimojo/biguint/exponential.mojo b/src/decimojo/biguint/exponential.mojo index 0645f10..7aab595 100644 --- a/src/decimojo/biguint/exponential.mojo +++ b/src/decimojo/biguint/exponential.mojo @@ -17,6 +17,8 @@ """Implements exponential functions for the BigUInt type.""" import math +from memory import memset_zero + from decimojo.biguint.biguint import BigUInt import decimojo.biguint.arithmetics @@ -45,11 +47,11 @@ fn sqrt(x: BigUInt) -> BigUInt: # Use built-in methods for small numbers (up to 2 words) if len(x.words) == 1: if x.words[0] == 0: - return BigUInt(UInt32(0)) + return BigUInt.ZERO elif x.words[0] == 1: - return BigUInt(UInt32(1)) + return BigUInt.ONE else: - return BigUInt(List[UInt32](math.sqrt(x.words[0]))) + return BigUInt.from_uint32_unsafe(math.sqrt(x.words[0])) elif len(x.words) == 2: var res = UInt32( @@ -60,7 +62,7 @@ fn sqrt(x: BigUInt) -> BigUInt: ).reduce_add() ) ) - return BigUInt(List[UInt32](res)) + return BigUInt.from_uint32_unsafe(res) # Use Newton's method for larger numbers else: # len(x.words) > 2 @@ -177,11 +179,12 @@ fn sqrt_initial_guess(x: BigUInt) -> BigUInt: if nsw > 999_999_999: # Cap at max word value nsw = 999_999_999 - words = List[UInt32](length=n_words + 1, fill=UInt32(0)) + result = BigUInt(unsafe_uninit_length=n_words + 1) + memset_zero(ptr=result.words._data, count=n_words + 1) # Boundary checks are not needed here because len(x.words) > 2 - words.unsafe_set(n_words, msw_sqrt) - words.unsafe_set( + result.words.unsafe_set(n_words, msw_sqrt) + result.words.unsafe_set( n_words - 1, UInt32(nsw) ) # Set the next significant word contribution - return BigUInt(words^) + return result^ diff --git a/tests/biguint/test_biguint_arithmetics.mojo b/tests/biguint/test_biguint_arithmetics.mojo index 8374f51..968fed9 100644 --- a/tests/biguint/test_biguint_arithmetics.mojo +++ b/tests/biguint/test_biguint_arithmetics.mojo @@ -57,6 +57,7 @@ fn test_biguint_arithmetics() raises: rhs=test_case.expected, msg=test_case.description, ) + print("BigUInt subtraction tests passed!") print("------------------------------------------------------") print("Testing BigUInt multiplication...") @@ -140,3 +141,11 @@ fn test_biguint_truncate_divide_random_numbers_against_python() raises: + python_result, ) print("BigUInt truncate division tests passed!") + + +fn main() raises: + test_biguint_arithmetics() + test_biguint_truncate_divide() + test_biguint_truncate_divide_random_numbers_against_python() + print("All BigUInt arithmetic tests passed!") + print("------------------------------------------------------") diff --git a/tests/biguint/test_biguint_exponential.mojo b/tests/biguint/test_biguint_exponential.mojo index 70e51ff..4a6f198 100644 --- a/tests/biguint/test_biguint_exponential.mojo +++ b/tests/biguint/test_biguint_exponential.mojo @@ -59,3 +59,10 @@ fn test_biguint_sqrt_random_numbers_against_python() raises: + python_result, ) print("BigUInt sqrt tests passed!") + + +fn main() raises: + test_biguint_sqrt() + test_biguint_sqrt_random_numbers_against_python() + print("All BigUInt exponential tests passed!") + print("------------------------------------------------------")