Skip to content

Commit 074bcbe

Browse files
authored
[bigint] Implement truncate_divide and truncate_modulo for BigInt (#53)
This pull request implements `truncate_divide` and `truncate_modulo` for `BigInt`, and introduces comprehensive benchmarking for BigInt operations, including addition and truncate division, and updates the project configuration to include these benchmarks.
1 parent 9a4cdee commit 074bcbe

File tree

9 files changed

+1563
-2
lines changed

9 files changed

+1563
-2
lines changed

benches/bigint/bench.mojo

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from bench_bigint_add import main as bench_add
2+
from bench_bigint_truncate_divide import main as bench_truncate_divide
3+
4+
5+
fn main() raises:
6+
bench_add()
7+
bench_truncate_divide()
Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
"""
2+
Comprehensive benchmarks for BigInt addition.
3+
Compares performance against Python's built-in int with 20 diverse test cases.
4+
"""
5+
6+
from decimojo.bigint.bigint import BigInt
7+
import decimojo.bigint.arithmetics
8+
from python import Python, PythonObject
9+
from time import perf_counter_ns
10+
import time
11+
import os
12+
from collections import List
13+
14+
15+
fn open_log_file() raises -> PythonObject:
16+
"""
17+
Creates and opens a log file with a timestamp in the filename.
18+
19+
Returns:
20+
A file object opened for writing.
21+
"""
22+
var python = Python.import_module("builtins")
23+
var datetime = Python.import_module("datetime")
24+
25+
# Create logs directory if it doesn't exist
26+
var log_dir = "./logs"
27+
if not os.path.exists(log_dir):
28+
os.makedirs(log_dir)
29+
30+
# Generate a timestamp for the filename
31+
var timestamp = String(datetime.datetime.now().isoformat())
32+
var log_filename = log_dir + "/benchmark_bigint_add_" + timestamp + ".log"
33+
34+
print("Saving benchmark results to:", log_filename)
35+
return python.open(log_filename, "w")
36+
37+
38+
fn log_print(msg: String, log_file: PythonObject) raises:
39+
"""
40+
Prints a message to both the console and the log file.
41+
42+
Args:
43+
msg: The message to print.
44+
log_file: The file object to write to.
45+
"""
46+
print(msg)
47+
log_file.write(msg + "\n")
48+
log_file.flush() # Ensure the message is written immediately
49+
50+
51+
fn run_benchmark_add(
52+
name: String,
53+
value1: String,
54+
value2: String,
55+
iterations: Int,
56+
log_file: PythonObject,
57+
mut speedup_factors: List[Float64],
58+
) raises:
59+
"""
60+
Run a benchmark comparing Mojo BigInt addition with Python int addition.
61+
62+
Args:
63+
name: Name of the benchmark case.
64+
value1: String representation of first operand.
65+
value2: String representation of second operand.
66+
iterations: Number of iterations to run.
67+
log_file: File object for logging results.
68+
speedup_factors: Mojo List to store speedup factors for averaging.
69+
"""
70+
log_print("\nBenchmark: " + name, log_file)
71+
log_print("First operand: " + value1, log_file)
72+
log_print("Second operand: " + value2, log_file)
73+
74+
# Set up Mojo and Python values
75+
var mojo_value1 = BigInt(value1)
76+
var mojo_value2 = BigInt(value2)
77+
var py = Python.import_module("builtins")
78+
var py_value1 = py.int(value1)
79+
var py_value2 = py.int(value2)
80+
81+
# Execute the operations once to verify correctness
82+
var mojo_result = mojo_value1 + mojo_value2
83+
var py_result = py_value1 + py_value2
84+
85+
# Display results for verification
86+
log_print("Mojo result: " + String(mojo_result), log_file)
87+
log_print("Python result: " + String(py_result), log_file)
88+
89+
# Benchmark Mojo implementation
90+
var t0 = perf_counter_ns()
91+
for _ in range(iterations):
92+
_ = mojo_value1 + mojo_value2
93+
var mojo_time = (perf_counter_ns() - t0) / iterations
94+
if mojo_time == 0:
95+
mojo_time = 1 # Prevent division by zero
96+
97+
# Benchmark Python implementation
98+
t0 = perf_counter_ns()
99+
for _ in range(iterations):
100+
_ = py_value1 + py_value2
101+
var python_time = (perf_counter_ns() - t0) / iterations
102+
103+
# Calculate speedup factor
104+
var speedup = python_time / mojo_time
105+
speedup_factors.append(Float64(speedup))
106+
107+
# Print results with speedup comparison
108+
log_print(
109+
"Mojo addition: " + String(mojo_time) + " ns per iteration",
110+
log_file,
111+
)
112+
log_print(
113+
"Python addition: " + String(python_time) + " ns per iteration",
114+
log_file,
115+
)
116+
log_print("Speedup factor: " + String(speedup), log_file)
117+
118+
119+
fn main() raises:
120+
# Open log file
121+
var log_file = open_log_file()
122+
var datetime = Python.import_module("datetime")
123+
124+
# Create a Mojo List to store speedup factors for averaging later
125+
var speedup_factors = List[Float64]()
126+
127+
# Display benchmark header with system information
128+
log_print("=== DeciMojo BigInt Addition Benchmark ===", log_file)
129+
log_print("Time: " + String(datetime.datetime.now().isoformat()), log_file)
130+
131+
# Try to get system info
132+
try:
133+
var platform = Python.import_module("platform")
134+
log_print(
135+
"System: "
136+
+ String(platform.system())
137+
+ " "
138+
+ String(platform.release()),
139+
log_file,
140+
)
141+
log_print("Processor: " + String(platform.processor()), log_file)
142+
log_print(
143+
"Python version: " + String(platform.python_version()), log_file
144+
)
145+
except:
146+
log_print("Could not retrieve system information", log_file)
147+
148+
var iterations = 1000
149+
150+
# Define benchmark cases
151+
log_print(
152+
"\nRunning addition benchmarks with "
153+
+ String(iterations)
154+
+ " iterations each",
155+
log_file,
156+
)
157+
158+
# Case 1: Small integer addition
159+
run_benchmark_add(
160+
"Small integer addition",
161+
"42",
162+
"58",
163+
iterations,
164+
log_file,
165+
speedup_factors,
166+
)
167+
168+
# Case 2: Medium integer addition
169+
run_benchmark_add(
170+
"Medium integer addition",
171+
"12345",
172+
"67890",
173+
iterations,
174+
log_file,
175+
speedup_factors,
176+
)
177+
178+
# Case 3: Large integer addition
179+
run_benchmark_add(
180+
"Large integer addition",
181+
"9999999999", # 10 billion
182+
"1234567890",
183+
iterations,
184+
log_file,
185+
speedup_factors,
186+
)
187+
188+
# Case 4: Very large integer addition
189+
run_benchmark_add(
190+
"Very large integer addition",
191+
"9" * 50, # 50-digit number
192+
"1" + "0" * 49, # 10^49
193+
iterations,
194+
log_file,
195+
speedup_factors,
196+
)
197+
198+
# Case 5: Negative number addition
199+
run_benchmark_add(
200+
"Negative number addition",
201+
"-12345",
202+
"-67890",
203+
iterations,
204+
log_file,
205+
speedup_factors,
206+
)
207+
208+
# Case 6: Mixed sign addition (positive + negative)
209+
run_benchmark_add(
210+
"Mixed sign addition (positive + negative)",
211+
"12345",
212+
"-6789",
213+
iterations,
214+
log_file,
215+
speedup_factors,
216+
)
217+
218+
# Case 7: Mixed sign addition (negative + positive)
219+
run_benchmark_add(
220+
"Mixed sign addition (negative + positive)",
221+
"-12345",
222+
"67890",
223+
iterations,
224+
log_file,
225+
speedup_factors,
226+
)
227+
228+
# Case 8: Addition with zero
229+
run_benchmark_add(
230+
"Addition with zero",
231+
"123456789",
232+
"0",
233+
iterations,
234+
log_file,
235+
speedup_factors,
236+
)
237+
238+
# Case 9: Addition resulting in zero (a + (-a))
239+
run_benchmark_add(
240+
"Addition resulting in zero",
241+
"987654321",
242+
"-987654321",
243+
iterations,
244+
log_file,
245+
speedup_factors,
246+
)
247+
248+
# Case 10: Power-of-10 addition
249+
run_benchmark_add(
250+
"Power-of-10 addition",
251+
"1" + "0" * 20, # 10^20
252+
"1" + "0" * 15, # 10^15
253+
iterations,
254+
log_file,
255+
speedup_factors,
256+
)
257+
258+
# Case 11: Fibonacci numbers addition
259+
run_benchmark_add(
260+
"Fibonacci numbers addition",
261+
"10946", # Fib(21)
262+
"17711", # Fib(22)
263+
iterations,
264+
log_file,
265+
speedup_factors,
266+
)
267+
268+
# Case 12: Extreme large integer addition
269+
run_benchmark_add(
270+
"Extreme large integer addition",
271+
"9" * 100, # 100-digit number
272+
"1" + "0" * 99, # 10^99
273+
iterations,
274+
log_file,
275+
speedup_factors,
276+
)
277+
278+
# Case 13: Addition with uneven length numbers
279+
run_benchmark_add(
280+
"Addition with uneven length numbers",
281+
"1" + "0" * 30, # 10^30
282+
"9" * 10, # 10-digit number
283+
iterations,
284+
log_file,
285+
speedup_factors,
286+
)
287+
288+
# Case 14: Addition with uneven length numbers (reversed)
289+
run_benchmark_add(
290+
"Addition with uneven length numbers (reversed)",
291+
"9" * 10, # 10-digit number
292+
"1" + "0" * 30, # 10^30
293+
iterations,
294+
log_file,
295+
speedup_factors,
296+
)
297+
298+
# Case 15: Addition with many carries
299+
run_benchmark_add(
300+
"Addition with many carries",
301+
"9" * 25, # 25 nines
302+
"1",
303+
iterations,
304+
log_file,
305+
speedup_factors,
306+
)
307+
308+
# Case 16: Prime number addition
309+
run_benchmark_add(
310+
"Prime number addition",
311+
"2147483647", # Mersenne prime (2^31 - 1)
312+
"2305843009213693951", # Mersenne prime (2^61 - 1)
313+
iterations,
314+
log_file,
315+
speedup_factors,
316+
)
317+
318+
# Case 17: Addition of numbers near Int64 limit
319+
run_benchmark_add(
320+
"Addition near Int64 limit",
321+
"9223372036854775807", # Int64.MAX
322+
"9223372036854775807", # Int64.MAX
323+
iterations,
324+
log_file,
325+
speedup_factors,
326+
)
327+
328+
# Case 18: Addition of numbers near UInt64 limit
329+
run_benchmark_add(
330+
"Addition near UInt64 limit",
331+
"18446744073709551615", # UInt64.MAX
332+
"1",
333+
iterations,
334+
log_file,
335+
speedup_factors,
336+
)
337+
338+
# Case 19: Addition of repeated digits
339+
run_benchmark_add(
340+
"Addition of repeated digits",
341+
"1" * 40, # 40 ones
342+
"9" * 40, # 40 nines
343+
iterations,
344+
log_file,
345+
speedup_factors,
346+
)
347+
348+
# Case 20: Addition of powers of two
349+
run_benchmark_add(
350+
"Addition of powers of two",
351+
"340282366920938463463374607431768211456", # 2^128
352+
"115792089237316195423570985008687907853269984665640564039457584007913129639936", # 2^256
353+
iterations,
354+
log_file,
355+
speedup_factors,
356+
)
357+
358+
# Calculate average speedup factor
359+
var sum_speedup: Float64 = 0.0
360+
for i in range(len(speedup_factors)):
361+
sum_speedup += speedup_factors[i]
362+
var average_speedup = sum_speedup / Float64(len(speedup_factors))
363+
364+
# Display summary
365+
log_print("\n=== BigInt Addition Benchmark Summary ===", log_file)
366+
log_print("Benchmarked: 20 different addition cases", log_file)
367+
log_print(
368+
"Each case ran: " + String(iterations) + " iterations", log_file
369+
)
370+
log_print("Average speedup: " + String(average_speedup) + "×", log_file)
371+
372+
# List all speedup factors
373+
log_print("\nIndividual speedup factors:", log_file)
374+
for i in range(len(speedup_factors)):
375+
log_print(
376+
String("Case {}: {}×").format(i + 1, round(speedup_factors[i], 2)),
377+
log_file,
378+
)
379+
380+
# Close the log file
381+
log_file.close()
382+
print("Benchmark completed. Log file closed.")

benches/bigint/bench_bigint_multiply.mojo

Whitespace-only changes.

0 commit comments

Comments
 (0)