Skip to content

Commit b30cddc

Browse files
authored
[integer] Implement Burnikel-Ziegler fast recursive division algorithm for BigUInt (#103)
This PR implements the Burnikel-Ziegler fast recursive division algorithm which significantly improves the performance of large-number divisions (particularly when there are thousands of words). It achieves a realized time complexity of O(n^1.585) (3x time when digits are doubled) instead of the O(n^2) (4x time when digits are doubled). ## Major Division Algorithm Improvements ### 1. **Burnikel-Ziegler Algorithm Implementation** - **New fast recursive division algorithm** for large numbers - Added `floor_divide_burnikel_ziegler()` function with cutoff at 64 words - Implemented supporting functions: - `floor_divide_two_by_one()` - `floor_divide_three_by_two()` - `floor_divide_three_by_two_uint32()` - `floor_divide_four_by_two_uint32()` ### 2. **Division Strategy Refactoring** - **Renamed** `floor_divide_general()` → `floor_divide_school()` - **Smart algorithm selection** in `floor_divide()`: - Small numbers (≤64 words): Use schoolbook division - Large numbers (>64 words): Use Burnikel-Ziegler algorithm - **Extracted normalization logic** into `calculate_normalization_factor()` helper function ## Benchmark and Testing Enhancements ### 3. **Extended Test Coverage** - **Increased test range** from 2^16 to 2^18 words (262,144 words) - **Added validation** to compare Mojo vs Python results - **Reduced iterations** for large number tests (100 → 10) - **Added new test cases** for extremely large divisions ### 4. **Improved Benchmarking** - **Extended Python string limit** from 1M to 10M digits - **Enhanced logging** with result validation - **Better error handling** for mismatched results ## Code Quality Improvements ### 5. **Constructor Safety** - **Fixed BigUInt constructor** to handle empty word lists - **Added validation** to prevent zero-length word arrays ### 6. **Build Process** - **Improved formatting command** to target specific directories - **Better project organization** ## Performance Impact The main achievement is **dramatically improved performance for large number division**: - **O(n²) → O(n^1.585)** complexity reduction using Burnikel-Ziegler - **Automatic algorithm selection** based on input size - **Maintains compatibility** with existing API This is a significant algorithmic improvement that should provide substantial speedups for large number operations while maintaining correctness for all input sizes.
1 parent d9bb737 commit b30cddc

5 files changed

Lines changed: 456 additions & 64 deletions

File tree

benches/biguint/bench_biguint_divide_complexity.mojo

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ===----------------------------------------------------------------------=== #
22
# Benchmark for BigUInt division time complexity analysis
3-
# Testing word sizes from 32 to 65536 words (powers of 2)
3+
# Testing word sizes from 32 to 2**18 words (powers of 2)
44
# ===----------------------------------------------------------------------=== #
55

66
from time import perf_counter_ns
@@ -157,7 +157,7 @@ fn main() raises:
157157
)
158158
log_print("", log_file)
159159

160-
# Test sizes: powers of 2 from 32 to 65536
160+
# Test sizes: powers of 2 from 32 to 2**18 words
161161
var test_sizes = List[Int]()
162162
test_sizes.append(32)
163163
test_sizes.append(64)
@@ -171,6 +171,8 @@ fn main() raises:
171171
test_sizes.append(16384)
172172
test_sizes.append(32768)
173173
test_sizes.append(65536)
174+
test_sizes.append(131072) # 2^17
175+
test_sizes.append(262144) # 2^18
174176

175177
# Test Case 1: Large / Small division (2n / n)
176178
log_print("=== TEST CASE 1: LARGE / SMALL DIVISION (2n / n) ===", log_file)
@@ -183,7 +185,7 @@ fn main() raises:
183185
for i in range(len(test_sizes)):
184186
var divisor_size = test_sizes[i]
185187
var dividend_size = divisor_size * 2
186-
if dividend_size <= 65536: # Stay within our limit
188+
if dividend_size <= 2**18: # Stay within our limit
187189
var avg_time = benchmark_divide_at_size(
188190
dividend_size, divisor_size, 5, log_file
189191
)
@@ -206,7 +208,7 @@ fn main() raises:
206208
for i in range(len(test_sizes)):
207209
var divisor_size = test_sizes[i]
208210
var dividend_size = divisor_size * 4
209-
if dividend_size <= 65536: # Stay within our limit
211+
if dividend_size <= 2**18: # Stay within our limit
210212
var avg_time = benchmark_divide_at_size(
211213
dividend_size, divisor_size, 5, log_file
212214
)
@@ -274,7 +276,7 @@ fn main() raises:
274276
for i in range(len(test_sizes)):
275277
var divisor_size = test_sizes[i]
276278
var dividend_size = divisor_size * 4
277-
if dividend_size <= 65536: # Only show results within our limit
279+
if dividend_size <= 2**18: # Only show results within our limit
278280
var time_taken = very_large_small_results[i]
279281

280282
if time_taken > 0.0: # Only show valid results

benches/biguint/bench_biguint_truncate_divide.mojo

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn open_log_file() raises -> PythonObject:
2323
var python = Python.import_module("builtins")
2424
var datetime = Python.import_module("datetime")
2525
var pysys = Python.import_module("sys")
26-
pysys.set_int_max_str_digits(1000000)
26+
pysys.set_int_max_str_digits(10000000)
2727

2828
# Create logs directory if it doesn't exist
2929
var log_dir = "./logs"
@@ -88,6 +88,15 @@ fn run_benchmark_truncate_divide(
8888
var mojo_result = mojo_dividend // mojo_divisor
8989
var py_result = py_dividend // py_divisor
9090

91+
if String(mojo_result) != String(py_result):
92+
log_print(
93+
"Error: Mojo and Python results do not match!",
94+
log_file,
95+
)
96+
log_print("Mojo result: " + String(mojo_result), log_file)
97+
log_print("Python result: " + String(py_result), log_file)
98+
return # Skip this benchmark case if results don't match
99+
91100
# Display results for verification
92101
log_print("Mojo result: " + String(mojo_result), log_file)
93102
log_print("Python result: " + String(py_result), log_file)
@@ -155,6 +164,7 @@ fn main() raises:
155164
log_print("Could not retrieve system information", log_file)
156165

157166
var iterations = 100
167+
var iterations_large_numbers = 10
158168

159169
# Define benchmark cases (all positive numbers for BigUInt)
160170
log_print(
@@ -492,20 +502,40 @@ fn main() raises:
492502

493503
# Case 31: Division of large numbers
494504
run_benchmark_truncate_divide(
495-
"Division of large numbers (1000 words vs 100 digits)",
505+
"Division of large numbers (5000 words vs words digits)",
496506
"316227766_016824890_583648059_893174009_579947593" * 1000,
497507
"141421356_237309504_880168872_420969807_856967187" * 100,
498-
3,
508+
iterations_large_numbers,
499509
log_file,
500510
speedup_factors,
501511
)
502512

503513
# Case 32: Division of large numbers
504514
run_benchmark_truncate_divide(
505-
"Division of large numbers (10000 words vs 1234 digits)",
515+
"Division of large numbers (50000 words vs 6170 words)",
506516
"316227766_016824890_583648059_893174009_579947593" * 10000,
507517
"141421356_237309504_880168872_420969807_856967187" * 1234,
508-
3,
518+
iterations_large_numbers,
519+
log_file,
520+
speedup_factors,
521+
)
522+
523+
# Case 33: Division of large numbers
524+
run_benchmark_truncate_divide(
525+
"Division of large numbers (2**16 words vs 2**12 digits)",
526+
"123456789" * 2**16,
527+
"987654321" * 2**12,
528+
iterations_large_numbers,
529+
log_file,
530+
speedup_factors,
531+
)
532+
533+
# Case 34: Division of large numbers
534+
run_benchmark_truncate_divide(
535+
"Division of large numbers (2**18 words vs 2**14 digits)",
536+
"123456789" * 2**18,
537+
"987654321" * 2**14,
538+
iterations_large_numbers,
509539
log_file,
510540
speedup_factors,
511541
)

pixi.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ max = "==25.4"
1414

1515
[tasks]
1616
# format the code
17-
format = "pixi run mojo format ./"
17+
format = "pixi run mojo format ./src && pixi run mojo format ./benches && pixi run mojo format ./tests && pixi run mojo format ./docs"
1818

1919
# compile the package
2020
package_decimojo = "pixi run mojo package src/decimojo && cp decimojo.mojopkg tests/ && cp decimojo.mojopkg benches/ && rm decimojo.mojopkg"

0 commit comments

Comments
 (0)