Skip to content

Conversation

@forfudan
Copy link
Owner

@forfudan forfudan commented Mar 11, 2025

This PR enhances the round() function and adds comprehensive benchmarks against Python's decimal package. The round() function now longer rely on scale_up and scale_down functions. Add checks on Decimal constructors to ensure that scale is between 0 and 28.

Fix wrong trailing zeros of sqrt().

@forfudan forfudan changed the title [decimojo] Enhance round() function and improve the performance [decimojo][fix] Enhance round() function and improve the performance + fix sqrt() Mar 11, 2025
@forfudan forfudan merged commit c2823a4 into main Mar 11, 2025
2 checks passed
@forfudan forfudan deleted the round branch March 11, 2025 19:53
@forfudan
Copy link
Owner Author

Before change: 57a7c56

=== DeciMojo Rounding Benchmark ===
Time: 2025-03-10T21:04:27.660441
System: Darwin 24.3.0
Processor: arm
Python version: 3.12.9
Python decimal precision: 28
Mojo decimal precision: 28

Running rounding benchmarks with 1000 iterations each

Benchmark:       Standard rounding to 2 places
Decimal:         123.456789
Round to:        2 places
Mojo result:     123.46
Python result:   123.46
Mojo Decimal:    232.0 ns per iteration
Python Decimal:  7298.0 ns per iteration
Speedup factor:  31.45689655172414

Benchmark:       Banker's rounding with even preceding digit
Decimal:         10.125
Round to:        2 places
Mojo result:     10.12
Python result:   10.12
Mojo Decimal:    104.0 ns per iteration
Python Decimal:  7421.0 ns per iteration
Speedup factor:  71.35576923076923

Benchmark:       Banker's rounding with odd preceding digit
Decimal:         10.135
Round to:        2 places
Mojo result:     10.14
Python result:   10.14
Mojo Decimal:    89.0 ns per iteration
Python Decimal:  6963.0 ns per iteration
Speedup factor:  78.23595505617978

Benchmark:       Rounding with less than half
Decimal:         10.124
Round to:        2 places
Mojo result:     10.12
Python result:   10.12
Mojo Decimal:    67.0 ns per iteration
Python Decimal:  6593.0 ns per iteration
Speedup factor:  98.40298507462687

Benchmark:       Rounding with more than half
Decimal:         10.126
Round to:        2 places
Mojo result:     10.13
Python result:   10.13
Mojo Decimal:    66.0 ns per iteration
Python Decimal:  7026.0 ns per iteration
Speedup factor:  106.45454545454545

Benchmark:       Rounding to whole number
Decimal:         123.456
Round to:        0 places
Mojo result:     123
Python result:   123
Mojo Decimal:    199.0 ns per iteration
Python Decimal:  6989.0 ns per iteration
Speedup factor:  35.120603015075375

Benchmark:       Rounding negative number
Decimal:         -123.456
Round to:        2 places
Mojo result:     -123.46
Python result:   -123.46
Mojo Decimal:    78.0 ns per iteration
Python Decimal:  6871.0 ns per iteration
Speedup factor:  88.08974358974359

Benchmark:       Rounding to tens (negative places)
Decimal:         123.456
Round to:        -1 places
Mojo result:     123
Python result:   123
Mojo Decimal:    210.0 ns per iteration
Python Decimal:  6865.0 ns per iteration
Speedup factor:  32.69047619047619

Benchmark:       Rounding to hundreds (negative places)
Decimal:         1234.56
Round to:        -2 places
Mojo result:     1235
Python result:   1235
Mojo Decimal:    139.0 ns per iteration
Python Decimal:  7055.0 ns per iteration
Speedup factor:  50.75539568345324

Benchmark:       Rounding very small number
Decimal:         0.0000001234
Round to:        10 places
Mojo result:     0.0000001234
Python result:   1.234E-7
Mojo Decimal:    1.0 ns per iteration
Python Decimal:  6985.0 ns per iteration
Speedup factor:  6985.0

Benchmark:       Rounding already rounded number
Decimal:         123.45
Round to:        2 places
Mojo result:     123.45
Python result:   123.45
Mojo Decimal:    1.0 ns per iteration
Python Decimal:  7057.0 ns per iteration
Speedup factor:  7057.0

Benchmark:       Rounding to high precision (20 places)
Decimal:         0.12345678901234567890123
Round to:        20 places
Mojo result:     0.12345678901234567890
Python result:   0.12345678901234567890
Mojo Decimal:    239.0 ns per iteration
Python Decimal:  7254.0 ns per iteration
Speedup factor:  30.351464435146443

Benchmark:       Rounding to more places than input (10)
Decimal:         123.456
Round to:        10 places
Mojo result:     123.4560000000
Python result:   123.4560000000
Mojo Decimal:    1.0 ns per iteration
Python Decimal:  6848.0 ns per iteration
Speedup factor:  6848.0

Benchmark:       Rounding number with trailing 9's
Decimal:         9.999
Round to:        2 places
Mojo result:     10.00
Python result:   10.00
Mojo Decimal:    103.0 ns per iteration
Python Decimal:  7186.0 ns per iteration
Speedup factor:  69.76699029126213

Benchmark:       Rounding requiring carry propagation
Decimal:         9.99
Round to:        1 places
Mojo result:     10.0
Python result:   10.0
Mojo Decimal:    76.0 ns per iteration
Python Decimal:  6987.0 ns per iteration
Speedup factor:  91.9342105263158

Benchmark:       Rounding exactly half with even preceding digit
Decimal:         2.5
Round to:        0 places
Mojo result:     2
Python result:   2
Mojo Decimal:    83.0 ns per iteration
Python Decimal:  6967.0 ns per iteration
Speedup factor:  83.93975903614458

Benchmark:       Rounding exactly half with odd preceding digit
Decimal:         3.5
Round to:        0 places
Mojo result:     4
Python result:   4
Mojo Decimal:    79.0 ns per iteration
Python Decimal:  7376.0 ns per iteration
Speedup factor:  93.36708860759494

Benchmark:       Rounding value close to MAX
Decimal:         12345678901234567889.87655
Round to:        2 places
Mojo result:     12345678901234567889.88
Python result:   12345678901234567889.88
Mojo Decimal:    226.0 ns per iteration
Python Decimal:  7221.0 ns per iteration
Speedup factor:  31.951327433628318

Benchmark:       Rounding minimum positive value
Decimal:         0.0000000000000000000000000001
Round to:        28 places
Mojo result:     0.0000000000000000000000000001
Python result:   1E-28
Mojo Decimal:    1.0 ns per iteration
Python Decimal:  7484.0 ns per iteration
Speedup factor:  7484.0

Benchmark:       Rounding engineering notation value
Decimal:         123.456789
Round to:        4 places
Mojo result:     123.4568
Python result:   123.4568
Mojo Decimal:    136.0 ns per iteration
Python Decimal:  7169.0 ns per iteration
Speedup factor:  52.713235294117645

Benchmark:       Rounding number with long integer part
Decimal:         12345678901234567.8901
Round to:        2 places
Mojo result:     12345678901234567.89
Python result:   12345678901234567.89
Mojo Decimal:    132.0 ns per iteration
Python Decimal:  7057.0 ns per iteration
Speedup factor:  53.46212121212121

Benchmark:       Rounding number just below half
Decimal:         10.12499999999999999
Round to:        2 places
Mojo result:     10.12
Python result:   10.12
Mojo Decimal:    371.0 ns per iteration
Python Decimal:  7216.0 ns per iteration
Speedup factor:  19.450134770889488

Benchmark:       Rounding number just above half
Decimal:         10.12500000000000001
Round to:        2 places
Mojo result:     10.13
Python result:   10.13
Mojo Decimal:    380.0 ns per iteration
Python Decimal:  6932.0 ns per iteration
Speedup factor:  18.242105263157896

Benchmark:       Rounding to maximum precision (28)
Decimal:         0.1111111111111111111111111111
Round to:        28 places
Mojo result:     0.1111111111111111111111111111
Python result:   0.1111111111111111111111111111
Mojo Decimal:    1.0 ns per iteration
Python Decimal:  7827.0 ns per iteration
Speedup factor:  7827.0

Benchmark:       Rounding zero
Decimal:         0
Round to:        10 places
Mojo result:     0.0000000000
Python result:   0E-10
Mojo Decimal:    1.0 ns per iteration
Python Decimal:  7371.0 ns per iteration
Speedup factor:  7371.0

Benchmark:       Rounding Pi to various places
Decimal:         3.141592653589793238462643383
Round to:        15 places
Mojo result:     3.141592653589793
Python result:   3.141592653589793
Mojo Decimal:    493.0 ns per iteration
Python Decimal:  7105.0 ns per iteration
Speedup factor:  14.411764705882353

Benchmark:       Rounding negative with carry propagation
Decimal:         -9.99
Round to:        1 places
Mojo result:     -10.0
Python result:   -10.0
Mojo Decimal:    76.0 ns per iteration
Python Decimal:  7324.0 ns per iteration
Speedup factor:  96.36842105263158

Benchmark:       Rounding with exact .5 and zeros after
Decimal:         1.5000000000000000000
Round to:        0 places
Mojo result:     2
Python result:   2
Mojo Decimal:    522.0 ns per iteration
Python Decimal:  7193.0 ns per iteration
Speedup factor:  13.779693486590038

Benchmark:       Rounding with exact .5 and non-zeros after
Decimal:         1.50000000000000000001
Round to:        0 places
Mojo result:     2
Python result:   2
Mojo Decimal:    532.0 ns per iteration
Python Decimal:  7492.0 ns per iteration
Speedup factor:  14.082706766917294

Benchmark:       Rounding extremely close to MAX
Decimal:         123456789012345677.999999999
Round to:        8 places
Mojo result:     123456789012345678.00000000
Python result:   123456789012345678.00000000
Mojo Decimal:    105.0 ns per iteration
Python Decimal:  7490.0 ns per iteration
Speedup factor:  71.33333333333333

Benchmark:       Random decimal with various digits
Decimal:         7.389465718934026719043
Round to:        12 places
Mojo result:     7.389465718934
Python result:   7.389465718934
Mojo Decimal:    482.0 ns per iteration
Python Decimal:  6721.0 ns per iteration
Speedup factor:  13.943983402489627

Benchmark:       Number with alternating digits
Decimal:         1.010101010101010101010101
Round to:        18 places
Mojo result:     1.010101010101010101
Python result:   1.010101010101010101
Mojo Decimal:    283.0 ns per iteration
Python Decimal:  7160.0 ns per iteration
Speedup factor:  25.300353356890458

=== Rounding Benchmark Summary ===
Benchmarked:      32 different rounding cases
Each case ran:    1000 iterations
Average speedup:  1192.78×

Individual speedup factors:
Case 1: 31.46×
Case 2: 71.36×
Case 3: 78.24×
Case 4: 98.40×
Case 5: 106.45×
Case 6: 35.12×
Case 7: 88.09×
Case 8: 32.69×
Case 9: 50.76×
Case 10: 6985.00×
Case 11: 7057.00×
Case 12: 30.35×
Case 13: 6848.00×
Case 14: 69.77×
Case 15: 91.93×
Case 16: 83.94×
Case 17: 93.37×
Case 18: 31.95×
Case 19: 7484.00×
Case 20: 52.71×
Case 21: 53.46×
Case 22: 19.45×
Case 23: 18.24×
Case 24: 7827.00×
Case 25: 7371.00×
Case 26: 14.41×
Case 27: 96.37×
Case 28: 13.78×
Case 29: 14.08×
Case 30: 71.33×
Case 31: 13.94×
Case 32: 25.30×

@forfudan
Copy link
Owner Author

After change c2823a4

=== DeciMojo Rounding Benchmark ===
Time: 2025-03-11T20:54:09.147735
System: Darwin 24.3.0
Processor: arm
Python version: 3.12.9
Python decimal precision: 28
Mojo decimal precision: 28

Running rounding benchmarks with 1000 iterations each

Benchmark:       Standard rounding to 2 places
Decimal:         123.456789
Round to:        2 places
Mojo result:     123.46
Python result:   123.46
Mojo Decimal:    97.0 ns per iteration
Python Decimal:  15665.0 ns per iteration
Speedup factor:  161.49484536082474

Benchmark:       Banker's rounding with even preceding digit
Decimal:         10.125
Round to:        2 places
Mojo result:     10.12
Python result:   10.12
Mojo Decimal:    39.0 ns per iteration
Python Decimal:  12446.0 ns per iteration
Speedup factor:  319.12820512820514

Benchmark:       Banker's rounding with odd preceding digit
Decimal:         10.135
Round to:        2 places
Mojo result:     10.14
Python result:   10.14
Mojo Decimal:    32.0 ns per iteration
Python Decimal:  11304.0 ns per iteration
Speedup factor:  353.25

Benchmark:       Rounding with less than half
Decimal:         10.124
Round to:        2 places
Mojo result:     10.12
Python result:   10.12
Mojo Decimal:    28.0 ns per iteration
Python Decimal:  10738.0 ns per iteration
Speedup factor:  383.5

Benchmark:       Rounding with more than half
Decimal:         10.126
Round to:        2 places
Mojo result:     10.13
Python result:   10.13
Mojo Decimal:    31.0 ns per iteration
Python Decimal:  9852.0 ns per iteration
Speedup factor:  317.80645161290323

Benchmark:       Rounding to whole number
Decimal:         123.456
Round to:        0 places
Mojo result:     123
Python result:   123
Mojo Decimal:    32.0 ns per iteration
Python Decimal:  9552.0 ns per iteration
Speedup factor:  298.5

Benchmark:       Rounding negative number
Decimal:         -123.456
Round to:        2 places
Mojo result:     -123.46
Python result:   -123.46
Mojo Decimal:    34.0 ns per iteration
Python Decimal:  9445.0 ns per iteration
Speedup factor:  277.79411764705884

Benchmark:       Rounding to tens (negative places)
Decimal:         123.456
Round to:        -1 places
Mojo result:     120
Python result:   1.2E+2
Mojo Decimal:    36.0 ns per iteration
Python Decimal:  8793.0 ns per iteration
Speedup factor:  244.25

Benchmark:       Rounding to hundreds (negative places)
Decimal:         1234.56
Round to:        -2 places
Mojo result:     1200
Python result:   1.2E+3
Mojo Decimal:    34.0 ns per iteration
Python Decimal:  8781.0 ns per iteration
Speedup factor:  258.2647058823529

Benchmark:       Rounding very small number
Decimal:         0.0000001234
Round to:        10 places
Mojo result:     0.0000001234
Python result:   1.234E-7
Mojo Decimal:    2.0 ns per iteration
Python Decimal:  8530.0 ns per iteration
Speedup factor:  4265.0

Benchmark:       Rounding already rounded number
Decimal:         123.45
Round to:        2 places
Mojo result:     123.45
Python result:   123.45
Mojo Decimal:    2.0 ns per iteration
Python Decimal:  8649.0 ns per iteration
Speedup factor:  4324.5

Benchmark:       Rounding to high precision (20 places)
Decimal:         0.12345678901234567890123
Round to:        20 places
Mojo result:     0.12345678901234567890
Python result:   0.12345678901234567890
Mojo Decimal:    153.0 ns per iteration
Python Decimal:  8384.0 ns per iteration
Speedup factor:  54.79738562091503

Benchmark:       Rounding to more places than input (10)
Decimal:         123.456
Round to:        10 places
Mojo result:     123.4560000000
Python result:   123.4560000000
Mojo Decimal:    18.0 ns per iteration
Python Decimal:  8855.0 ns per iteration
Speedup factor:  491.94444444444446

Benchmark:       Rounding number with trailing 9's
Decimal:         9.999
Round to:        2 places
Mojo result:     10.00
Python result:   10.00
Mojo Decimal:    25.0 ns per iteration
Python Decimal:  8687.0 ns per iteration
Speedup factor:  347.48

Benchmark:       Rounding requiring carry propagation
Decimal:         9.99
Round to:        1 places
Mojo result:     10.0
Python result:   10.0
Mojo Decimal:    20.0 ns per iteration
Python Decimal:  8783.0 ns per iteration
Speedup factor:  439.15

Benchmark:       Rounding exactly half with even preceding digit
Decimal:         2.5
Round to:        0 places
Mojo result:     2
Python result:   2
Mojo Decimal:    15.0 ns per iteration
Python Decimal:  8783.0 ns per iteration
Speedup factor:  585.5333333333333

Benchmark:       Rounding exactly half with odd preceding digit
Decimal:         3.5
Round to:        0 places
Mojo result:     4
Python result:   4
Mojo Decimal:    14.0 ns per iteration
Python Decimal:  8627.0 ns per iteration
Speedup factor:  616.2142857142857

Benchmark:       Rounding value close to MAX
Decimal:         12345678901234567889.87655
Round to:        2 places
Mojo result:     12345678901234567889.88
Python result:   12345678901234567889.88
Mojo Decimal:    166.0 ns per iteration
Python Decimal:  8730.0 ns per iteration
Speedup factor:  52.59036144578313

Benchmark:       Rounding minimum positive value
Decimal:         0.0000000000000000000000000001
Round to:        28 places
Mojo result:     0.0000000000000000000000000001
Python result:   1E-28
Mojo Decimal:    2.0 ns per iteration
Python Decimal:  8991.0 ns per iteration
Speedup factor:  4495.5

Benchmark:       Rounding engineering notation value
Decimal:         123.456789
Round to:        4 places
Mojo result:     123.4568
Python result:   123.4568
Mojo Decimal:    53.0 ns per iteration
Python Decimal:  9388.0 ns per iteration
Speedup factor:  177.1320754716981

Benchmark:       Rounding number with long integer part
Decimal:         12345678901234567.8901
Round to:        2 places
Mojo result:     12345678901234567.89
Python result:   12345678901234567.89
Mojo Decimal:    160.0 ns per iteration
Python Decimal:  8600.0 ns per iteration
Speedup factor:  53.75

Benchmark:       Rounding number just below half
Decimal:         10.12499999999999999
Round to:        2 places
Mojo result:     10.12
Python result:   10.12
Mojo Decimal:    130.0 ns per iteration
Python Decimal:  8876.0 ns per iteration
Speedup factor:  68.27692307692308

Benchmark:       Rounding number just above half
Decimal:         10.12500000000000001
Round to:        2 places
Mojo result:     10.13
Python result:   10.13
Mojo Decimal:    126.0 ns per iteration
Python Decimal:  8896.0 ns per iteration
Speedup factor:  70.60317460317461

Benchmark:       Rounding to maximum precision (28)
Decimal:         0.1111111111111111111111111111
Round to:        28 places
Mojo result:     0.1111111111111111111111111111
Python result:   0.1111111111111111111111111111
Mojo Decimal:    1.0 ns per iteration
Python Decimal:  8980.0 ns per iteration
Speedup factor:  8980.0

Benchmark:       Rounding zero
Decimal:         0
Round to:        10 places
Mojo result:     0.0000000000
Python result:   0E-10
Mojo Decimal:    8.0 ns per iteration
Python Decimal:  8816.0 ns per iteration
Speedup factor:  1102.0

Benchmark:       Rounding Pi to various places
Decimal:         3.141592653589793238462643383
Round to:        15 places
Mojo result:     3.141592653589793
Python result:   3.141592653589793
Mojo Decimal:    213.0 ns per iteration
Python Decimal:  9174.0 ns per iteration
Speedup factor:  43.070422535211264

Benchmark:       Rounding negative with carry propagation
Decimal:         -9.99
Round to:        1 places
Mojo result:     -10.0
Python result:   -10.0
Mojo Decimal:    18.0 ns per iteration
Python Decimal:  9257.0 ns per iteration
Speedup factor:  514.2777777777778

Benchmark:       Rounding with exact .5 and zeros after
Decimal:         1.5000000000000000000
Round to:        0 places
Mojo result:     2
Python result:   2
Mojo Decimal:    145.0 ns per iteration
Python Decimal:  8850.0 ns per iteration
Speedup factor:  61.03448275862069

Benchmark:       Rounding with exact .5 and non-zeros after
Decimal:         1.50000000000000000001
Round to:        0 places
Mojo result:     1
Python result:   2
Mojo Decimal:    172.0 ns per iteration
Python Decimal:  9255.0 ns per iteration
Speedup factor:  53.80813953488372

Benchmark:       Rounding extremely close to MAX
Decimal:         123456789012345677.999999999
Round to:        8 places
Mojo result:     123456789012345678.00000000
Python result:   123456789012345678.00000000
Mojo Decimal:    185.0 ns per iteration
Python Decimal:  8906.0 ns per iteration
Speedup factor:  48.14054054054054

Benchmark:       Random decimal with various digits
Decimal:         7.389465718934026719043
Round to:        12 places
Mojo result:     7.389465718934
Python result:   7.389465718934
Mojo Decimal:    162.0 ns per iteration
Python Decimal:  9073.0 ns per iteration
Speedup factor:  56.00617283950617

Benchmark:       Number with alternating digits
Decimal:         1.010101010101010101010101
Round to:        18 places
Mojo result:     1.010101010101010101
Python result:   1.010101010101010101
Mojo Decimal:    176.0 ns per iteration
Python Decimal:  8894.0 ns per iteration
Speedup factor:  50.53409090909091

=== Rounding Benchmark Summary ===
Benchmarked:      32 different rounding cases
Each case ran:    1000 iterations
Average speedup:  923.9166230074229×

Individual speedup factors:
Case 1: 161.49×
Case 2: 319.13×
Case 3: 353.25×
Case 4: 383.5×
Case 5: 317.81×
Case 6: 298.5×
Case 7: 277.79×
Case 8: 244.25×
Case 9: 258.26×
Case 10: 4265.0×
Case 11: 4324.5×
Case 12: 54.8×
Case 13: 491.94×
Case 14: 347.48×
Case 15: 439.15×
Case 16: 585.53×
Case 17: 616.21×
Case 18: 52.59×
Case 19: 4495.5×
Case 20: 177.13×
Case 21: 53.75×
Case 22: 68.28×
Case 23: 70.6×
Case 24: 8980.0×
Case 25: 1102.0×
Case 26: 43.07×
Case 27: 514.28×
Case 28: 61.03×
Case 29: 53.81×
Case 30: 48.14×
Case 31: 56.01×
Case 32: 50.53×

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants