diff --git a/README.md b/README.md index 449b53b..1b06226 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ For brevity, you can also refer to it "decimo" (derived from the Latin root "dec ## Status -Rome is not built in one day. DeciMojo is currently under active development and appears to be between the **"make it work"** and **"make it right"** phases, leaning more toward the latter. Bug reports and feature requests are welcome! If you encounter issues, please [file them here](https://github.com/forFudan/decimojo/issues). +Rome wasn't built in a day. DeciMojo is currently under active development, positioned between the **"make it work"** and **"make it right"** phases, with a stronger emphasis on the latter. Bug reports and feature requests are welcome! If you encounter issues, please [file them here](https://github.com/forFudan/decimojo/issues). ### Make it Work ✅ (MOSTLY COMPLETED) @@ -42,12 +42,16 @@ Rome is not built in one day. DeciMojo is currently under active development and - High precision support is implemented (up to 28 decimal places) - The examples show robust handling of various scenarios -### Make it Fast ⏳ (FUTURE WORK) +### Make it Fast ⏳ (IN PROGRESS & FUTURE WORK) -- Performance optimization is acknowledged but not currently prioritized +- Performance optimization on basic operations (+ - * /) in progress +- Regular benchmarking against Python's `decimal` module (see `bench/` folder) +- Performance optimization on other functions are acknowleged but not currently prioritized ## Examples +The `Decimal` type can represent at most 29 significant digits and 28 digits after the decimal point. If the significant digits of a decimal value exceeds the maximum value (`2^96 - 1`), it either casts and error, or it is rounded until the siginificant digits are within the maximum value. For example, for the number `8.8888888888888888888888888888`, the significant digits (29 eights)exceeds the maximum value. It will then be rounded into `8.888888888888888888888888888` (28 eights). + Here are 10 key examples highlighting the most important features of the `Decimal` type in its current state: ### 1. Fixed-Point Precision for Financial Calculations @@ -277,9 +281,7 @@ After cloning the repo onto your local disk, you can: - Use `magic run test` (or `maigic run t`) to run tests. - Use `magic run bench` (or `magic run b`) to generate logs for benchmarking tests agains `python.decimal` module. The log files are saved in `benches/logs/`. -## License - -Distributed under the Apache 2.0 License. See [LICENSE](https://github.com/forFudan/decimojo/blob/main/LICENSE) for details. +## Citation If you find DeciMojo useful for your research, consider listing it in your citations 😀. @@ -293,3 +295,14 @@ If you find DeciMojo useful for your research, consider listing it in your citat note = {Computer Software} } ``` + +## License + +Copyright 2025 Yuhao Zhu + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, 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/benches/bench_add.mojo b/benches/bench_add.mojo index 2f47c3e..8db56b7 100644 --- a/benches/bench_add.mojo +++ b/benches/bench_add.mojo @@ -72,6 +72,9 @@ fn run_benchmark( # Verify correctness var mojo_result = a_mojo + b_mojo var py_result = a_py + b_py + log_print( + "Decimals: " + String(a_mojo) + " + " + String(b_mojo), log_file + ) log_print("Mojo result: " + String(mojo_result), log_file) log_print("Python result: " + String(py_result), log_file) diff --git a/benches/bench_divide.mojo b/benches/bench_divide.mojo index 2da5c05..4eb06a2 100644 --- a/benches/bench_divide.mojo +++ b/benches/bench_divide.mojo @@ -68,6 +68,9 @@ fn run_benchmark( log_file: File object for logging results. """ log_print("\nBenchmark: " + name, log_file) + log_print( + "Decimals: " + String(a_mojo) + " / " + String(b_mojo), log_file + ) # Verify correctness var mojo_result = a_mojo / b_mojo diff --git a/benches/bench_multiply.mojo b/benches/bench_multiply.mojo index 9e03e48..cf9f39e 100644 --- a/benches/bench_multiply.mojo +++ b/benches/bench_multiply.mojo @@ -72,6 +72,9 @@ fn run_benchmark( # Verify correctness var mojo_result = a_mojo * b_mojo var py_result = a_py * b_py + log_print( + "Decimals: " + String(a_mojo) + " * " + String(b_mojo), log_file + ) log_print("Mojo result: " + String(mojo_result), log_file) log_print("Python result: " + String(py_result), log_file) diff --git a/benches/bench_subtract.mojo b/benches/bench_subtract.mojo index eb9007c..9f0ed03 100644 --- a/benches/bench_subtract.mojo +++ b/benches/bench_subtract.mojo @@ -72,6 +72,9 @@ fn run_benchmark( # Verify correctness var mojo_result = a_mojo - b_mojo var py_result = a_py - b_py + log_print( + "Decimals: " + String(a_mojo) + " - " + String(b_mojo), log_file + ) log_print("Mojo result: " + String(mojo_result), log_file) log_print("Python result: " + String(py_result), log_file) diff --git a/mojoproject.toml b/mojoproject.toml index f0147b9..991f829 100644 --- a/mojoproject.toml +++ b/mojoproject.toml @@ -20,14 +20,18 @@ p = "clear && magic run package" delete_package = "rm tests/decimojo.mojopkg" # debugs (run the testing files only) -debug = "magic run package && magic run mojo tests/*.mojo" -d = "clear && magic run debug" +debug = "magic run package && magic run mojo tests/*.mojo && magic run delete_package" +debug_arith = "magic run package && magic run mojo tests/test_arithmetics.mojo && magic run delete_package" +debug_div = "magic run package && magic run mojo tests/test_division.mojo && magic run delete_package" +debug_sqrt = "magic run package && magic run mojo tests/test_sqrt.mojo && magic run delete_package" # tests (use the mojo testing tool) test = "magic run package && magic run mojo test tests && magic run delete_package" t = "clear && magic run test" - -# individual test files +test_arith = "magic run package && magic run mojo test tests/test_arithmetics.mojo && magic run delete_package" +test_div = "magic run package && magic run mojo test tests/test_division.mojo && magic run delete_package" +test_sqrt = "magic run package && magic run mojo test tests/test_sqrt.mojo && magic run delete_package" +test_round = "magic run package && magic run mojo test tests/test_rounding.mojo && magic run delete_package" # benches bench = "magic run package && cd benches && magic run mojo bench.mojo && cd .." diff --git a/src/decimojo/maths/arithmetics.mojo b/src/decimojo/maths/arithmetics.mojo index 0197bc4..46514ac 100644 --- a/src/decimojo/maths/arithmetics.mojo +++ b/src/decimojo/maths/arithmetics.mojo @@ -11,6 +11,10 @@ # # List of functions in this module: # +# add(x1: Decimal, x2: Decimal): Adds two Decimal values and returns a new Decimal containing the sum +# subtract(x1: Decimal, x2: Decimal): Subtracts the x2 Decimal from x1 and returns a new Decimal +# multiply(x1: Decimal, x2: Decimal): Multiplies two Decimal values and returns a new Decimal containing the product +# true_divide(x1: Decimal, x2: Decimal): Divides x1 by x2 and returns a new Decimal containing the quotient # power(base: Decimal, exponent: Decimal): Raises base to the power of exponent (integer exponents only) # power(base: Decimal, exponent: Int): Convenience method for integer exponents # sqrt(x: Decimal): Computes the square root of x using Newton-Raphson method @@ -171,8 +175,8 @@ fn add(x1: Decimal, x2: Decimal) raises -> Decimal: -1 ) ** x2.is_negative() * Int128(x2.coefficient()) - var is_nagative = summation < 0 - if is_nagative: + var is_negative = summation < 0 + if is_negative: summation = -summation # Now we need to truncate the summation to fit in 96 bits @@ -200,7 +204,7 @@ fn add(x1: Decimal, x2: Decimal) raises -> Decimal: mid = UInt32((truncated_summation >> 32) & 0xFFFFFFFF) high = UInt32((truncated_summation >> 64) & 0xFFFFFFFF) - return Decimal(low, mid, high, is_nagative, final_scale) + return Decimal(low, mid, high, is_negative, final_scale) # Float addition which with different scales else: @@ -224,8 +228,8 @@ fn add(x1: Decimal, x2: Decimal) raises -> Decimal: x2.coefficient() ) - var is_nagative = summation < 0 - if is_nagative: + var is_negative = summation < 0 + if is_negative: summation = -summation # Now we need to truncate the summation to fit in 96 bits @@ -253,7 +257,7 @@ fn add(x1: Decimal, x2: Decimal) raises -> Decimal: mid = UInt32((truncated_summation >> 32) & 0xFFFFFFFF) high = UInt32((truncated_summation >> 64) & 0xFFFFFFFF) - return Decimal(low, mid, high, is_nagative, final_scale) + return Decimal(low, mid, high, is_negative, final_scale) fn subtract(x1: Decimal, x2: Decimal) raises -> Decimal: @@ -305,7 +309,7 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: var x2_scale = x2.scale() var combined_scale = x1_scale + x2_scale """Combined scale of the two operands.""" - var is_nagative = x1.is_negative() != x2.is_negative() + var is_negative = x1.is_negative() != x2.is_negative() # SPECIAL CASE: zero # Return zero while preserving the scale @@ -326,12 +330,12 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: 0, 0, 0, - is_nagative, + is_negative, Decimal.MAX_PRECISION, ) # Otherwise, return 1 with correct sign and scale var final_scale = min(Decimal.MAX_PRECISION, combined_scale) - return Decimal(1, 0, 0, is_nagative, final_scale) + return Decimal(1, 0, 0, is_negative, final_scale) # SPECIAL CASE: First operand has coefficient of 1 if x1_coef == 1: @@ -339,28 +343,28 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: if x1_scale == 0: var result = x2 result.flags &= ~Decimal.SIGN_MASK - if is_nagative: + if is_negative: result.flags |= Decimal.SIGN_MASK return result else: - var mul = x2_coef + var prod = x2_coef # Rounding may be needed. - var num_digits_mul = decimojo.utility.number_of_digits(mul) - var num_digits_to_keep = num_digits_mul - ( + var num_digits_prod = decimojo.utility.number_of_digits(prod) + var num_digits_to_keep = num_digits_prod - ( combined_scale - Decimal.MAX_PRECISION ) - var truncated_mul = decimojo.utility.truncate_to_digits( - mul, num_digits_to_keep + var truncated_prod = decimojo.utility.truncate_to_digits( + prod, num_digits_to_keep ) var final_scale = min(Decimal.MAX_PRECISION, combined_scale) - var low = UInt32(truncated_mul & 0xFFFFFFFF) - var mid = UInt32((truncated_mul >> 32) & 0xFFFFFFFF) - var high = UInt32((truncated_mul >> 64) & 0xFFFFFFFF) + var low = UInt32(truncated_prod & 0xFFFFFFFF) + var mid = UInt32((truncated_prod >> 32) & 0xFFFFFFFF) + var high = UInt32((truncated_prod >> 64) & 0xFFFFFFFF) return Decimal( low, mid, high, - is_nagative, + is_negative, final_scale, ) @@ -370,28 +374,28 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: if x2_scale == 0: var result = x1 result.flags &= ~Decimal.SIGN_MASK - if is_nagative: + if is_negative: result.flags |= Decimal.SIGN_MASK return result else: - var mul = x1_coef + var prod = x1_coef # Rounding may be needed. - var num_digits_mul = decimojo.utility.number_of_digits(mul) - var num_digits_to_keep = num_digits_mul - ( + var num_digits_prod = decimojo.utility.number_of_digits(prod) + var num_digits_to_keep = num_digits_prod - ( combined_scale - Decimal.MAX_PRECISION ) - var truncated_mul = decimojo.utility.truncate_to_digits( - mul, num_digits_to_keep + var truncated_prod = decimojo.utility.truncate_to_digits( + prod, num_digits_to_keep ) var final_scale = min(Decimal.MAX_PRECISION, combined_scale) - var low = UInt32(truncated_mul & 0xFFFFFFFF) - var mid = UInt32((truncated_mul >> 32) & 0xFFFFFFFF) - var high = UInt32((truncated_mul >> 64) & 0xFFFFFFFF) + var low = UInt32(truncated_prod & 0xFFFFFFFF) + var mid = UInt32((truncated_prod >> 32) & 0xFFFFFFFF) + var high = UInt32((truncated_prod >> 64) & 0xFFFFFFFF) return Decimal( low, mid, high, - is_nagative, + is_negative, final_scale, ) @@ -409,57 +413,59 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: if x1_scale == 0 and x2_scale == 0: # Small integers, use UInt64 multiplication if combined_num_bits <= 64: - var mul: UInt64 = UInt64(x1.low) * UInt64(x2.low) - var low = UInt32(mul & 0xFFFFFFFF) - var mid = UInt32((mul >> 32) & 0xFFFFFFFF) - return Decimal(low, mid, 0, is_nagative, 0) + var prod: UInt64 = UInt64(x1.low) * UInt64(x2.low) + var low = UInt32(prod & 0xFFFFFFFF) + var mid = UInt32((prod >> 32) & 0xFFFFFFFF) + return Decimal(low, mid, 0, is_negative, 0) # Moderate integers, use UInt128 multiplication elif combined_num_bits <= 128: - var mul: UInt128 = UInt128(x1_coef) * UInt128(x2_coef) - var low = UInt32(mul & 0xFFFFFFFF) - var mid = UInt32((mul >> 32) & 0xFFFFFFFF) - var high = UInt32((mul >> 64) & 0xFFFFFFFF) - return Decimal(low, mid, high, is_nagative, 0) + var prod: UInt128 = UInt128(x1_coef) * UInt128(x2_coef) + var low = UInt32(prod & 0xFFFFFFFF) + var mid = UInt32((prod >> 32) & 0xFFFFFFFF) + var high = UInt32((prod >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, 0) # Large integers, use UInt256 multiplication else: - var mul: UInt256 = UInt256(x1_coef) * UInt256(x2_coef) - if mul > Decimal.MAX_AS_UINT256: - raise Error("Error in `multiply()`: Decimal overflow") + var prod: UInt256 = UInt256(x1_coef) * UInt256(x2_coef) + if prod > Decimal.MAX_AS_UINT256: + raise Error("Error in `prodtiply()`: Decimal overflow") else: - var low = UInt32(mul & 0xFFFFFFFF) - var mid = UInt32((mul >> 32) & 0xFFFFFFFF) - var high = UInt32((mul >> 64) & 0xFFFFFFFF) - return Decimal(low, mid, high, is_nagative, 0) + var low = UInt32(prod & 0xFFFFFFFF) + var mid = UInt32((prod >> 32) & 0xFFFFFFFF) + var high = UInt32((prod >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, 0) # SPECIAL CASE: Both operands are integers but with scales # Examples: 123.0 * 456.00 if x1.is_integer() and x2.is_integer(): var x1_integral_part = x1_coef // (UInt128(10) ** UInt128(x1_scale)) var x2_integral_part = x2_coef // (UInt128(10) ** UInt128(x2_scale)) - var mul: UInt256 = UInt256(x1_integral_part) * UInt256(x2_integral_part) - if mul > Decimal.MAX_AS_UINT256: + var prod: UInt256 = UInt256(x1_integral_part) * UInt256( + x2_integral_part + ) + if prod > Decimal.MAX_AS_UINT256: raise Error("Error in `multiply()`: Decimal overflow") else: - var num_digits = decimojo.utility.number_of_digits(mul) + var num_digits = decimojo.utility.number_of_digits(prod) var final_scale = min( Decimal.MAX_VALUE_DIGITS - num_digits, combined_scale ) # Scale up before it overflows - mul = mul * 10**final_scale - if mul > Decimal.MAX_AS_UINT256: - mul = mul // 10 + prod = prod * 10**final_scale + if prod > Decimal.MAX_AS_UINT256: + prod = prod // 10 final_scale -= 1 - var low = UInt32(mul & 0xFFFFFFFF) - var mid = UInt32((mul >> 32) & 0xFFFFFFFF) - var high = UInt32((mul >> 64) & 0xFFFFFFFF) + var low = UInt32(prod & 0xFFFFFFFF) + var mid = UInt32((prod >> 32) & 0xFFFFFFFF) + var high = UInt32((prod >> 64) & 0xFFFFFFFF) return Decimal( low, mid, high, - is_nagative, + is_negative, final_scale, ) @@ -471,27 +477,27 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: # Result coefficient will less than 2^96 - 1 = 79228162514264337593543950335 # Examples: 1.23 * 4.56 if combined_num_bits <= 96: - var mul: UInt128 = x1_coef * x2_coef + var prod: UInt128 = x1_coef * x2_coef # Combined scale more than max precision, no need to truncate if combined_scale <= Decimal.MAX_PRECISION: - var low = UInt32(mul & 0xFFFFFFFF) - var mid = UInt32((mul >> 32) & 0xFFFFFFFF) - var high = UInt32((mul >> 64) & 0xFFFFFFFF) - return Decimal(low, mid, high, is_nagative, combined_scale) + var low = UInt32(prod & 0xFFFFFFFF) + var mid = UInt32((prod >> 32) & 0xFFFFFFFF) + var high = UInt32((prod >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, combined_scale) # Combined scale no more than max precision, truncate with rounding else: - var num_digits = decimojo.utility.number_of_digits(mul) + var num_digits = decimojo.utility.number_of_digits(prod) var num_digits_to_keep = num_digits - ( combined_scale - Decimal.MAX_PRECISION ) - mul = decimojo.utility.truncate_to_digits(mul, num_digits_to_keep) + prod = decimojo.utility.truncate_to_digits(prod, num_digits_to_keep) var final_scale = min(Decimal.MAX_PRECISION, combined_scale) - var low = UInt32(mul & 0xFFFFFFFF) - var mid = UInt32((mul >> 32) & 0xFFFFFFFF) - var high = UInt32((mul >> 64) & 0xFFFFFFFF) - return Decimal(low, mid, high, is_nagative, final_scale) + var low = UInt32(prod & 0xFFFFFFFF) + var mid = UInt32((prod >> 32) & 0xFFFFFFFF) + var high = UInt32((prod >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, final_scale) # SUB-CASE: Both operands are moderate # The bits of the product will not exceed 128 bits @@ -500,19 +506,19 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: # Either raises an error if intergral part overflows # Or truncates the product to fit into Decimal's capacity if combined_num_bits <= 128: - var mul: UInt128 = x1_coef * x2_coef + var prod: UInt128 = x1_coef * x2_coef # Check outflow # The number of digits of the integral part var num_digits_of_integral_part = decimojo.utility.number_of_digits( - mul + prod ) - combined_scale # Truncated first 29 digits - var truncated_mul_at_max_length = decimojo.utility.truncate_to_digits( - mul, Decimal.MAX_VALUE_DIGITS + var truncated_prod_at_max_length = decimojo.utility.truncate_to_digits( + prod, Decimal.MAX_VALUE_DIGITS ) if (num_digits_of_integral_part >= Decimal.MAX_VALUE_DIGITS) & ( - truncated_mul_at_max_length > Decimal.MAX_AS_UINT128 + truncated_prod_at_max_length > Decimal.MAX_AS_UINT128 ): raise Error("Error in `multiply()`: Decimal overflow") @@ -525,22 +531,22 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: # If the first 29 digits exceed the limit, # we need to adjust the num_digits_of_decimal_part by -1 # so that the final coefficient will be of 28 digits. - if truncated_mul_at_max_length > Decimal.MAX_AS_UINT128: + if truncated_prod_at_max_length > Decimal.MAX_AS_UINT128: num_digits_of_decimal_part -= 1 - mul = decimojo.utility.truncate_to_digits( - mul, Decimal.MAX_VALUE_DIGITS - 1 + prod = decimojo.utility.truncate_to_digits( + prod, Decimal.MAX_VALUE_DIGITS - 1 ) else: - mul = truncated_mul_at_max_length + prod = truncated_prod_at_max_length - # I think combined_scale should always be smaller + # Yuhao's notes: I think combined_scale should always be smaller var final_scale = min(num_digits_of_decimal_part, combined_scale) # Extract the 32-bit components from the UInt128 product - var low = UInt32(mul & 0xFFFFFFFF) - var mid = UInt32((mul >> 32) & 0xFFFFFFFF) - var high = UInt32((mul >> 64) & 0xFFFFFFFF) - return Decimal(low, mid, high, is_nagative, final_scale) + var low = UInt32(prod & 0xFFFFFFFF) + var mid = UInt32((prod >> 32) & 0xFFFFFFFF) + var high = UInt32((prod >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, final_scale) # REMAINING CASES: Both operands are big # The bits of the product will not exceed 192 bits @@ -548,20 +554,20 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: # IMPORTANT: This means that the product will exceed Decimal's capacity # Either raises an error if intergral part overflows # Or truncates the product to fit into Decimal's capacity - var mul: UInt256 = UInt256(x1_coef) * UInt256(x2_coef) + var prod: UInt256 = UInt256(x1_coef) * UInt256(x2_coef) # Check outflow # The number of digits of the integral part var num_digits_of_integral_part = decimojo.utility.number_of_digits( - mul + prod ) - combined_scale # Truncated first 29 digits - var truncated_mul_at_max_length = decimojo.utility.truncate_to_digits( - mul, Decimal.MAX_VALUE_DIGITS + var truncated_prod_at_max_length = decimojo.utility.truncate_to_digits( + prod, Decimal.MAX_VALUE_DIGITS ) # Check for overflow of the integral part after rounding if (num_digits_of_integral_part >= Decimal.MAX_VALUE_DIGITS) & ( - truncated_mul_at_max_length > Decimal.MAX_AS_UINT256 + truncated_prod_at_max_length > Decimal.MAX_AS_UINT256 ): raise Error("Error in `multiply()`: Decimal overflow") @@ -574,29 +580,29 @@ fn multiply(x1: Decimal, x2: Decimal) raises -> Decimal: # If the first 29 digits exceed the limit, # we need to adjust the num_digits_of_decimal_part by -1 # so that the final coefficient will be of 28 digits. - if truncated_mul_at_max_length > Decimal.MAX_AS_UINT256: + if truncated_prod_at_max_length > Decimal.MAX_AS_UINT256: num_digits_of_decimal_part -= 1 - mul = decimojo.utility.truncate_to_digits( - mul, Decimal.MAX_VALUE_DIGITS - 1 + prod = decimojo.utility.truncate_to_digits( + prod, Decimal.MAX_VALUE_DIGITS - 1 ) else: - mul = truncated_mul_at_max_length + prod = truncated_prod_at_max_length # I think combined_scale should always be smaller final_scale = min(num_digits_of_decimal_part, combined_scale) # Extract the 32-bit components from the UInt256 product - var low = UInt32(mul & 0xFFFFFFFF) - var mid = UInt32((mul >> 32) & 0xFFFFFFFF) - var high = UInt32((mul >> 64) & 0xFFFFFFFF) + var low = UInt32(prod & 0xFFFFFFFF) + var mid = UInt32((prod >> 32) & 0xFFFFFFFF) + var high = UInt32((prod >> 64) & 0xFFFFFFFF) - return Decimal(low, mid, high, is_nagative, final_scale) + return Decimal(low, mid, high, is_negative, final_scale) fn true_divide(x1: Decimal, x2: Decimal) raises -> Decimal: """ Divides x1 by x2 and returns a new Decimal containing the quotient. - Uses a simpler string-based long division approach. + Uses a simpler string-based long division approach as fallback. Args: x1: The dividend. @@ -609,11 +615,25 @@ fn true_divide(x1: Decimal, x2: Decimal) raises -> Decimal: Error: If x2 is zero. """ + # print("----------------------------------------") + # print("DEBUG divide()") + # print("DEBUG: x1", x1) + # print("DEBUG: x2", x2) + + # Treatment for special cases + # 對各類特殊情況進行處理 + + # SPECIAL CASE: zero divisor + # 特例: 除數爲零 # Check for division by zero if x2.is_zero(): raise Error("Error in `__truediv__`: Division by zero") - # Special case: if dividend is zero, return zero with appropriate scale + # SPECIAL CASE: zero dividend + # If dividend is zero, return zero with appropriate scale + # The final scale is the (scale 1 - scale 2) floored to 0 + # For example, 0.000 / 1234.0 = 0.00 + # For example, 0.00 / 1.3456 = 0 if x1.is_zero(): var result = Decimal.ZERO() var result_scale = max(0, x1.scale() - x2.scale()) @@ -622,152 +642,361 @@ fn true_divide(x1: Decimal, x2: Decimal) raises -> Decimal: ) return result - # If dividing identical numbers, return 1 - if ( - x1.low == x2.low - and x1.mid == x2.mid - and x1.high == x2.high - and x1.scale() == x2.scale() - ): - return Decimal.ONE() - - # Determine sign of result (positive if signs are the same, negative otherwise) - var result_is_negative = x1.is_negative() != x2.is_negative() - - # Get coefficients as strings (absolute values) - var dividend_coef = String(x1.coefficient()) - var divisor_coef = String(x2.coefficient()) + var x1_coef = x1.coefficient() + var x2_coef = x2.coefficient() + var x1_scale = x1.scale() + var x2_scale = x2.scale() + var diff_scale = x1_scale - x2_scale + var is_negative = x1.is_negative() != x2.is_negative() + + # SPECIAL CASE: one dividend or coefficient of dividend is one + # 特例: 除數爲一或者除數的係數爲一 + # Return divisor with appropriate scale and sign + # For example, 1.412 / 1 = 1.412 + # For example, 10.123 / 0.0001 = 101230 + # For example, 1991.10180000 / 0.01 = 199110.180000 + if x2_coef == 1: + # SUB-CASE: divisor is 1 + # If divisor is 1, return dividend with correct sign + if x2_scale == 0: + return Decimal(x1.low, x1.mid, x1.high, is_negative, x1_scale) - # Use string-based division to avoid overflow with large numbers + # SUB-CASE: divisor is of coefficient 1 with positive scale + # diff_scale > 0, then final scale is diff_scale + elif diff_scale > 0: + return Decimal(x1.low, x1.mid, x1.high, is_negative, diff_scale) - # Determine precision needed for calculation - var working_precision = Decimal.MAX_VALUE_DIGITS + 1 # +1 for potential rounding + # diff_scale < 0, then times 10 ** (-diff_scale) + else: + # print("DEBUG: x1_coef", x1_coef) + # print("DEBUG: x1_scale", x1_scale) + # print("DEBUG: x2_coef", x2_coef) + # print("DEBUG: x2_scale", x2_scale) + # print("DEBUG: diff_scale", diff_scale) + + # If the result can be stored in UInt128 + if ( + decimojo.utility.number_of_digits(x1_coef) - diff_scale + < Decimal.MAX_VALUE_DIGITS + ): + var quot = x1_coef * UInt128(10) ** (-diff_scale) + # print("DEBUG: quot", quot) + var low = UInt32(quot & 0xFFFFFFFF) + var mid = UInt32((quot >> 32) & 0xFFFFFFFF) + var high = UInt32((quot >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, 0) + + # If the result should be stored in UInt256 + else: + var quot = UInt256(x1_coef) * UInt256(10) ** (-diff_scale) + # print("DEBUG: quot", quot) + if quot > Decimal.MAX_AS_UINT256: + raise Error("Error in `true_divide()`: Decimal overflow") + else: + var low = UInt32(quot & 0xFFFFFFFF) + var mid = UInt32((quot >> 32) & 0xFFFFFFFF) + var high = UInt32((quot >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, 0) + + # SPECIAL CASE: The coefficients are equal + # 特例: 係數相等 + # For example, 1234.5678 / 1234.5678 = 1.0000 + # Return 1 with appropriate scale and sign + if x1_coef == x2_coef: + # SUB-CASE: The scales are equal + # If the scales are equal, return 1 with the scale of 0 + # For example, 1234.5678 / 1234.5678 = 1 + # SUB-CASE: The scales are positive + # If the scales are positive, return 1 with the difference in scales + # For example, 0.1234 / 1234 = 0.0001 + if diff_scale >= 0: + return Decimal(1, 0, 0, is_negative, diff_scale) + + # SUB-CASE: The scales are negative + # diff_scale < 0, then times 1e-diff_scale + # For example, 1234 / 0.1234 = 10000 + # Since -diff_scale is less than 28, the result would not overflow + else: + var quot = UInt128(1) * UInt128(10) ** (-diff_scale) + var low = UInt32(quot & 0xFFFFFFFF) + var mid = UInt32((quot >> 32) & 0xFFFFFFFF) + var high = UInt32((quot >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, 0) - # Perform long division algorithm - var quotient = String("") - var remainder = String("") - var digit = 0 - var current_pos = 0 - var processed_all_dividend = False - var number_of_significant_digits_of_quotient = 0 + # SPECIAL CASE: Modulus of coefficients is zero (exact division) + # 特例: 係數的餘數爲零 (可除盡) + # For example, 32 / 2 = 16 + # For example, 18.00 / 3.0 = 6.0 + # For example, 123456780000 / 1000 = 123456780 + # For example, 246824.68 / 12.341234 = 20000 + if x1_coef % x2_coef == 0: + if diff_scale >= 0: + # If diff_scale >= 0, return the quotient with diff_scale + # Yuhao's notes: + # Because the dividor == 1 has been handled before dividor shoud be greater than 1 + # High will be zero because the quotient is less than 2^48 + # For safety, we still calcuate the high word + var quot = x1_coef // x2_coef + var low = UInt32(quot & 0xFFFFFFFF) + var mid = UInt32((quot >> 32) & 0xFFFFFFFF) + var high = UInt32((quot >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, diff_scale) - while number_of_significant_digits_of_quotient < working_precision: - # Grab next digit from dividend if available - if current_pos < len(dividend_coef): - remainder += dividend_coef[current_pos] - current_pos += 1 else: - # If we've processed all dividend digits, add a zero - if not processed_all_dividend: - processed_all_dividend = True - remainder += "0" - - # Remove leading zeros from remainder for cleaner comparison - var remainder_start = 0 - while ( - remainder_start < len(remainder) - 1 - and remainder[remainder_start] == "0" + # If diff_scale < 0, return the quotient with scaling up + # Posibly overflow, so we need to check + + var quot = x1_coef // x2_coef + + # If the result can be stored in UInt128 + if ( + decimojo.utility.number_of_digits(quot) - diff_scale + < Decimal.MAX_VALUE_DIGITS + ): + var quot = quot * UInt128(10) ** (-diff_scale) + var low = UInt32(quot & 0xFFFFFFFF) + var mid = UInt32((quot >> 32) & 0xFFFFFFFF) + var high = UInt32((quot >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, 0) + + # If the result should be stored in UInt256 + else: + var quot = UInt256(quot) * UInt256(10) ** (-diff_scale) + if quot > Decimal.MAX_AS_UINT256: + raise Error("Error in `true_divide()`: Decimal overflow") + else: + var low = UInt32(quot & 0xFFFFFFFF) + var mid = UInt32((quot >> 32) & 0xFFFFFFFF) + var high = UInt32((quot >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, 0) + + # REMAINING CASES: Perform long division + # 其他情況: 進行長除法 + # + # Example: 123456.789 / 12.8 = 964506.1640625 + # x1_coef = 123456789, x2_coef = 128 + # x1_scale = 3, x2_scale = 1, diff_scale = 2 + # Step 0: 123456789 // 128 -> quot = 964506, rem = 21 + # Step 1: (21 * 10) // 128 -> quot = 1, rem = 82 + # Step 2: (82 * 10) // 128 -> quot = 6, rem = 52 + # Step 3: (52 * 10) // 128 -> quot = 4, rem = 8 + # Step 4: (8 * 10) // 128 -> quot = 0, rem = 80 + # Step 5: (80 * 10) // 128 -> quot = 6, rem = 32 + # Step 6: (32 * 10) // 128 -> quot = 2, rem = 64 + # Step 7: (64 * 10) // 128 -> quot = 5, rem = 0 + # Result: 9645061640625 with scale 9 (= step_counter + diff_scale) + # + # Example: 12345678.9 / 1.28 = 9645061.640625 + # x1_coef = 123456789, x2_coef = 128 + # x1_scale = 1, x2_scale = 2, diff_scale = -1 + # Result: 9645061640625 with scale 6 (= step_counter + diff_scale) + # + # Long division algorithm + # Stop when remainder is zero or precision is reached or the optimal number of steps is reached + # + # Yuhao's notes: How to determine the optimal number of steps? + # First, we need to consider that the max scale (precision) is 28 + # Second, we need to consider the significant digits of the quotient + # EXAMPLE: 1 / 1.1111111111111111111111111111 ~= 0.900000000000000000000000000090 + # If we only consider the precision, we just need 28 steps + # Then quotient of coefficients would be zeros + # Approach 1: The optimal number of steps should be approximately + # max_len - diff_digits - digits_of_first_quotient + 1 + # Approach 2: Times 10**(-diff_digits) to the dividend and then perform the long division + # The number of steps is set to be max_len - digits_of_first_quotient + 1 + # so that we just need to scale up one than loop -diff_digits times + # + # Get intitial quotient and remainder + # Yuhao's notes: remainder should be positive beacuse the previous cases have been handled + # 朱宇浩注: 餘數應該爲正,因爲之前的特例已經處理過了 + + var x1_number_of_digits = decimojo.utility.number_of_digits(x1_coef) + var x2_number_of_digits = decimojo.utility.number_of_digits(x2_coef) + var diff_digits = x1_number_of_digits - x2_number_of_digits + # Here is an estimation of the maximum possible number of digits of the quotient's integral part + # If it is higher than 28, we need to use UInt256 to store the quotient + var est_max_num_of_digits_of_quot_int_part = diff_digits - diff_scale + 1 + var is_use_uint128 = est_max_num_of_digits_of_quot_int_part < Decimal.MAX_VALUE_DIGITS + + # SUB-CASE: Use UInt128 to store the quotient + # If the quotient's integral part is less than 28 digits, we can use UInt128 + # if is_use_uint128: + var quot: UInt128 + var rem: UInt128 + var ajusted_scale = 0 + + # The adjusted dividend coefficient will not exceed 2^96 - 1 + if diff_digits < 0: + var adjusted_x1_coef = x1_coef * UInt128(10) ** (-diff_digits) + quot = adjusted_x1_coef // x2_coef + rem = adjusted_x1_coef % x2_coef + ajusted_scale = -diff_digits + else: + quot = x1_coef // x2_coef + rem = x1_coef % x2_coef + + if is_use_uint128: + # Maximum number of steps is MAX_VALUE_DIGITS - num_digits_first_quot + 1 + # num_digis_first_quot is the number of digits of the quotient before using long division + # The extra digit is used for rounding up when it is 5 and not exact division + # 最大步數加一,用於捨去項爲5且非精確相除時向上捨去 + + # digit is the tempory quotient digit + var digit = UInt128(0) + # The final step counter stands for the number of dicimal points + var step_counter = 0 + var num_digits_first_quot = decimojo.utility.number_of_digits(quot) + while (rem != 0) and ( + step_counter + < (Decimal.MAX_VALUE_DIGITS - num_digits_first_quot + 1) ): - remainder_start += 1 - remainder = remainder[remainder_start:] + # Multiply remainder by 10 + rem *= 10 + # Calculate next quotient digit + digit = rem // x2_coef + quot = quot * 10 + digit + # Calculate new remainder + rem = rem % x2_coef + # Increment step counter + step_counter += 1 + # Check if division is exact + + # Yuhao's notes: When the remainder is non-zero at the end and the the digit to round is 5 + # we always round up, even if the rounding mode is round half to even + # 朱宇浩注: 捨去項爲5時,其後方的數字可能會影響捨去項,但後方數字可能是無限位,所以無法確定 + # 比如: 1.0000000000000000000000000000_5 可能是 1.0000000000000000000000000000_5{100 zeros}1 + # 但我們只能算到 1.0000000000000000000000000000_5, + # 在銀行家捨去法中,我們將捨去項爲5時,向上捨去, 保留28位後爲1.0000000000000000000000000000 + # 這樣的捨去法是不準確的,所以我們一律在到達餘數非零且捨去項爲5時,向上捨去 + var is_exact_division: Bool = False + if rem == 0: + is_exact_division = True + else: + if digit == 5: + # Not exact division, round up the last digit + quot += 1 + + var scale_of_quot = step_counter + diff_scale + ajusted_scale + # If the scale is negative, we need to scale up the quotient + if scale_of_quot < 0: + quot = quot * UInt128(10) ** (-scale_of_quot) + scale_of_quot = 0 + var number_of_digits_quot = decimojo.utility.number_of_digits(quot) + var number_of_digits_quot_int_part = number_of_digits_quot - scale_of_quot + + # If quot is within MAX, return the result + if quot <= Decimal.MAX_AS_UINT128: + var low = UInt32(quot & 0xFFFFFFFF) + var mid = UInt32((quot >> 32) & 0xFFFFFFFF) + var high = UInt32((quot >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, scale_of_quot) + + # Otherwise, we need to truncate the first 29 or 28 digits + else: + var truncated_quot = decimojo.utility.truncate_to_digits( + quot, Decimal.MAX_VALUE_DIGITS + ) + var scale_of_truncated_quot = ( + Decimal.MAX_VALUE_DIGITS - number_of_digits_quot_int_part + ) + if truncated_quot > Decimal.MAX_AS_UINT128: + truncated_quot = decimojo.utility.truncate_to_digits( + quot, Decimal.MAX_VALUE_DIGITS - 1 + ) + scale_of_truncated_quot -= 1 - # Compare remainder with divisor to determine next quotient digit - digit = 0 - var can_subtract = False + var low = UInt32(truncated_quot & 0xFFFFFFFF) + var mid = UInt32((truncated_quot >> 32) & 0xFFFFFFFF) + var high = UInt32((truncated_quot >> 64) & 0xFFFFFFFF) - # Check if remainder >= divisor_coef - if len(remainder) > len(divisor_coef) or ( - len(remainder) == len(divisor_coef) and remainder >= divisor_coef - ): - can_subtract = True - - if can_subtract: - # Find how many times divisor goes into remainder - while True: - # Try to subtract divisor from remainder - var new_remainder = _subtract_strings(remainder, divisor_coef) - if ( - new_remainder[0] == "-" - ): # Negative result means we've gone too far - break - remainder = new_remainder - digit += 1 - - # Add digit to quotient - quotient += String(digit) - number_of_significant_digits_of_quotient = len( - decimojo.str._remove_leading_zeros(quotient) - ) + return Decimal(low, mid, high, is_negative, scale_of_truncated_quot) - # Check if division is exact - var is_exact = remainder == "0" and current_pos >= len(dividend_coef) + # SUB-CASE: Use UInt256 to store the quotient + # Also the FALLBACK approach for the remaining cases + # If the quotient's integral part is possibly more than 28 digits, we use UInt256 + # It is almost the same also the case above, so we just use the same code - # Remove leading zeros - var leading_zeros = 0 - for i in range(len(quotient)): - if quotient[i] == "0": - leading_zeros += 1 + else: + # Maximum number of steps is MAX_VALUE_DIGITS - num_digits_first_quot + 1 + # The extra digit is used for rounding up when it is 5 and not exact division + # 最大步數加一,用於捨去項爲5且非精確相除時向上捨去 + + var quot256: UInt256 = UInt256(quot) + var rem256: UInt256 = UInt256(rem) + # digit is the tempory quotient digit + var digit = UInt256(0) + # The final step counter stands for the number of dicimal points + var step_counter = 0 + var num_digits_first_quot = decimojo.utility.number_of_digits(quot256) + while (rem256 != 0) and ( + step_counter + < (Decimal.MAX_VALUE_DIGITS - num_digits_first_quot + 1) + ): + # Multiply remainder by 10 + rem256 *= 10 + # Calculate next quotient digit + digit = rem256 // UInt256(x2_coef) + quot256 = quot256 * 10 + digit + # Calculate new remainder + rem256 = rem256 % UInt256(x2_coef) + # Increment step counter + step_counter += 1 + # Check if division is exact + + var is_exact_division: Bool = False + if rem256 == 0: + is_exact_division = True else: - break + if digit == 5: + # Not exact division, round up the last digit + quot256 += 1 + + var scale_of_quot = step_counter + diff_scale + ajusted_scale + # If the scale is negative, we need to scale up the quotient + if scale_of_quot < 0: + quot256 = quot256 * UInt256(10) ** (-scale_of_quot) + scale_of_quot = 0 + var number_of_digits_quot = decimojo.utility.number_of_digits(quot256) + var number_of_digits_quot_int_part = number_of_digits_quot - scale_of_quot + + # If quot is within MAX, return the result + if quot256 <= Decimal.MAX_AS_UINT256: + var low = UInt32(quot256 & 0xFFFFFFFF) + var mid = UInt32((quot256 >> 32) & 0xFFFFFFFF) + var high = UInt32((quot256 >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, is_negative, scale_of_quot) + + # Otherwise, we need to truncate the first 29 or 28 digits + else: + var truncated_quot = decimojo.utility.truncate_to_digits( + quot256, Decimal.MAX_VALUE_DIGITS + ) - if leading_zeros == len(quotient): - # All zeros, keep just one - quotient = "0" - elif leading_zeros > 0: - quotient = quotient[leading_zeros:] - - # Handle trailing zeros for exact division - var trailing_zeros = 0 - if is_exact and len(quotient) > 1: # Don't remove single digit - for i in range(len(quotient) - 1, 0, -1): - if quotient[i] == "0": - trailing_zeros += 1 - else: - break - - if trailing_zeros > 0: - quotient = quotient[: len(quotient) - trailing_zeros] - - # Calculate decimal point position - var dividend_scientific_exponent = x1.scientific_exponent() - var divisor_scientific_exponent = x2.scientific_exponent() - var result_scientific_exponent = dividend_scientific_exponent - divisor_scientific_exponent - - if decimojo.str._remove_trailing_zeros( - dividend_coef - ) < decimojo.str._remove_trailing_zeros(divisor_coef): - # If dividend < divisor, result < 1 - result_scientific_exponent -= 1 - - var decimal_pos = result_scientific_exponent + 1 - - # Format result with decimal point - var result_str = String("") - - if decimal_pos <= 0: - # decimal_pos <= 0, needs leading zeros - # For example, decimal_pos = -1 - # 1234 -> 0.1234 - result_str = "0." + "0" * (-decimal_pos) + quotient - elif decimal_pos >= len(quotient): - # All digits are to the left of the decimal point - # For example, decimal_pos = 5 - # 1234 -> 12340 - result_str = quotient + "0" * (decimal_pos - len(quotient)) - else: - # Insert decimal point within the digits - # For example, decimal_pos = 2 - # 1234 -> 12.34 - result_str = quotient[:decimal_pos] + "." + quotient[decimal_pos:] + # If integer part of quot is more than max, raise error + if (number_of_digits_quot_int_part > Decimal.MAX_VALUE_DIGITS) or ( + (number_of_digits_quot_int_part == Decimal.MAX_VALUE_DIGITS) + and (truncated_quot > Decimal.MAX_AS_UINT256) + ): + raise Error("Error in `true_divide()`: Decimal overflow") - # Apply sign - if result_is_negative and result_str != "0": - result_str = "-" + result_str + var scale_of_truncated_quot = ( + Decimal.MAX_VALUE_DIGITS - number_of_digits_quot_int_part + ) - # Convert to Decimal and return - var result = Decimal(result_str) + if truncated_quot > Decimal.MAX_AS_UINT256: + truncated_quot = decimojo.utility.truncate_to_digits( + quot256, Decimal.MAX_VALUE_DIGITS - 1 + ) + scale_of_truncated_quot -= 1 - return result + print("DEBUG: truncated_quot", truncated_quot) + print("DEBUG: scale_of_truncated_quot", scale_of_truncated_quot) + + var low = UInt32(truncated_quot & 0xFFFFFFFF) + var mid = UInt32((truncated_quot >> 32) & 0xFFFFFFFF) + var high = UInt32((truncated_quot >> 64) & 0xFFFFFFFF) + + return Decimal(low, mid, high, is_negative, scale_of_truncated_quot) fn power(base: Decimal, exponent: Decimal) raises -> Decimal: @@ -899,10 +1128,6 @@ fn sqrt(x: Decimal) raises -> Decimal: if x == Decimal.ONE(): return Decimal.ONE() - # Working precision - we'll compute with extra digits and round at the end - var working_precision = UInt32(x.scale() * 2) - working_precision = max(working_precision, UInt32(10)) # At least 10 digits - # Initial guess - a good guess helps converge faster # For numbers near 1, use the number itself # For very small or large numbers, scale appropriately @@ -943,6 +1168,11 @@ fn sqrt(x: Decimal) raises -> Decimal: var max_iterations = 100 # Prevent infinite loops while guess != prev_guess and iteration_count < max_iterations: + # print("------------------------------------------------------") + # print("DEBUG: iteration_count", iteration_count) + # print("DEBUG: prev_guess", prev_guess) + # print("DEBUG: guess", guess) + prev_guess = guess try: @@ -954,70 +1184,20 @@ fn sqrt(x: Decimal) raises -> Decimal: iteration_count += 1 - # Round to appropriate precision - typically half the working precision - var result_precision = x.scale() - if result_precision % 2 == 1: - # For odd scales, add 1 to ensure proper rounding - result_precision += 1 - - # The result scale should be approximately half the input scale - result_precision = result_precision // 2 - - # Format to the appropriate number of decimal places - var result_str = String(guess) - - try: - var rounded_result = Decimal(result_str) - return rounded_result - except e: - raise e - - -fn _subtract_strings(a: String, b: String) -> String: - """ - Subtracts string b from string a and returns the result as a string. - The input strings must be integers. - - Args: - a: The string to subtract from. Must be an integer. - b: The string to subtract. Must be an integer. - - Returns: - A string containing the result of the subtraction. - """ - - # Ensure a is longer or equal to b by padding with zeros - var a_padded = a - var b_padded = b - - if len(a) < len(b): - a_padded = "0" * (len(b) - len(a)) + a - elif len(b) < len(a): - b_padded = "0" * (len(a) - len(b)) + b - - var result = String("") - var borrow = 0 - - # Perform subtraction digit by digit from right to left - for i in range(len(a_padded) - 1, -1, -1): - var digit_a = ord(a_padded[i]) - ord("0") - var digit_b = ord(b_padded[i]) - ord("0") + borrow - - if digit_a < digit_b: - digit_a += 10 - borrow = 1 + # If exact square root found + # Remove trailing zeros after the decimal point + var guess_coef = guess.coefficient() + var count = 0 + for _ in range(guess.scale()): + if guess_coef % 10 == 0: + guess_coef //= 10 + count += 1 else: - borrow = 0 - - result = String(digit_a - digit_b) + result - - # Check if result is negative - if borrow > 0: - return "-" + result - - # Remove leading zeros - var start_idx = 0 - while start_idx < len(result) - 1 and result[start_idx] == "0": - start_idx += 1 + break + if guess_coef * guess_coef == x.coefficient(): + var low = UInt32(guess_coef & 0xFFFFFFFF) + var mid = UInt32((guess_coef >> 32) & 0xFFFFFFFF) + var high = UInt32((guess_coef >> 64) & 0xFFFFFFFF) + return Decimal(low, mid, high, False, guess.scale() - count) - return result[start_idx:] + return guess diff --git a/tests/test_arithmetics.mojo b/tests/test_arithmetics.mojo index 2515795..76db1f5 100644 --- a/tests/test_arithmetics.mojo +++ b/tests/test_arithmetics.mojo @@ -7,6 +7,7 @@ import testing fn test_add() raises: + print("------------------------------------------------------") print("Testing decimal addition...") # Test case 1: Simple addition with same scale @@ -160,6 +161,7 @@ fn test_add() raises: fn test_negation() raises: + print("------------------------------------------------------") print("Testing decimal negation...") # Test case 1: Negate positive number @@ -237,6 +239,7 @@ fn test_negation() raises: fn test_abs() raises: + print("------------------------------------------------------") print("Testing decimal absolute value...") # Test case 1: Absolute value of positive number @@ -318,6 +321,7 @@ fn test_abs() raises: fn test_subtract() raises: + print("------------------------------------------------------") print("Testing decimal subtraction...") # Test case 1: Simple subtraction with same scale @@ -450,6 +454,7 @@ fn test_subtract() raises: fn test_multiplication() raises: + print("------------------------------------------------------") print("Testing decimal multiplication...") # Test case 1: Simple multiplication with same scale @@ -617,345 +622,8 @@ fn test_multiplication() raises: print("Decimal multiplication tests passed!") -fn test_division() raises: - print("Testing decimal division...") - - # Test case 1: Simple division with same scale - var a1 = Decimal("10.00") - var b1 = Decimal("2.00") - var result1 = a1 / b1 - testing.assert_equal( - String(result1), "5", "Simple division with same scale" - ) - - # Test case 2: Division with different scales - var a2 = Decimal("10.5") - var b2 = Decimal("2.1") - var result2 = a2 / b2 - testing.assert_equal( - String(result2), - "5", - "Division with different scales", - ) - - # Test case 3: Division with negative numbers - var a3 = Decimal("12.34") - var b3 = Decimal("-2.0") - var result3 = a3 / b3 - testing.assert_equal( - String(result3), - "-6.17", - "Division with negative number", - ) - - # Test case 4: Division with both negative numbers - var a4 = Decimal("-12.34") - var b4 = Decimal("-2.0") - var result4 = a4 / b4 - testing.assert_equal( - String(result4), - "6.17", - "Division with both negative numbers", - ) - - # Test case 5: Division by one - var a5 = Decimal("12.34") - var b5 = Decimal("1.0") - var result5 = a5 / b5 - testing.assert_equal(String(result5), "12.34", "Division by one") - - # Test case 6: Division resulting in repeating decimal - var a6 = Decimal("10.0") - var b6 = Decimal("3.0") - var result6 = a6 / b6 - testing.assert_equal( - String(result6).startswith("3.333333333"), - True, - "Division resulting in repeating decimal", - ) - - # Test case 7: Division resulting in exact value - var a7 = Decimal("10.0") - var b7 = Decimal("5.0") - var result7 = a7 / b7 - testing.assert_equal( - String(result7), "2", "Division resulting in exact value" - ) - - # Test case 8: Division of zero by non-zero - var a8 = Decimal("0.0") - var b8 = Decimal("5.0") - var result8 = a8 / b8 - testing.assert_equal(String(result8), "0", "Division of zero by non-zero") - - # Test case 9: Division with small numbers - var a9 = Decimal("0.001") - var b9 = Decimal("0.01") - var result9 = a9 / b9 - testing.assert_equal(String(result9), "0.1", "Division with small numbers") - - # Test case 10: Division with large numbers - var a10 = Decimal("1000000.0") - var b10 = Decimal("0.001") - var result10 = a10 / b10 - testing.assert_equal( - String(result10), "1000000000", "Division with large numbers" - ) - - # Test case 11: Division requiring rounding - var a11 = Decimal("1.0") - var b11 = Decimal("7.0") - var result11 = a11 / b11 - testing.assert_equal( - String(result11).startswith("0.142857142857142857142857"), - True, - "Division requiring rounding", - ) - - # Test case 12: Division with mixed precision - var a12 = Decimal("123.456") - var b12 = Decimal("0.1") - var result12 = a12 / b12 - testing.assert_equal( - String(result12), "1234.56", "Division with mixed precision" - ) - - # Test case 13: Verify mathematical identity (a/b)*b ≈ a within rounding error - var a13 = Decimal("123.45") - var b13 = Decimal("7.89") - var div_result = a13 / b13 - var mul_result = div_result * b13 - # Because of rounding, we don't expect exact equality, so check if the difference is small - var diff = a13 - mul_result - var abs_diff = -diff if diff.is_negative() else diff - var is_close = Float64(String(abs_diff)) < 0.0001 - testing.assert_equal(is_close, True, "(a/b)*b should approximately equal a") - - # Test case 14: Division of number by itself should be 1 - var a14 = Decimal("123.45") - var result14 = a14 / a14 - testing.assert_equal( - String(result14), - "1", - "Division of number by itself", - ) - - # Test case 15: Division by zero should raise an error - var a15 = Decimal("123.45") - var b15 = Decimal("0.0") - try: - var result15 = a15 / b15 - testing.assert_equal( - True, False, "Division by zero should raise an error" - ) - except: - testing.assert_equal(True, True, "Division by zero correctly rejected") - - # ============= ADDITIONAL DIVISION TEST CASES ============= - print("\nTesting additional division scenarios...") - - # Test case 16: Division with very large number by very small number - var a16 = Decimal("1000000000") - var b16 = Decimal("0.0001") - var result16 = a16 / b16 - testing.assert_equal( - String(result16), - "10000000000000", - "Large number divided by small number", - ) - - # Test case 17: Division with very small number by very large number - var a17 = Decimal("0.0001") - var b17 = Decimal("1000000000") - var result17 = a17 / b17 - testing.assert_true( - String(result17).startswith("0.0000000000001"), - "Small number divided by large number", - ) - - # Test case 18: Division resulting in repeating decimal - var a18 = Decimal("1") - var b18 = Decimal("3") - var result18 = a18 / b18 - testing.assert_true( - String(result18).startswith("0.33333333"), - "Division resulting in repeating decimal (1/3)", - ) - - # Test case 19: Division by powers of 10 - var a19 = Decimal("123.456") - var b19 = Decimal("10") - var result19 = a19 / b19 - testing.assert_equal( - String(result19), - "12.3456", - "Division by power of 10", - ) - - # Test case 20: Division by powers of 10 (another case) - var a20 = Decimal("123.456") - var b20 = Decimal("0.01") - var result20 = a20 / b20 - testing.assert_equal( - String(result20), - "12345.6", - "Division by 0.01 (multiply by 100)", - ) - - # Test case 21: Division of nearly equal numbers - var a21 = Decimal("1.000001") - var b21 = Decimal("1") - var result21 = a21 / b21 - testing.assert_equal( - String(result21), - "1.000001", - "Division of nearly equal numbers", - ) - - # Test case 22: Division resulting in a number with many trailing zeros - var a22 = Decimal("1") - var b22 = Decimal("8") - var result22 = a22 / b22 - testing.assert_true( - String(result22).startswith("0.125"), - "Division resulting in an exact decimal with trailing zeros", - ) - - # Test case 23: Division with negative numerator - var a23 = Decimal("-50") - var b23 = Decimal("10") - var result23 = a23 / b23 - testing.assert_equal( - String(result23), - "-5", - "Division with negative numerator", - ) - - # Test case 24: Division with negative denominator - var a24 = Decimal("50") - var b24 = Decimal("-10") - var result24 = a24 / b24 - testing.assert_equal( - String(result24), - "-5", - "Division with negative denominator", - ) - - # Test case 25: Division with both negative - var a25 = Decimal("-50") - var b25 = Decimal("-10") - var result25 = a25 / b25 - testing.assert_equal( - String(result25), - "5", - "Division with both negative numbers", - ) - - # Test case 26: Division resulting in exact integer - var a26 = Decimal("96.75") - var b26 = Decimal("4.5") - var result26 = a26 / b26 - testing.assert_equal( - String(result26), - "21.5", - "Division resulting in exact value", - ) - - # Test case 27: Division with high precision numbers - var a27 = Decimal("0.123456789012345678901234567") - var b27 = Decimal("0.987654321098765432109876543") - var result27 = a27 / b27 - testing.assert_true( - String(result27).startswith("0.12499"), - "Division of high precision numbers", - ) - - # Test case 28: Division with extreme digit patterns - var a28 = Decimal("9" * 15) # 999999999999999 - var b28 = Decimal("9" * 5) # 99999 - var result28 = a28 / b28 - testing.assert_equal( - String(result28), - "10000100001", - "Division with extreme digit patterns (all 9's)", - ) - - # Test case 29: Division where result is zero - var a29 = Decimal("0") - var b29 = Decimal("123.45") - var result29 = a29 / b29 - testing.assert_equal( - String(result29), - "0", - "Division where result is zero", - ) - - # Test case 30: Division where numerator is smaller than denominator - var a30 = Decimal("1") - var b30 = Decimal("10000") - var result30 = a30 / b30 - testing.assert_equal( - String(result30), - "0.0001", - "Division where numerator is smaller than denominator", - ) - - # Test case 31: Division resulting in scientific notation range - var a31 = Decimal("1") - var b31 = Decimal("1" + "0" * 20) # 10^20 - var result31 = a31 / b31 - testing.assert_true( - String(result31).startswith("0.00000000000000000001"), - "Division resulting in very small number", - ) - - # Test case 32: Division with mixed precision - var a32 = Decimal("1") - var b32 = Decimal("3.33333333333333333333333333") - var result32 = a32 / b32 - testing.assert_true( - String(result32).startswith("0.3"), - "Division with mixed precision numbers", - ) - - # Test case 33: Division by fractional power of 10 - var a33 = Decimal("5.5") - var b33 = Decimal("0.055") - var result33 = a33 / b33 - testing.assert_equal( - String(result33), - "100", - "Division by fractional power of 10", - ) - - # Test case 34: Division with rounding at precision boundary - var a34 = Decimal("2") - var b34 = Decimal("3") - var result34 = a34 / b34 - # Result should be about 0.66666... - var expected34 = Decimal("0.66666666666666666666666666667") - print(result34) - testing.assert_equal( - result34, - expected34, - "Division with rounding at precision boundary", - ) - - # Test case 35: Division by value very close to zero - var a35 = Decimal("1") - var b35 = Decimal("0." + "0" * 26 + "1") # 0.000...0001 (27 zeros) - var result35 = a35 / b35 - testing.assert_true( - String(result35).startswith("1" + "0" * 27), - "Division by value very close to zero", - ) - - print("Additional division tests passed!") - - print("Decimal division tests passed!") - - fn test_power_integer_exponents() raises: + print("------------------------------------------------------") print("Testing power with integer exponents...") # Test case 1: Base cases: x^0 = 1 for any x except 0 @@ -1018,6 +686,7 @@ fn test_power_integer_exponents() raises: fn test_power_negative_exponents() raises: + print("------------------------------------------------------") print("Testing power with negative integer exponents...") # Test case 1: Basic negative exponent @@ -1056,6 +725,7 @@ fn test_power_negative_exponents() raises: fn test_power_special_cases() raises: + print("------------------------------------------------------") print("Testing power function special cases...") # Test case 1: 0^0 (typically defined as 1) @@ -1094,6 +764,7 @@ fn test_power_special_cases() raises: fn test_power_decimal_exponents() raises: + print("------------------------------------------------------") print("Testing power with decimal exponents...") # Try a few basic decimal exponents if supported @@ -1116,6 +787,7 @@ fn test_power_decimal_exponents() raises: fn test_power_precision() raises: + print("------------------------------------------------------") print("Testing power precision...") # These tests assume we have overloaded the ** operator @@ -1142,6 +814,7 @@ fn test_power_precision() raises: fn test_extreme_cases() raises: + print("------------------------------------------------------") print("Testing extreme cases...") # Test case 1: Addition that results in exactly zero with high precision @@ -1211,9 +884,6 @@ fn main() raises: # Run multiplication tests test_multiplication() - # Run division tests - test_division() - # Run power tests with integer exponents test_power_integer_exponents() diff --git a/tests/test_conversions.mojo b/tests/test_conversions.mojo index 844fe37..1744e95 100644 --- a/tests/test_conversions.mojo +++ b/tests/test_conversions.mojo @@ -9,7 +9,8 @@ import time fn test_int_conversion() raises: - print("\n--- Testing Int Conversion ---") + print("------------------------------------------------------") + print("--- Testing Int Conversion ---") # Test positive integer var d1 = Decimal("123") @@ -49,7 +50,8 @@ fn test_int_conversion() raises: fn test_float_conversion() raises: - print("\n--- Testing Float64 Conversion ---") + print("------------------------------------------------------") + print("--- Testing Float64 Conversion ---") # Test positive number var d1 = Decimal("123.456") @@ -83,7 +85,8 @@ fn test_float_conversion() raises: fn test_str_conversion() raises: - print("\n--- Testing String Conversion ---") + print("------------------------------------------------------") + print("--- Testing String Conversion ---") # Test positive number var d1 = Decimal("123.456") diff --git a/tests/test_creation.mojo b/tests/test_creation.mojo index c2c8b17..30380b2 100644 --- a/tests/test_creation.mojo +++ b/tests/test_creation.mojo @@ -6,8 +6,8 @@ import testing fn test_decimal_from_int() raises: + print("------------------------------------------------------") print("Testing Decimal Creation from Integer Values") - print("------------------------------------------") # Basic integer constructors var zero = Decimal(0) @@ -63,8 +63,8 @@ fn test_decimal_from_int() raises: fn test_decimal_from_float() raises: + print("------------------------------------------------------") print("Testing Decimal Creation from Float Values") - print("----------------------------------------") # Basic float constructors var zero_float = Decimal(0.0) @@ -179,8 +179,8 @@ fn test_decimal_from_float() raises: fn test_decimal_from_string() raises: + print("------------------------------------------------------") print("Testing Decimal Creation from String Values") - print("-----------------------------------------") # Basic string constructors var zero_str = Decimal("0") @@ -325,8 +325,8 @@ fn test_decimal_from_string() raises: fn test_decimal_from_components() raises: + print("------------------------------------------------------") print("Testing Decimal Creation from Components") - print("---------------------------------------") # Test case 1: Zero with zero scale var zero = Decimal(0, 0, 0, False, 0) diff --git a/tests/test_division.mojo b/tests/test_division.mojo new file mode 100644 index 0000000..7309396 --- /dev/null +++ b/tests/test_division.mojo @@ -0,0 +1,857 @@ +""" +Comprehensive test suite for Decimal division operations. +Includes 100 test cases covering edge cases, precision limits, and various scenarios. +""" + +from decimojo.prelude import * +import testing + + +fn test_basic_division() raises: + print("------------------------------------------------------") + print("Testing basic division cases...") + + # 1. Simple integer division + testing.assert_equal( + String(Decimal("10") / Decimal("2")), "5", "Simple integer division" + ) + + # 2. Division with no remainder + testing.assert_equal( + String(Decimal("100") / Decimal("4")), + "25", + "Division with no remainder", + ) + + # 3. Division resulting in non-integer + testing.assert_equal( + String(Decimal("10") / Decimal("4")), + "2.5", + "Division resulting in non-integer", + ) + + # 4. Division by one + testing.assert_equal( + String(Decimal("123.45") / Decimal("1")), "123.45", "Division by one" + ) + + # 5. Division of zero + testing.assert_equal( + String(Decimal("0") / Decimal("42")), "0", "Division of zero" + ) + + # 6. Division with both negative numbers + testing.assert_equal( + String(Decimal("-10") / Decimal("-5")), + "2", + "Division with both negative numbers", + ) + + # 7. Division with negative dividend + testing.assert_equal( + String(Decimal("-10") / Decimal("5")), + "-2", + "Division with negative dividend", + ) + + # 8. Division with negative divisor + testing.assert_equal( + String(Decimal("10") / Decimal("-5")), + "-2", + "Division with negative divisor", + ) + + # 9. Division with decimals, same scale + testing.assert_equal( + String(Decimal("10.5") / Decimal("2.1")), + "5", + "Division with decimals, same scale", + ) + + # 10. Division with decimals, different scales + testing.assert_equal( + String(Decimal("10.5") / Decimal("0.5")), + "21", + "Division with decimals, different scales", + ) + + print("✓ Basic division tests passed!") + + +fn test_repeating_decimals() raises: + print("------------------------------------------------------") + print("Testing division with repeating decimals...") + + # 11. Division resulting in 1/3 + var third = Decimal("1") / Decimal("3") + testing.assert_true( + String(third).startswith("0.33333333333333"), + "Case 11: Division resulting in 1/3 failed", + ) + + # 12. Division resulting in 1/6 + var sixth = Decimal("1") / Decimal("6") + testing.assert_true( + String(sixth).startswith("0.16666666666666"), + "Case 12: Division resulting in 1/6 failed", + ) + + # 13. Division resulting in 1/7 + var seventh = Decimal("1") / Decimal("7") + testing.assert_true( + String(seventh).startswith("0.142857142857142857"), + "Case 13: Division resulting in 1/7 failed", + ) + + # 14. Division resulting in 2/3 + var two_thirds = Decimal("2") / Decimal("3") + testing.assert_true( + String(two_thirds).startswith("0.66666666666666"), + "Case 14: Division resulting in 2/3 failed", + ) + + # 15. Division resulting in 5/6 + var five_sixths = Decimal("5") / Decimal("6") + testing.assert_true( + String(five_sixths).startswith("0.83333333333333"), + "Case 15: Division resulting in 5/6 failed", + ) + + # 16. Division of 1 by 9 + var one_ninth = Decimal("1") / Decimal("9") + testing.assert_true( + String(one_ninth).startswith("0.11111111111111"), + "Case 16: Division of 1 by 9 failed", + ) + + # 17. Division of 1 by 11 + var one_eleventh = Decimal("1") / Decimal("11") + testing.assert_true( + String(one_eleventh).startswith("0.0909090909090"), + "Case 17: Division of 1 by 11 failed", + ) + + # 18. Division of 1 by 12 + var one_twelfth = Decimal("1") / Decimal("12") + testing.assert_true( + String(one_twelfth).startswith("0.08333333333333"), + "Case 18: Division of 1 by 12 failed", + ) + + # 19. Division of 5 by 11 + var five_elevenths = Decimal("5") / Decimal("11") + testing.assert_true( + String(five_elevenths).startswith("0.4545454545454"), + "Case 19: Division of 5 by 11 failed", + ) + + # 20. Division of 10 by 3 + var ten_thirds = Decimal("10") / Decimal("3") + testing.assert_true( + String(ten_thirds).startswith("3.33333333333333"), + "Case 20: Division of 10 by 3 failed", + ) + + print("✓ Repeating decimal tests passed!") + + +fn test_precision_rounding() raises: + print("------------------------------------------------------") + print("Testing division precision and rounding...") + + # 21. Rounding half even (banker's rounding) at precision limit + var a21 = Decimal("2") / Decimal("3") # Should be ~0.6666...67 + var b21 = Decimal("0." + "6" * 27 + "7") # 0.6666...67 + testing.assert_equal( + String(a21), String(b21), "Rounding half even at precision limit" + ) + + # 22. Another case of rounding half even + var a22 = Decimal("1") / Decimal("9") # Should be ~0.1111...11 + var b22 = Decimal("0." + "1" * 28) # 0.1111...11 + testing.assert_equal( + String(a22), String(b22), "Another case of rounding half even" + ) + + # 23. Rounding up at precision limit + var a23 = Decimal("10") / Decimal("3") # Should be ~3.3333...33 + var b23 = Decimal("3." + "3" * 28) # 3.3333...33 + testing.assert_equal( + String(a23), String(b23), "Rounding up at precision limit" + ) + + # 24. Division requiring rounding to precision limit + var a24 = Decimal("1") / Decimal("7") # ~0.142857... + var manually_calculated = Decimal("0.1428571428571428571428571429") + testing.assert_equal( + String(a24), + String(manually_calculated), + "Division requiring rounding to precision limit", + ) + + # 25. Precision limit with repeating 9s + var a25 = Decimal("1") / Decimal("81") # ~0.01234... + var precision_reached = a25.scale() <= Decimal.MAX_PRECISION + testing.assert_true( + precision_reached, "Scale should not exceed MAX_PRECISION" + ) + + # 26. Test precision with negative numbers + var a26 = Decimal("-1") / Decimal("3") + var b26 = Decimal("-0." + "3" * 28) # -0.3333...33 + testing.assert_equal( + String(a26), String(b26), "Test precision with negative numbers" + ) + + # 27. Division with result at exactly precision limit + var a27 = Decimal("1") / Decimal(String("1" + "0" * 28)) # 1/10^28 + testing.assert_equal( + String(a27), + String(Decimal("0." + "0" * 27 + "1")), + "Division with result at exactly precision limit", + ) + + # 28. Division with result needing one more than precision limit + var a28 = Decimal("1") / Decimal(String("1" + "0" * 28)) # 1/10^29 + testing.assert_equal( + String(a28), + String(Decimal("0." + "0" * 27 + "1")), + "Division with result needing one more than precision limit", + ) + + # 29. Division where quotient has more digits than precision allows + var a29 = Decimal("12345678901234567890123456789") / Decimal("7") + testing.assert_true( + a29.scale() <= Decimal.MAX_PRECISION, + "Scale should not exceed MAX_PRECISION", + ) + + # 30. Division where both operands have maximum precision + var a30 = Decimal("0." + "1" * 28) / Decimal("0." + "9" * 28) + testing.assert_true( + a30.scale() <= Decimal.MAX_PRECISION, + "Scale should not exceed MAX_PRECISION", + ) + + print("✓ Precision and rounding tests passed!") + + +fn test_scale_handling() raises: + print("------------------------------------------------------") + print("Testing scale handling in division...") + + # 31. Division by power of 10 + testing.assert_equal( + String(Decimal("123.456") / Decimal("10")), + "12.3456", + "Division by power of 10", + ) + + # 32. Division by 0.1 (multiply by 10) + testing.assert_equal( + String(Decimal("123.456") / Decimal("0.1")), + "1234.56", + "Division by 0.1", + ) + + # 33. Division by 0.01 (multiply by 100) + testing.assert_equal( + String(Decimal("123.456") / Decimal("0.01")), + "12345.6", + "Division by 0.01", + ) + + # 34. Division by 100 (divide by 100) + testing.assert_equal( + String(Decimal("123.456") / Decimal("100")), + "1.23456", + "Division by 100", + ) + + # 35. Division resulting in loss of trailing zeros + testing.assert_equal( + String(Decimal("10.000") / Decimal("2")), + "5.000", + "Division resulting in loss of trailing zeros", + ) + + # 36. Division where quotient needs more decimal places + testing.assert_equal( + String(Decimal("1") / Decimal("8")), + "0.125", + "Division where quotient needs more decimal places", + ) + + # 37. Division where dividend has more scale than divisor + testing.assert_equal( + String(Decimal("0.01") / Decimal("2")), + "0.005", + "Division where dividend has more scale than divisor", + ) + + # 38. Division where divisor has more scale than dividend + testing.assert_equal( + String(Decimal("2") / Decimal("0.01")), + "200", + "Division where divisor has more scale than dividend", + ) + + # 39. Division where both have high scale and result needs less + testing.assert_equal( + String(Decimal("0.0001") / Decimal("0.0001")), + "1", + "Division where both have high scale and result needs less", + ) + + # 40. Division where both have high scale and result needs more + testing.assert_equal( + String(Decimal("0.0001") / Decimal("0.0003")), + "0.3333333333333333333333333333", + "Division where both have high scale and result needs more", + ) + + print("✓ Scale handling tests passed!") + + +fn test_edge_cases() raises: + print("------------------------------------------------------") + print("Testing division edge cases...") + + # 41. Division by very small number close to zero + var a41 = Decimal("1") / Decimal( + "0." + "0" * 27 + "1" + ) # Dividing by 10^-28 + testing.assert_true( + a41 > Decimal(String("1" + "0" * 27)), + "Case 41: Division by very small number failed", + ) + + # 42. Division resulting in a number close to zero + var a42 = Decimal("0." + "0" * 27 + "1") / Decimal("10") # Very small / 10 + testing.assert_equal( + a42, + Decimal("0." + "0" * 28), + "Case 42: Division resulting in number close to zero failed", + ) + + # 43. Division of very large number by very small number + var max_decimal = Decimal.MAX() + var small_divisor = Decimal("0.0001") + try: + var a43 = max_decimal / small_divisor + except: + print( + "Division of very large number by very small number raised" + " exception" + ) + + # 44. Division of minimum representable positive number + var min_positive = Decimal( + "0." + "0" * 27 + "1" + ) # Smallest positive decimal + var a44 = min_positive / Decimal("2") + testing.assert_true(a44.scale() <= Decimal.MAX_PRECISION) + + # 45. Division by power of 2 (binary divisions) + testing.assert_equal( + String(Decimal("1") / Decimal("4")), "0.25", "Division by power of 2" + ) + + # 46. Division by 9's + testing.assert_equal( + String(Decimal("100") / Decimal("9.9")), + "10.101010101010101010101010101", + "Division by 9's", + ) + + # 47. Division resulting in exactly MAX_PRECISION digits + var a47 = Decimal("1") / Decimal("3") + testing.assert_true( + a47.scale() == Decimal.MAX_PRECISION, + "Case 47: Division resulting in exactly MAX_PRECISION digits failed", + ) + + # 48. Division of large integers resulting in max precision + testing.assert_equal( + String(Decimal("9876543210") / Decimal("123456789")), + "80.00000072900000663390006037", + "Division of large integers resulting in max precision", + ) + + # 49. Division of zero by one (edge case) + testing.assert_equal( + String(Decimal("0") / Decimal("1")), "0", "Division of zero by one" + ) + + # 50. Division with value at maximum supported scale + var a50 = Decimal("0." + "0" * 27 + "5") / Decimal("1") + testing.assert_true( + a50.scale() <= Decimal.MAX_PRECISION, + "Case 50: Division with value at maximum supported scale failed", + ) + + print("✓ Edge case tests passed!") + + +fn test_large_numbers() raises: + print("------------------------------------------------------") + print("Testing division with large numbers...") + + # 51. Division of large number that results in small number + testing.assert_equal( + String(Decimal("1" + "0" * 20) / Decimal("1" + "0" * 20)), + "1", + "Division of large number that results in small number", + ) + + # 52. Division where dividend is at max capacity + var max_value = Decimal.MAX() + var a52 = max_value / Decimal("1") + testing.assert_equal( + a52, + max_value, + "Case 52: Division where dividend is at max capacity failed", + ) + + # 53. Division where dividend is slightly below max + var near_max = Decimal.MAX() - Decimal("1") + var a53 = near_max / Decimal("10") + testing.assert_equal(a53, Decimal("7922816251426433759354395033.4")) + + # 54. Division where result approaches max + var large_num = Decimal.MAX() / Decimal("2") + var a54 = large_num * Decimal("2") + testing.assert_true( + a54 <= Decimal.MAX(), + "Case 54: Division where result approaches max failed", + ) + + # 55. Large negative divided by large positive + var large_neg = -Decimal(String("1" + "0" * 15)) + var a55 = large_neg / Decimal("10000") + testing.assert_equal( + a55, + -Decimal(String("1" + "0" * 11)), + "Case 55: Large negative divided by large positive failed", + ) + + # 56. Large integer division with remainder + testing.assert_equal( + String(Decimal("12345678901234567890") / Decimal("9876543210")), + "1249999988.7343749990033203125", + "Large integer division with many digits", + ) + + # 57. Large numbers with exact division + testing.assert_equal( + String(Decimal("9" * 28) / Decimal("9" * 14)), + String(Decimal("100000000000001")), + "Large numbers with exact division", + ) + + # 58. Division of large numbers with same leading digits + var a58 = Decimal("123" + "0" * 25) / Decimal("123" + "0" * 15) + testing.assert_equal( + a58, + Decimal("1" + "0" * 10), + "Case 58: Division of large numbers with same leading digits failed", + ) + + # 59. Large numbers with different signs + var a59 = Decimal("9" * 28) / Decimal("-" + "9" * 14) + testing.assert_equal( + a59, + -Decimal("100000000000001"), + "Case 59: Large numbers with different signs failed", + ) + + # 60. Division near maximum representable value + try: + var a60 = Decimal.MAX() / Decimal("0.5") + testing.assert_true(a60 <= Decimal.MAX()) + except: + print("Division overflows") + + print("✓ Large number division tests passed!") + + +fn test_special_cases() raises: + print("Testing special division cases...") + + # 61. Identical numbers should give 1 + testing.assert_equal( + String(Decimal("123.456") / Decimal("123.456")), + "1", + "Identical numbers should give 1", + ) + + # 62. Division by 0.1 power for decimal shift + testing.assert_equal( + String(Decimal("1.234") / Decimal("0.001")), + "1234", + "Division by 0.1 power for decimal shift", + ) + + # 63. Division that normalizes out trailing zeros + testing.assert_equal( + Decimal("1.000") / Decimal("1.000"), + Decimal("1"), + "Case 63: Division that normalizes out trailing zeros failed", + ) + + # 64. Division by 1 should leave number unchanged + var special_value = Decimal("123.456789012345678901234567") + testing.assert_equal( + special_value / Decimal("1"), + special_value, + "Case 64: Division by 1 should leave number unchanged failed", + ) + + # 65. Division by self should be 1 for non-zero + testing.assert_equal( + Decimal("0.000123") / Decimal("0.000123"), + Decimal("1"), + "Case 65: Division by self should be 1 for non-zero failed", + ) + + # 66. Division of 1 by numbers close to 1 + testing.assert_equal( + Decimal("1") / Decimal("0.999999"), + Decimal("1.000001000001000001000001000"), + "Case 66: Division of 1 by numbers close to 1 failed", + ) + + # 67. Series of divisions that should cancel out + var value = Decimal("123.456") + var divided = value / Decimal("7") + var result = divided * Decimal("7") + testing.assert_true( + abs(value - result) / value < Decimal("0.0001"), + "Case 67: Series of divisions that should cancel out failed", + ) + + # 68. Division by fractional power of 10 + testing.assert_equal( + String(Decimal("5.5") / Decimal("0.055")), + "100", + "Division by fractional power of 10", + ) + + # 69. Division causing exact shift in magnitude + testing.assert_equal( + String(Decimal("1") / Decimal("1000")), + "0.001", + "Division causing exact shift in magnitude", + ) + + # 70. Dividing number very close to zero by one + var very_small = Decimal("0." + "0" * 27 + "1") + testing.assert_equal( + very_small / Decimal("1"), + very_small, + "Case 70: Dividing number very close to zero by one failed", + ) + + print("✓ Special case tests passed!") + + +fn test_mixed_precision() raises: + print("Testing mixed precision division cases...") + + # 71. High precision / low precision + testing.assert_equal( + String(Decimal("123.456789012345678901234567") / Decimal("2")), + "61.7283945061728394506172835", + "High precision / low precision", + ) + + # 72. Low precision / high precision + var a72 = Decimal("1234") / Decimal("0.0000000000000000000000011") + testing.assert_equal( + a72, + Decimal("1121818181818181818181818181.8"), + "Low precision / high precision", + ) + + # 73. Mixing high precision with power of 10 + var a73 = Decimal("0.123456789012345678901234567") / Decimal("0.1") + testing.assert_equal( + a73, + Decimal("1.23456789012345678901234567"), + "Case 73: Mixing high precision with power of 10 failed", + ) + + # 74. Precision of result higher than either operand + var a74 = Decimal("0.1") / Decimal("3") + testing.assert_true( + String(a74).startswith("0.0333333333333333"), + "Case 74: Precision of result higher than either operand failed", + ) + + # 75. Division where divisor has higher precision than dividend + var a75 = Decimal("1") / Decimal("0.0001234567890123456789") + testing.assert_true( + a75 > Decimal("8000"), + ( + "Case 75: Division where divisor has higher precision than dividend" + " failed" + ), + ) + + # 76. Division where precision shifts dramatically + var a76 = Decimal("0.000000001") / Decimal("0.000000000001") + testing.assert_equal( + a76, + Decimal("1000"), + "Case 76: Division where precision shifts dramatically failed", + ) + + # 77. Mixing different but high precision values + var a77 = Decimal("0.12345678901234567") / Decimal("0.98765432109876543") + testing.assert_true( + a77 < Decimal("0.13"), + "Case 77: Mixing different but high precision values failed", + ) + + # 78. Very different scales that result in exact division + testing.assert_equal( + String(Decimal("0.0000004") / Decimal("0.0002")), + "0.002", + "Very different scales that result in exact division", + ) + + # 79. Maximum precision divided by maximum precision + testing.assert_equal( + String(Decimal("0." + "9" * 28) / Decimal("0." + "3" * 28)), + "3", + "Maximum precision divided by maximum precision", + ) + + # 80. Many trailing zeros in result + testing.assert_equal( + String(Decimal("2.000") / Decimal("0.001")), + "2000", + "Many trailing zeros in result", + ) + + print("✓ Mixed precision tests passed!") + + +fn test_rounding_behavior() raises: + print("------------------------------------------------------") + print("Testing division rounding behavior...") + + # 81. Banker's rounding at boundary (round to even) + var a81 = Decimal("1") / Decimal( + String("3" + "0" * (Decimal.MAX_PRECISION - 1)) + ) + var expected = "0." + "0" * (Decimal.MAX_PRECISION - 1) + "3" + testing.assert_equal( + String(a81), expected, "Case 81: Banker's rounding at boundary failed" + ) + + # 82. Banker's rounding up at precision limit + var a82 = Decimal("5") / Decimal("9") # ~0.55555... + var b82 = Decimal("0." + "5" * 27 + "6") # 0.5555...6 + testing.assert_equal( + a82, b82, "Case 82: Banker's rounding up at precision limit failed" + ) + + # 83. Rounding that requires carry propagation + var a83 = Decimal("1") / Decimal("1.9999999999999999999999999") + var expected83 = Decimal("0.5000000000000000000000000250") + testing.assert_equal( + a83, + expected83, + "Case 83: Rounding that requires carry propagation failed", + ) + + # 84. Division that results in exactly half a unit in last place + var a84 = Decimal("1") / Decimal("4" + "0" * Decimal.MAX_PRECISION) + var expected84 = Decimal("0." + "0" * (Decimal.MAX_PRECISION)) + testing.assert_equal( + a84, + expected84, + ( + "Case 84: Division that results in exactly half a unit in last" + " place failed" + ), + ) + + # 85. Rounding stress test: 1/7 at different precisions + var a85 = Decimal("1") / Decimal("7") + var expected85 = Decimal( + "0.1428571428571428571428571429" + ) # 28 decimal places + testing.assert_equal( + a85, + expected85, + "Case 85: Rounding stress test: 1/7 at different precisions failed", + ) + + # 86. Division at the edge + testing.assert_equal( + String(Decimal("9.999999999999999999999999999") / Decimal("10")), + "0.9999999999999999999999999999", + "Division at the edge", + ) + + # 87. Division requiring rounding to even at last digit + testing.assert_equal( + String(Decimal("1.25") / Decimal("0.5")), + "2.5", + "Division requiring rounding to even at last digit", + ) + + # 88. Half-even rounding with even digit before + testing.assert_equal( + String(Decimal("24.5") / Decimal("10")), + "2.45", + "Testing half-even rounding with even digit before", + ) + + # 89. Half-even rounding with odd digit before + testing.assert_equal( + String(Decimal("25.5") / Decimal("10")), + "2.55", + "Testing half-even rounding with odd digit before", + ) + + # 90. Division with MAX_PRECISION-3 digits + # 1 / 300000000000000000000000000 (26 zeros) + var a90 = Decimal("1") / Decimal(String("300000000000000000000000000")) + testing.assert_equal( + String(a90), + "0.0000000000000000000000000033", + "Case 90: Division with exactly MAX_PRECISION digits failed", + ) + + print("✓ Rounding behavior tests passed!") + + +fn test_error_cases() raises: + print("------------------------------------------------------") + print("Testing division error cases...") + + # 91. Division by zero + try: + var result = Decimal("123") / Decimal("0") + testing.assert_true( + False, "Case 91: Expected division by zero to raise exception" + ) + except: + testing.assert_true( + True, "Case 91: Division by zero correctly raised exception" + ) + + # 92. Division with overflow potential + # This is intended to test if the implementation can avoid overflow + # by handling the operation algebraically before doing actual division + var large1 = Decimal.MAX() + var large2 = Decimal("0.5") + try: + var result92 = large1 / large2 + testing.assert_true(result92 > large1) + except: + print("Overflow detected (acceptable)") + + # 93. Division of maximum possible value + try: + var result93 = Decimal.MAX() / Decimal("0.1") + testing.assert_true(result93 > Decimal.MAX()) + except: + print("Overflow detected (acceptable)") + + # 94. Division of minimum possible value + var result94 = Decimal.MIN() / Decimal("10.12345") + testing.assert_equal( + result94, + Decimal("-7826201790324873199704048554.1"), + "Case 94: Division of minimum possible value failed", + ) + + # 95. Division of very small by very large (approaching underflow) + var result95 = Decimal("0." + "0" * 27 + "1") / Decimal.MAX() + testing.assert_equal( + String(result95), + "0.0000000000000000000000000000", + "Case 95: Division of very small by very large failed", + ) + + # 96. Division of maximum by minimum value + testing.assert_equal( + String(Decimal.MAX() / Decimal.MIN()), + "-1", + "Division of maximum by minimum value", + ) + + # 97. Division with potential for intermediate overflow + testing.assert_equal( + String(Decimal("1" + "0" * 20) / Decimal("1" + "0" * 20)), + "1", + "Division with potential for intermediate overflow", + ) + + # 98. Division resulting in value greater than representable max + try: + # This may either return MAX or raise an error depending on implementation + var result = Decimal.MAX() / Decimal("0.00001") + testing.assert_true(result >= Decimal.MAX()) + except: + testing.assert_true(True, "Overflow detected (acceptable)") + + # 99. Multiple operations that could cause cumulative error + var calc = (Decimal("1") / Decimal("3")) * Decimal("3") + testing.assert_equal( + String(calc), + "0.9999999999999999999999999999", + "Case 99: Multiple operations that could cause cumulative error failed", + ) + + # 100. Division at the exact boundary of precision limit + # 1 / 70000000000000000000000000000 (28 zeros) + var a100 = Decimal("1") / Decimal(String("7" + "0" * Decimal.MAX_PRECISION)) + testing.assert_equal( + String(a100), + "0.0000000000000000000000000000", + "Case 100: Division at the exact boundary of precision limit failed", + ) + + print("✓ Error case tests passed!") + + +fn main() raises: + print("\n=== Running Comprehensive Decimal Division Tests ===\n") + + # Run all test groups + test_basic_division() + print() + + test_repeating_decimals() + print() + + test_precision_rounding() + print() + + test_scale_handling() + print() + + test_edge_cases() + print() + + test_large_numbers() + print() + + test_special_cases() + print() + + test_mixed_precision() + print() + + test_rounding_behavior() + print() + + test_error_cases() + print() + + print("✓✓✓ All 100 division tests passed! ✓✓✓") diff --git a/tests/test_logic.mojo b/tests/test_logic.mojo index 47f9454..5712fd3 100644 --- a/tests/test_logic.mojo +++ b/tests/test_logic.mojo @@ -15,6 +15,7 @@ import testing fn test_equality() raises: + print("------------------------------------------------------") print("Testing decimal equality...") # Test case 1: Equal decimals @@ -60,6 +61,7 @@ fn test_equality() raises: fn test_inequality() raises: + print("------------------------------------------------------") print("Testing decimal inequality...") # Test case 1: Equal decimals diff --git a/tests/test_mathematics.mojo b/tests/test_sqrt.mojo similarity index 70% rename from tests/test_mathematics.mojo rename to tests/test_sqrt.mojo index 791f7ce..672a037 100644 --- a/tests/test_mathematics.mojo +++ b/tests/test_sqrt.mojo @@ -9,133 +9,209 @@ import testing fn test_perfect_squares() raises: print("Testing square root of perfect squares...") - # Test case 1 + # Test case 1: sqrt(1) = 1 try: var d1 = Decimal("1") - var expected1 = "1" + print(" Testing sqrt(1)...") var result1 = sqrt(d1) + print(" Got result: " + String(result1)) testing.assert_equal( String(result1), - expected1, - "sqrt(" + String(d1) + ") should be " + expected1, + "1", + "Case 1: sqrt(1) should be 1, got " + String(result1), ) + print(" Test case 1 passed.") except e: print("ERROR in test case 1: sqrt(1) = 1") + print("Exception: " + String(e)) raise e - # Test case 2 + # Test case 2: sqrt(4) = 2 try: var d2 = Decimal("4") - var expected2 = "2" + print(" Testing sqrt(4)...") var result2 = sqrt(d2) + print(" Got result: " + String(result2)) testing.assert_equal( String(result2), - expected2, - "sqrt(" + String(d2) + ") should be " + expected2, + "2", + "Case 2: sqrt(4) should be 2, got " + String(result2), ) + print(" Test case 2 passed.") except e: print("ERROR in test case 2: sqrt(4) = 2") + print("Exception: " + String(e)) raise e - # Test case 3 - var d3 = Decimal("9") - var expected3 = "3" - var result3 = sqrt(d3) - testing.assert_equal( - String(result3), - expected3, - "sqrt(" + String(d3) + ") should be " + expected3, - ) + # Test case 3: sqrt(9) = 3 + try: + var d3 = Decimal("9") + print(" Testing sqrt(9)...") + var result3 = sqrt(d3) + print(" Got result: " + String(result3)) + testing.assert_equal( + String(result3), + "3", + "Case 3: sqrt(9) should be 3, got " + String(result3), + ) + print(" Test case 3 passed.") + except e: + print("ERROR in test case 3: sqrt(9) = 3") + print("Exception: " + String(e)) + raise e - # Test case 4 - var d4 = Decimal("16") - var expected4 = "4" - var result4 = sqrt(d4) - testing.assert_equal( - String(result4), - expected4, - "sqrt(" + String(d4) + ") should be " + expected4, - ) + # Test case 4: sqrt(16) = 4 + try: + var d4 = Decimal("16") + print(" Testing sqrt(16)...") + var result4 = sqrt(d4) + print(" Got result: " + String(result4)) + testing.assert_equal( + String(result4), + "4", + "Case 4: sqrt(16) should be 4, got " + String(result4), + ) + print(" Test case 4 passed.") + except e: + print("ERROR in test case 4: sqrt(16) = 4") + print("Exception: " + String(e)) + raise e - # Test case 5 - var d5 = Decimal("25") - var expected5 = "5" - var result5 = sqrt(d5) - testing.assert_equal( - String(result5), - expected5, - "sqrt(" + String(d5) + ") should be " + expected5, - ) + # Test case 5: sqrt(25) = 5 + try: + var d5 = Decimal("25") + print(" Testing sqrt(25)...") + var result5 = sqrt(d5) + print(" Got result: " + String(result5)) + testing.assert_equal( + String(result5), + "5", + "Case 5: sqrt(25) should be 5, got " + String(result5), + ) + print(" Test case 5 passed.") + except e: + print("ERROR in test case 5: sqrt(25) = 5") + print("Exception: " + String(e)) + raise e - # Test case 6 - var d6 = Decimal("36") - var expected6 = "6" - var result6 = sqrt(d6) - testing.assert_equal( - String(result6), - expected6, - "sqrt(" + String(d6) + ") should be " + expected6, - ) + # Test case 6: sqrt(36) = 6 + try: + var d6 = Decimal("36") + print(" Testing sqrt(36)...") + var result6 = sqrt(d6) + print(" Got result: " + String(result6)) + testing.assert_equal( + String(result6), + "6", + "Case 6: sqrt(36) should be 6, got " + String(result6), + ) + print(" Test case 6 passed.") + except e: + print("ERROR in test case 6: sqrt(36) = 6") + print("Exception: " + String(e)) + raise e - # Test case 7 - var d7 = Decimal("49") - var expected7 = "7" - var result7 = sqrt(d7) - testing.assert_equal( - String(result7), - expected7, - "sqrt(" + String(d7) + ") should be " + expected7, - ) + # Test case 7: sqrt(49) = 7 + try: + var d7 = Decimal("49") + print(" Testing sqrt(49)...") + var result7 = sqrt(d7) + print(" Got result: " + String(result7)) + testing.assert_equal( + String(result7), + "7", + "Case 7: sqrt(49) should be 7, got " + String(result7), + ) + print(" Test case 7 passed.") + except e: + print("ERROR in test case 7: sqrt(49) = 7") + print("Exception: " + String(e)) + raise e - # Test case 8 - var d8 = Decimal("64") - var expected8 = "8" - var result8 = sqrt(d8) - testing.assert_equal( - String(result8), - expected8, - "sqrt(" + String(d8) + ") should be " + expected8, - ) + # Test case 8: sqrt(64) = 8 + try: + var d8 = Decimal("64") + print(" Testing sqrt(64)...") + var result8 = sqrt(d8) + print(" Got result: " + String(result8)) + testing.assert_equal( + String(result8), + "8", + "Case 8: sqrt(64) should be 8, got " + String(result8), + ) + print(" Test case 8 passed.") + except e: + print("ERROR in test case 8: sqrt(64) = 8") + print("Exception: " + String(e)) + raise e - # Test case 9 - var d9 = Decimal("81") - var expected9 = "9" - var result9 = sqrt(d9) - testing.assert_equal( - String(result9), - expected9, - "sqrt(" + String(d9) + ") should be " + expected9, - ) + # Test case 9: sqrt(81) = 9 + try: + var d9 = Decimal("81") + print(" Testing sqrt(81)...") + var result9 = sqrt(d9) + print(" Got result: " + String(result9)) + testing.assert_equal( + String(result9), + "9", + "Case 9: sqrt(81) should be 9, got " + String(result9), + ) + print(" Test case 9 passed.") + except e: + print("ERROR in test case 9: sqrt(81) = 9") + print("Exception: " + String(e)) + raise e - # Test case 10 - var d10 = Decimal("100") - var expected10 = "10" - var result10 = sqrt(d10) - testing.assert_equal( - String(result10), - expected10, - "sqrt(" + String(d10) + ") should be " + expected10, - ) + # Test case 10: sqrt(100) = 10 + try: + var d10 = Decimal("100") + print(" Testing sqrt(100)...") + var result10 = sqrt(d10) + print(" Got result: " + String(result10)) + testing.assert_equal( + String(result10), + "10", + "Case 10: sqrt(100) should be 10, got " + String(result10), + ) + print(" Test case 10 passed.") + except e: + print("ERROR in test case 10: sqrt(100) = 10") + print("Exception: " + String(e)) + raise e - # Test case 11 - var d11 = Decimal("10000") - var expected11 = "100" - var result11 = sqrt(d11) - testing.assert_equal( - String(result11), - expected11, - "sqrt(" + String(d11) + ") should be " + expected11, - ) + # Test case 11: sqrt(10000) = 100 + try: + var d11 = Decimal("10000") + print(" Testing sqrt(10000)...") + var result11 = sqrt(d11) + print(" Got result: " + String(result11)) + testing.assert_equal( + String(result11), + "100", + "Case 11: sqrt(10000) should be 100, got " + String(result11), + ) + print(" Test case 11 passed.") + except e: + print("ERROR in test case 11: sqrt(10000) = 100") + print("Exception: " + String(e)) + raise e - # Test case 12 - var d12 = Decimal("1000000") - var expected12 = "1000" - var result12 = sqrt(d12) - testing.assert_equal( - String(result12), - expected12, - "sqrt(" + String(d12) + ") should be " + expected12, - ) + # Test case 12: sqrt(1000000) = 1000 + try: + var d12 = Decimal("1000000") + print(" Testing sqrt(1000000)...") + var result12 = sqrt(d12) + print(" Got result: " + String(result12)) + testing.assert_equal( + String(result12), + "1000", + "Case 12: sqrt(1000000) should be 1000, got " + String(result12), + ) + print(" Test case 12 passed.") + except e: + print("ERROR in test case 12: sqrt(1000000) = 1000") + print("Exception: " + String(e)) + raise e print("Perfect square tests passed!") @@ -146,7 +222,7 @@ fn test_non_perfect_squares() raises: # Test case 1 try: var d1 = Decimal("2") - var expected_prefix1 = "1.414" + var expected_prefix1 = "1.414213562373095048801688724" var result1 = sqrt(d1) var result_str1 = String(result1) testing.assert_true( @@ -164,7 +240,7 @@ fn test_non_perfect_squares() raises: # Test case 2 var d2 = Decimal("3") - var expected_prefix2 = "1.732" + var expected_prefix2 = "1.73205080756887729352744634" var result2 = sqrt(d2) var result_str2 = String(result2) testing.assert_true( @@ -179,7 +255,7 @@ fn test_non_perfect_squares() raises: # Test case 3 var d3 = Decimal("5") - var expected_prefix3 = "2.236" + var expected_prefix3 = "2.23606797749978969640917366" var result3 = sqrt(d3) var result_str3 = String(result3) testing.assert_true( @@ -194,7 +270,7 @@ fn test_non_perfect_squares() raises: # Test case 4 var d4 = Decimal("10") - var expected_prefix4 = "3.162" + var expected_prefix4 = "3.162277660168379331998893544" var result4 = sqrt(d4) var result_str4 = String(result4) testing.assert_true( @@ -209,7 +285,7 @@ fn test_non_perfect_squares() raises: # Test case 5 var d5 = Decimal("50") - var expected_prefix5 = "7.071" + var expected_prefix5 = "7.071067811865475244008443621" var result5 = sqrt(d5) var result_str5 = String(result5) testing.assert_true( @@ -224,7 +300,7 @@ fn test_non_perfect_squares() raises: # Test case 6 var d6 = Decimal("99") - var expected_prefix6 = "9.949" + var expected_prefix6 = "9.949874371066199547344798210" var result6 = sqrt(d6) var result_str6 = String(result6) testing.assert_true( @@ -239,7 +315,7 @@ fn test_non_perfect_squares() raises: # Test case 7 var d7 = Decimal("999") - var expected_prefix7 = "31.60" + var expected_prefix7 = "31.6069612585582165452042139" var result7 = sqrt(d7) var result_str7 = String(result7) testing.assert_true( @@ -362,8 +438,9 @@ fn test_edge_cases() raises: "0." + "0" * 27 + "1" ) # Smallest possible positive decimal var result_small = sqrt(very_small) - testing.assert_true( - String(result_small).startswith("0.00000000000001"), + testing.assert_equal( + String(result_small), + "0.00000000000001", String( "sqrt of very small number should be positive and smaller," " very_small={}, result_small={}" @@ -378,17 +455,17 @@ fn test_edge_cases() raises: var very_large = Decimal("1" + "0" * 27) # Large decimal var result_large = sqrt(very_large) testing.assert_true( - String(result_large).startswith("3162277"), - "sqrt of 10^27 should start with 3162277...", + String(result_large).startswith("31622776601683.79331998893544"), + "sqrt of 10^27 should start with 31622776601683.79331998893544...", ) except e: print("ERROR in test_edge_cases: sqrt of very large number (10^27)") raise e # Test negative number exception - var negative = Decimal("-1") var negative_exception_caught = False try: + var negative = Decimal("-1") var _result_negative = sqrt(negative) testing.assert_equal( True, False, "sqrt() of negative should raise exception" @@ -414,48 +491,37 @@ fn test_edge_cases() raises: fn test_precision() raises: print("Testing precision of square root calculations...") - var expected_sqrt2 = "1.4142135623" # First 10 decimal places of sqrt(2) + var expected_sqrt2 = "1.414213562373095048801688724" # First 10 decimal places of sqrt(2) # Test precision for irrational numbers - try: - var two = Decimal("2") - var result = sqrt(two) + var two = Decimal("2") + var result = sqrt(two) - # Check at least 10 decimal places (should be enough for most applications) - testing.assert_true( - String(result).startswith(expected_sqrt2), - "sqrt(2) should start with " + expected_sqrt2, - ) - except e: - print("ERROR in test_precision: sqrt(2) precision check") - raise e + # Check at least 10 decimal places (should be enough for most applications) + testing.assert_true( + String(result).startswith(expected_sqrt2), + "sqrt(2) should start with " + expected_sqrt2, + ) # Test high precision values - try: - var precise_value = Decimal("2.0000000000000000000000000") - var precise_result = sqrt(precise_value) - testing.assert_true( - String(precise_result).startswith(expected_sqrt2), - "sqrt of high precision 2 should start with " + expected_sqrt2, - ) - except e: - print("ERROR in test_precision: high precision sqrt(2)") - raise e + var precise_value = Decimal("2.0000000000000000000000000") + var precise_result = sqrt(precise_value) + testing.assert_true( + String(precise_result).startswith(expected_sqrt2), + "sqrt of high precision 2 should start with " + expected_sqrt2, + ) # Check that results are appropriately rounded - try: - var d = Decimal("10") - var sqrt_d = sqrt(d) - var expected_places = 7 # Typical minimum precision - testing.assert_true( - sqrt_d.scale() >= expected_places, - "sqrt(10) should have at least " - + String(expected_places) - + " decimal places", - ) - except e: - print("ERROR in test_precision: sqrt(10) scale check") - raise e + var d = Decimal("1894128.128951235") + var sqrt_d = sqrt(d) + testing.assert_true( + String(sqrt_d).startswith("1376.27327553478091940498131"), + ( + "sqrt(1894128.128951235) should startwith" + " 1376.273275534780919404981314 but got " + + String(sqrt_d) + ), + ) print("Precision tests passed!") @@ -465,24 +531,20 @@ fn test_mathematical_identities() raises: # Test that sqrt(x)² = x - Expanded for each test number # Test number 1 - try: - var num1 = Decimal("2") - var sqrt_num1 = sqrt(num1) - var squared1 = sqrt_num1 * sqrt_num1 - var original_rounded1 = round(num1, 10) - var squared_rounded1 = round(squared1, 10) - testing.assert_true( - original_rounded1 == squared_rounded1, - "sqrt(" - + String(num1) - + ")² should approximately equal " - + String(num1) - + ", but got " - + String(squared_rounded1), - ) - except e: - print("ERROR in test_mathematical_identities: sqrt(2)² = 2") - raise e + var num1 = Decimal("2") + var sqrt_num1 = sqrt(num1) + var squared1 = sqrt_num1 * sqrt_num1 + var original_rounded1 = round(num1, 10) + var squared_rounded1 = round(squared1, 10) + testing.assert_true( + original_rounded1 == squared_rounded1, + "sqrt(" + + String(num1) + + ")² should approximately equal " + + String(num1) + + ", but got " + + String(squared_rounded1), + ) # Test number 2 var num2 = Decimal("3") @@ -828,15 +890,19 @@ fn run_test_with_error_handling( """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("✓ " + test_name + " passed\n") + print("\n✓ " + test_name + " passed\n") except e: - print("✗ " + test_name + " FAILED!") + print("\n✗ " + test_name + " FAILED!") print("Error message: " + String(e)) raise e fn main() raises: + print("=========================================") print("Running comprehensive Decimal square root tests") run_test_with_error_handling(test_perfect_squares, "Perfect squares test")