diff --git a/docs/todo.md b/docs/todo.md index f5ed6d0..087e0e4 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -2,4 +2,4 @@ This is a to-do list for Yuhao's personal use. -- The `exp()` function performs slower than Python's counterpart in specific cases. Detailed investigation reveals the bottleneck stems from multiplication operations between decimals with significant fractional components. These operations currently rely on UInt256 arithmetic, which introduces performance overhead. Optimization of the `multiply()` function is required to address these performance bottlenecks, particularly for high-precision decimal multiplication with many digits after the decimal point. \ No newline at end of file +- [x] (#31) The `exp()` function performs slower than Python's counterpart in specific cases. Detailed investigation reveals the bottleneck stems from multiplication operations between decimals with significant fractional components. These operations currently rely on UInt256 arithmetic, which introduces performance overhead. Optimization of the `multiply()` function is required to address these performance bottlenecks, particularly for high-precision decimal multiplication with many digits after the decimal point. \ No newline at end of file diff --git a/mojoproject.toml b/mojoproject.toml index b740096..9f2f1b7 100644 --- a/mojoproject.toml +++ b/mojoproject.toml @@ -40,6 +40,7 @@ test_round = "magic run package && magic run mojo test tests/test_round.mojo && test_creation = "magic run package && magic run mojo test tests/test_creation.mojo && magic run delete_package" test_from_float = "magic run package && magic run mojo test tests/test_from_float.mojo && magic run delete_package" test_from_string = "magic run package && magic run mojo test tests/test_from_string.mojo && magic run delete_package" +test_to_float = "magic run package && magic run mojo test tests/test_to_float.mojo && magic run delete_package" test_comparison = "magic run package && magic run mojo test tests/test_comparison.mojo && magic run delete_package" test_factorial = "magic run package && magic run mojo test tests/test_factorial.mojo && magic run delete_package" test_exp = "magic run package && magic run mojo test tests/test_exp.mojo && magic run delete_package" diff --git a/src/decimojo/__init__.mojo b/src/decimojo/__init__.mojo index 1c66b8e..7b73588 100644 --- a/src/decimojo/__init__.mojo +++ b/src/decimojo/__init__.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # """ diff --git a/src/decimojo/arithmetics.mojo b/src/decimojo/arithmetics.mojo index 58a570f..59ecdf0 100644 --- a/src/decimojo/arithmetics.mojo +++ b/src/decimojo/arithmetics.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # # # Implements basic arithmetic functions for the Decimal type diff --git a/src/decimojo/comparison.mojo b/src/decimojo/comparison.mojo index fdbef44..97ab927 100644 --- a/src/decimojo/comparison.mojo +++ b/src/decimojo/comparison.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # # # Implements comparison operations for the Decimal type diff --git a/src/decimojo/decimal.mojo b/src/decimojo/decimal.mojo index 4ec1839..08fcdf0 100644 --- a/src/decimojo/decimal.mojo +++ b/src/decimojo/decimal.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # # # Implements basic object methods for the Decimal type @@ -967,7 +965,7 @@ struct Decimal( The floating-point representation of this Decimal. """ - var result = Float64(self.coefficient()) / (10 ** self.scale()) + var result = Float64(self.coefficient()) / (Float64(10) ** self.scale()) result = -result if self.is_negative() else result return result diff --git a/src/decimojo/exponential.mojo b/src/decimojo/exponential.mojo index b17a635..2efe8e2 100644 --- a/src/decimojo/exponential.mojo +++ b/src/decimojo/exponential.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # # # Implements exponential functions for the Decimal type diff --git a/src/decimojo/prelude.mojo b/src/decimojo/prelude.mojo index c1e992f..2da998d 100644 --- a/src/decimojo/prelude.mojo +++ b/src/decimojo/prelude.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # """ diff --git a/src/decimojo/rounding.mojo b/src/decimojo/rounding.mojo index b9960aa..0e44a07 100644 --- a/src/decimojo/rounding.mojo +++ b/src/decimojo/rounding.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # # # Implements basic object methods for the Decimal type diff --git a/src/decimojo/rounding_mode.mojo b/src/decimojo/rounding_mode.mojo index d1cb629..9a1d635 100644 --- a/src/decimojo/rounding_mode.mojo +++ b/src/decimojo/rounding_mode.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # diff --git a/src/decimojo/special.mojo b/src/decimojo/special.mojo index 3730398..a3247d0 100644 --- a/src/decimojo/special.mojo +++ b/src/decimojo/special.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # # # Implements special functions for the Decimal type diff --git a/src/decimojo/utility.mojo b/src/decimojo/utility.mojo index 15dd5bb..08cc56b 100644 --- a/src/decimojo/utility.mojo +++ b/src/decimojo/utility.mojo @@ -1,5 +1,4 @@ # ===----------------------------------------------------------------------=== # -# # DeciMojo: A fixed-point decimal arithmetic library in Mojo # https://github.com/forFudan/DeciMojo # @@ -16,7 +15,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# # ===----------------------------------------------------------------------=== # # # Implements internal utility functions for the Decimal type diff --git a/tests/test_conversions.mojo b/tests/test_conversions.mojo deleted file mode 100644 index edcd586..0000000 --- a/tests/test_conversions.mojo +++ /dev/null @@ -1,129 +0,0 @@ -""" -Test Decimal conversion methods: __int__, __float__, and __str__ -for different numerical cases. -""" - -from decimojo.prelude import dm, Decimal, RoundingMode -import testing -import time - - -fn test_int_conversion() raises: - print("------------------------------------------------------") - print("--- Testing Int Conversion ---") - - # Test positive integer - var d1 = Decimal("123") - var i1 = Int(d1) - print("Int(123) =", i1) - testing.assert_equal(i1, 123) - - # Test negative integer - var d2 = Decimal("-456") - var i2 = Int(d2) - print("Int(-456) =", i2) - testing.assert_equal(i2, -456) - - # Test zero - var d3 = Decimal("0") - var i3 = Int(d3) - print("Int(0) =", i3) - testing.assert_equal(i3, 0) - - # Test decimal truncation - var d4 = Decimal("789.987") - var i4 = Int(d4) - print("Int(789.987) =", i4) - testing.assert_equal(i4, 789) - - # Test negative decimal truncation - var d5 = Decimal("-123.456") - var i5 = Int(d5) - print("Int(-123.456) =", i5) - testing.assert_equal(i5, -123) - - # Test large number - var d6 = Decimal("9999999999") - var i6 = Int(d6) - print("Int(9999999999) =", i6) - testing.assert_equal(i6, 9999999999) - - -fn test_float_conversion() raises: - print("------------------------------------------------------") - print("--- Testing Float64 Conversion ---") - - # Test positive number - var d1 = Decimal("123.456") - var f1 = Float64(d1) - print("Float64(123.456) =", f1) - testing.assert_almost_equal(f1, 123.456) - - # Test negative number - var d2 = Decimal("-789.012") - var f2 = Float64(d2) - print("Float64(-789.012) =", f2) - testing.assert_almost_equal(f2, -789.012) - - # Test zero - var d3 = Decimal("0.0") - var f3 = Float64(d3) - print("Float64(0.0) =", f3) - testing.assert_equal(f3, 0.0) - - # Test very small number - var d4 = Decimal("0.0000000001") - var f4 = Float64(d4) - print("Float64(0.0000000001) =", f4) - testing.assert_almost_equal(f4, 0.0000000001) - - # Test very large number - var d5 = Decimal("1234567890123.4567") - var f5 = Float64(d5) - print("Float64(1234567890123.4567) =", f5) - testing.assert_almost_equal(f5, 1234567890123.4567) - - -fn test_str_conversion() raises: - print("------------------------------------------------------") - print("--- Testing String Conversion ---") - - # Test positive number - var d1 = Decimal("123.456") - var s1 = String(d1) - print("String(123.456) =", s1) - testing.assert_equal(s1, "123.456") - - # Test negative number - var d2 = Decimal("-789.012") - var s2 = String(d2) - print("String(-789.012) =", s2) - testing.assert_equal(s2, "-789.012") - - # Test zero - var d3 = Decimal("0") - var s3 = String(d3) - print("String(0) =", s3) - testing.assert_equal(s3, "0") - - # Test large number with precision - var d4 = Decimal("9876543210.0123456789") - var s4 = String(d4) - print("String(9876543210.0123456789) =", s4) - testing.assert_equal(s4, "9876543210.0123456789") - - # Test small number - var d5 = Decimal("0.0000000001") - var s5 = String(d5) - print("String(0.0000000001) =", s5) - testing.assert_equal(s5, "0.0000000001") - - -fn main() raises: - print("Starting Decimal conversion tests...") - - test_int_conversion() - test_float_conversion() - test_str_conversion() - - print("\nAll tests completed!") diff --git a/tests/test_to_float.mojo b/tests/test_to_float.mojo new file mode 100644 index 0000000..bbff71c --- /dev/null +++ b/tests/test_to_float.mojo @@ -0,0 +1,215 @@ +""" +Comprehensive tests for the Decimal.__float__() method. +Tests 20 different cases to ensure proper conversion from Decimal to float. +""" + +import testing +from decimojo.prelude import dm, Decimal, RoundingMode + + +fn test_basic_integer_conversions() raises: + """Test conversion of basic integers to float.""" + print("Testing basic integer conversions to float...") + + # Test case 1: Zero + var zero = Decimal("0") + var zero_float = Float64(zero) + testing.assert_equal( + zero_float, 0.0, "Decimal('0') should convert to float 0.0" + ) + + # Test case 2: One + var one = Decimal("1") + var one_float = Float64(one) + testing.assert_equal( + one_float, 1.0, "Decimal('1') should convert to float 1.0" + ) + + # Test case 3: Ten + var ten = Decimal("10") + var ten_float = Float64(ten) + testing.assert_equal( + ten_float, 10.0, "Decimal('10') should convert to float 10.0" + ) + + # Test case 4: Large integer + var large_int = Decimal("123456") + var large_int_float = Float64(large_int) + testing.assert_equal(large_int_float, 123456.0) + + print("✓ Basic integer conversions to float passed!") + + +fn test_decimal_conversions() raises: + """Test conversion of decimal values to float.""" + print("Testing decimal conversions to float...") + + # Test case 5: Simple decimal + var simple_dec = Decimal("3.14") + var simple_dec_float = Float64(simple_dec) + testing.assert_equal( + simple_dec_float, 3.14, "Decimal('3.14') should convert to float 3.14" + ) + + # Test case 6: Decimal with many places + var pi = Decimal("3.14159265358979323846") + var pi_float = Float64(pi) + # Allow for small difference due to float precision + testing.assert_true(abs(pi_float - 3.14159265358979323846) < 1e-15) + + # Test case 7: Small decimal + var small_dec = Decimal("0.0001") + var small_dec_float = Float64(small_dec) + testing.assert_equal(small_dec_float, 0.0001) + + # Test case 8: Repeating decimal + var repeating = Decimal("0.33333333333333") + var repeating_float = Float64(repeating) + testing.assert_true(abs(repeating_float - 0.33333333333333) < 1e-14) + + print("✓ Decimal conversions to float passed!") + + +fn test_negative_conversions() raises: + """Test conversion of negative values to float.""" + print("Testing negative value conversions to float...") + + # Test case 9: Negative integer + var neg_int = Decimal("-123") + var neg_int_float = Float64(neg_int) + testing.assert_equal(neg_int_float, -123.0) + + # Test case 10: Negative decimal + var neg_dec = Decimal("-0.5") + var neg_dec_float = Float64(neg_dec) + testing.assert_equal(neg_dec_float, -0.5) + + # Test case 11: Negative zero + var neg_zero = Decimal("-0") + var neg_zero_float = Float64(neg_zero) + testing.assert_equal( + neg_zero_float, 0.0 + ) # Note: -0.0 equals 0.0 in most comparisons + + print("✓ Negative value conversions to float passed!") + + +fn test_edge_cases() raises: + """Test edge cases for conversion to float.""" + print("Testing edge cases for float conversion...") + + # Test case 12: Very small positive number + var very_small = Decimal("0." + "0" * 20 + "1") # 0.00000000000000000001 + var very_small_float = Float64(very_small) + testing.assert_true( + very_small_float > 0.0 and very_small_float < 1e-19, + "Very small Decimal should convert to near-zero positive float", + ) + + # Test case 13: Value close to float precision + var precision_edge = Decimal("0.1234567890123456") + var precision_edge_float = Float64(precision_edge) + testing.assert_true( + abs(precision_edge_float - 0.1234567890123456) < 1e-15, + "Float conversion should preserve precision within float limits", + ) + + # Test case 14: Very large number + var large_num = Decimal("1e15") # 1,000,000,000,000,000 + var large_num_float = Float64(large_num) + testing.assert_equal(large_num_float, 1e15) + + # Test case 15: Number larger than float precision but within range + var large_precise = Decimal( + "9007199254740993" + ) # First integer not exactly representable in float64 + var large_precise_float = Float64(large_precise) + testing.assert_true( + abs(large_precise_float - 9007199254740993.0) <= 1.0, + "Large number should be converted with expected float precision loss", + ) + + print("✓ Edge case conversions to float passed!") + + +fn test_special_values() raises: + """Test special values for conversion to float.""" + print("Testing special values for float conversion...") + + # Test case 16: Decimal with trailing zeros + var trailing_zeros = Decimal("5.0000") + var trailing_zeros_float = Float64(trailing_zeros) + testing.assert_equal(trailing_zeros_float, 5.0) + + # Test case 17: Decimal with leading zeros + var leading_zeros = Decimal("000123.456") + var leading_zeros_float = Float64(leading_zeros) + testing.assert_equal(leading_zeros_float, 123.456) + + # Test case 18: Scientific notation + var sci_notation = Decimal("1.23e5") + var sci_notation_float = Float64(sci_notation) + testing.assert_equal(sci_notation_float, 123000.0) + + # Test case 19: Max decimal convertible to float + var max_decimal = Decimal( + "79228162514264337593543950335" + ) # Approximate Float64 max + var max_decimal_float = Float64(max_decimal) + # Allow some imprecision with large values + testing.assert_true( + abs( + (max_decimal_float - 79228162514264337593543950335) + / 79228162514264337593543950335 + ) + < 1e-10, + "Large value should convert within reasonable precision", + ) + + # Test case 20: Another number with specific precision challenges + var challenge_num = Decimal("0.1") + var challenge_float = Float64(challenge_num) + # 0.1 cannot be exactly represented in binary floating point + testing.assert_true( + abs(challenge_float - 0.1) < 1e-15, + "Binary float approximation of decimal 0.1 should be very close", + ) + + print("✓ Special value conversions to float passed!") + + +fn run_test_with_error_handling( + test_fn: fn () raises -> None, test_name: String +) raises: + """Helper function to run a test function with error handling and reporting. + """ + try: + print("\n" + "=" * 50) + print("RUNNING: " + test_name) + print("=" * 50) + test_fn() + print("\n✓ " + test_name + " passed\n") + except e: + print("\n✗ " + test_name + " FAILED!") + print("Error message: " + String(e)) + raise e + + +fn main() raises: + print("=========================================") + print("Running 20 tests for Decimal.__float__()") + print("=========================================") + + run_test_with_error_handling( + test_basic_integer_conversions, "Basic integer conversions" + ) + run_test_with_error_handling( + test_decimal_conversions, "Decimal conversions" + ) + run_test_with_error_handling( + test_negative_conversions, "Negative value conversions" + ) + run_test_with_error_handling(test_edge_cases, "Edge cases") + run_test_with_error_handling(test_special_values, "Special values") + + print("All 20 Decimal.__float__() tests passed!") diff --git a/tests/test_to_int.mojo b/tests/test_to_int.mojo new file mode 100644 index 0000000..be9f8cf --- /dev/null +++ b/tests/test_to_int.mojo @@ -0,0 +1,57 @@ +""" +Test Decimal conversion methods: __int__ +for different numerical cases. +""" + +from decimojo.prelude import dm, Decimal, RoundingMode +import testing +import time + + +fn test_int_conversion() raises: + print("------------------------------------------------------") + print("--- Testing Int Conversion ---") + + # Test positive integer + var d1 = Decimal("123") + var i1 = Int(d1) + print("Int(123) =", i1) + testing.assert_equal(i1, 123) + + # Test negative integer + var d2 = Decimal("-456") + var i2 = Int(d2) + print("Int(-456) =", i2) + testing.assert_equal(i2, -456) + + # Test zero + var d3 = Decimal("0") + var i3 = Int(d3) + print("Int(0) =", i3) + testing.assert_equal(i3, 0) + + # Test decimal truncation + var d4 = Decimal("789.987") + var i4 = Int(d4) + print("Int(789.987) =", i4) + testing.assert_equal(i4, 789) + + # Test negative decimal truncation + var d5 = Decimal("-123.456") + var i5 = Int(d5) + print("Int(-123.456) =", i5) + testing.assert_equal(i5, -123) + + # Test large number + var d6 = Decimal("9999999999") + var i6 = Int(d6) + print("Int(9999999999) =", i6) + testing.assert_equal(i6, 9999999999) + + +fn main() raises: + print("Starting Decimal conversion __int__ tests...") + + test_int_conversion() + + print("\nAll tests completed!") diff --git a/tests/test_to_string.mojo b/tests/test_to_string.mojo new file mode 100644 index 0000000..c885ed5 --- /dev/null +++ b/tests/test_to_string.mojo @@ -0,0 +1,51 @@ +""" +Test Decimal conversion methods: __str__ +for different numerical cases. +""" + +from decimojo.prelude import dm, Decimal, RoundingMode +import testing +import time + + +fn test_str_conversion() raises: + print("------------------------------------------------------") + print("--- Testing String Conversion ---") + + # Test positive number + var d1 = Decimal("123.456") + var s1 = String(d1) + print("String(123.456) =", s1) + testing.assert_equal(s1, "123.456") + + # Test negative number + var d2 = Decimal("-789.012") + var s2 = String(d2) + print("String(-789.012) =", s2) + testing.assert_equal(s2, "-789.012") + + # Test zero + var d3 = Decimal("0") + var s3 = String(d3) + print("String(0) =", s3) + testing.assert_equal(s3, "0") + + # Test large number with precision + var d4 = Decimal("9876543210.0123456789") + var s4 = String(d4) + print("String(9876543210.0123456789) =", s4) + testing.assert_equal(s4, "9876543210.0123456789") + + # Test small number + var d5 = Decimal("0.0000000001") + var s5 = String(d5) + print("String(0.0000000001) =", s5) + testing.assert_equal(s5, "0.0000000001") + + +fn main() raises: + print("Starting Decimal conversion __str__ tests...") + + test_str_conversion() + + print("\nAll tests completed!")