Skip to content

Commit 7448804

Browse files
authored
[decimal] Refine Decimal struct + improve docstrings (#41)
This pull request includes several changes to the `benches/bench.mojo` file, new benchmarking functions for `Decimal.from_int`, updates to the `mojoproject.toml` file, and improvements to the `Decimal` struct in `src/decimojo/decimal.mojo`. The most important changes include adding new benchmark cases, updating the `mojoproject.toml` with new test and benchmark commands, and refining the `Decimal` struct's methods and documentation. ### New Benchmark Functions: * Added comprehensive benchmarks for the `Decimal.from_int` constructor in `benches/bench_from_int.mojo`. This includes performance comparisons against Python's decimal module with 20 diverse test cases. ### Updates to Benchmark and Test Commands: * Updated `benches/bench.mojo` to include new benchmarks for `bench_from_int` and `bench_root`. * Modified `mojoproject.toml` to add new test and benchmark commands for `test_from_int` and `bench_from_int`. ### Improvements to `Decimal` Struct: * Refined the `Decimal` struct in `src/decimojo/decimal.mojo` by updating method documentation and adding a new `copy` method and a new `clone` method. These changes enhance the benchmarking capabilities, improve the organization of test commands, and refine the `Decimal` struct for better clarity and functionality.
1 parent 8ddf9df commit 7448804

18 files changed

+1287
-837
lines changed

benches/bench.mojo

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ from bench_divide import main as bench_divide
55
from bench_sqrt import main as bench_sqrt
66
from bench_from_float import main as bench_from_float
77
from bench_from_string import main as bench_from_string
8+
from bench_from_int import main as bench_from_int
89
from bench_round import main as bench_round
910
from bench_comparison import main as bench_comparison
1011
from bench_exp import main as bench_exp
1112
from bench_ln import main as bench_ln
1213
from bench_power import main as bench_power
14+
from bench_root import main as bench_root
1315

1416

1517
fn main() raises:
@@ -20,8 +22,10 @@ fn main() raises:
2022
bench_sqrt()
2123
bench_from_float()
2224
bench_from_string()
25+
bench_from_int()
2326
bench_round()
2427
bench_comparison()
2528
bench_exp()
2629
bench_ln()
2730
bench_power()
31+
bench_root()

benches/bench_from_int.mojo

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

0 commit comments

Comments
 (0)