diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 09bddad..fff5c75 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -55,10 +55,9 @@ jobs: cp decimojo.mojopkg tests/ cp decimojo.mojopkg benches/ - - name: Run tests and benches + - name: Run tests run: | magic run mojo test tests - magic run bench - name: Install pre-commit run: | diff --git a/README.md b/README.md index dbf6543..05c661b 100644 --- a/README.md +++ b/README.md @@ -1,218 +1,118 @@ # DeciMojo -A fixed-point decimal arithmetic library implemented in [the Mojo programming language 🔥](https://www.modular.com/mojo). +A fixed-point decimal mathematics library for [the Mojo programming language 🔥](https://www.modular.com/mojo). -**[中文·漢字»](./docs/readme_zht.md)** +**[中文·漢字»](./docs/readme_zht.md)** | **[Repository on GitHub»](https://github.com/forfudan/decimojo)** ## Overview -DeciMojo provides a Decimal type implementation for Mojo with fixed-precision arithmetic, designed to handle financial calculations and other scenarios where floating-point rounding errors are problematic. +DeciMojo offers a complete fixed-precision decimal mathematics implementation for Mojo, providing exact calculations for financial modeling, scientific computing, and any application where floating-point approximation errors are unacceptable. Beyond basic arithmetic, DeciMojo delivers advanced mathematical functions with guaranteed precision. -## Installation - -DeciMojo can be directly added to your project environment by typing `magic add decimojo` in the Modular CLI. This command fetches the latest version of DeciMojo and makes it available for import in your Mojo project. - -To use DeciMojo, import the necessary components from the `decimojo.prelude` module. This module provides convenient access to the most commonly used classes and functions, including `dm` (an alias for the `decimojo` module itself), `Decimal` and `RoundingMode`. - -```mojo -from decimojo.prelude import dm, Decimal, RoundingMode - -fn main() raises: - var r = Decimal("3") # radius - var pi = Decimal("3.1415926") # pi - var area = pi * r * r # area of a circle - print(area) # 28.2743334 -``` - -The Github repo of the project is at [https://github.com/forfudan/decimojo](https://github.com/forfudan/decimojo). - -## Examples - -The `Decimal` type can represent values with up to 29 significant digits and a maximum of 28 digits after the decimal point. When a value exceeds the maximum representable value (`2^96 - 1`), DeciMojo either raises an error or rounds the value to fit within these constraints. For example, the significant digits of `8.8888888888888888888888888888` (29 eights total with 28 after the decimal point) exceeds the maximum representable value (`2^96 - 1`) and is automatically rounded to `8.888888888888888888888888889` (28 eights total with 27 after the decimal point). - -Here are 8 key examples highlighting the most important features of the `Decimal` type in its current state: - -### 1. Fixed-Point Precision for Financial Calculations +### Current Implementation -```mojo -from decimojo import dm, Decimal - -# The classic floating-point problem -print(0.1 + 0.2) # 0.30000000000000004 (not exactly 0.3) - -# Decimal solves this with exact representation -var d1 = Decimal("0.1") -var d2 = Decimal("0.2") -var sum = d1 + d2 -print(sum) # Exactly 0.3 - -# Financial calculation example - computing tax -var price = Decimal("19.99") -var tax_rate = Decimal("0.0725") -var tax = price * tax_rate # Exactly 1.449275 -var total = price + tax # Exactly 21.439275 -``` +- **Decimal**: A 128-bit fixed-point decimal type supporting up to 29 significant digits with a maximum of 28 decimal places[^fixed_precision], featuring comprehensive mathematical functions including logarithms, exponentiation, roots, and more. -### 2. Basic Arithmetic with Proper Banker's Rounding +### Future Roadmap -```mojo -# Addition with different scales -var a = Decimal("123.45") -var b = Decimal("67.8") -print(a + b) # 191.25 (preserves highest precision) - -# Subtraction with negative result -var c = Decimal("50") -var d = Decimal("75.25") -print(c - d) # -25.25 - -# Multiplication with banker's rounding (round to even) -var e = Decimal("12.345") -var f = Decimal("5.67") -print(round(e * f, 2)) # 69.96 (rounds to nearest even) - -# Division with banker's rounding -var g = Decimal("10") -var h = Decimal("3") -print(round(g / h, 2)) # 3.33 (rounded banker's style) -``` +- **BigInt**: Arbitrary-precision integer type with unlimited digits. +- **BigDecimal**: Arbitrary-precision decimal type with configurable precision[^arbitrary_precision]. +- **BigComplex**: Arbitrary-precision complex number type built on BigDecimal. -### 3. Scale and Precision Management +## Installation -```mojo -# Scale refers to number of decimal places -var d1 = Decimal("123.45") -print(d1.scale()) # 2 +DeciMojo is available in the [modular-community](https://repo.prefix.dev/modular-community) package repository. You can install it using any of these methods: -# Precision control with explicit rounding -var d2 = Decimal("123.456") -print(d2.round_to_scale(1)) # 123.5 (banker's rounding) +### `magic` CLI -# High precision is preserved (up to 28 decimal places) -var precise = Decimal("0.1234567890123456789012345678") -print(precise) # 0.1234567890123456789012345678 -``` +From the magic CLI, simply run: -### 4. Sign Handling and Absolute Value - -```mojo -# Negation operator -var pos = Decimal("123.45") -var neg = -pos -print(neg) # -123.45 - -# Absolute value -var abs_val = abs(Decimal("-987.65")) -print(abs_val) # 987.65 - -# Sign checking -print(Decimal("-123.45").is_negative()) # True -print(Decimal("0").is_negative()) # False - -# Sign preservation in multiplication -print(Decimal("-5") * Decimal("3")) # -15 -print(Decimal("-5") * Decimal("-3")) # 15 +```console +magic add decimojo ``` -### 5. Advanced Mathematical Operations - -```mojo -# Highly accurate square root implementation -var root2 = Decimal("2").sqrt() -print(root2) # 1.4142135623730950488016887242 +This fetches the latest version and makes it immediately available for import. -# Square root of imperfect squares -var root_15_9999 = Decimal("15.9999").sqrt() -print(root_15_9999) # 3.9999874999804686889646053305 +### `toml` file -# Integer powers with fast binary exponentiation -var cubed = Decimal("3") ** 3 -print(cubed) # 27 +For projects with a mojoproject.toml file, add the dependency: -# Negative powers (reciprocals) -var recip = Decimal("2") ** (-1) -print(recip) # 0.5 +```toml +[dependencies] +decimojo = ">=0.1.0" ``` -### 6. Robust Edge Case Handling +Then run `magic install` to download and install the package. -```mojo -# Division by zero is properly caught -try: - var result = Decimal("10") / Decimal("0") -except: - print("Division by zero properly detected") - -# Zero raised to negative power -try: - var invalid = Decimal("0") ** (-1) -except: - print("Zero to negative power properly detected") - -# Overflow detection and prevention -var max_val = Decimal.MAX() -try: - var overflow = max_val * Decimal("2") -except: - print("Overflow correctly detected") -``` +### Local package -### 7. Equality and Comparison Operations +For the latest development version, clone the [GitHub repository](https://github.com/forfudan/decimojo) and build the package locally. -```mojo -# Equal values with different scales -var a = Decimal("123.4500") -var b = Decimal("123.45") -print(a == b) # True (numeric value equality) - -# Comparison operators -var c = Decimal("100") -var d = Decimal("200") -print(c < d) # True -print(c <= d) # True -print(c > d) # False -print(c >= d) # False -print(c != d) # True -``` +## Quick start -### 8. Real World Financial Examples +Here is a comprehensive quick-start guide showcasing each major function of the `Decimal` type. ```mojo -# Monthly loan payment calculation with precise interest -var principal = Decimal("200000") # $200,000 loan -var annual_rate = Decimal("0.05") # 5% interest rate -var monthly_rate = annual_rate / Decimal("12") -var num_payments = Decimal("360") # 30 years - -# Monthly payment formula: P * r(1+r)^n/((1+r)^n-1) -var numerator = monthly_rate * (Decimal("1") + monthly_rate) ** 360 -var denominator = (Decimal("1") + monthly_rate) ** 360 - Decimal("1") -var payment = principal * (numerator / denominator) -print("Monthly payment: $" + String(round(payment, 2))) # $1,073.64 -``` - -## Advantages - -DeciMojo provides exceptional computational precision without sacrificing performance. It maintains accuracy throughout complex calculations where floating-point or other decimal implementations might introduce subtle errors. - -Consider the square root of `15.9999`. When comparing DeciMojo's implementation with Python's decimal module (both rounded to 16 decimal places): +from decimojo import Decimal, RoundingMode -- DeciMojo calculates: `3.9999874999804687` -- Python's decimal returns: `3.9999874999804685` - -The mathematically correct value (to 50+ digits) is: -`3.9999874999804686889646053303778122644631365491812...` - -When rounded to 16 decimal places, the correct result is `3.9999874999804687`, confirming that DeciMojo produces the more accurate result in this case. - -```log -Function: sqrt() -Decimal value: 15.9999 -DeciMojo result: 3.9999874999804686889646053305 -Python's decimal result: 3.9999874999804685 +fn main() raises: + # === Construction === + var a = Decimal("123.45") # From string + var b = Decimal(123) # From integer + var c = Decimal(123, 2) # Integer with scale (1.23) + var d = Decimal.from_float(3.14159) # From floating-point + + # === Basic Arithmetic === + print(a + b) # Addition: 246.45 + print(a - b) # Subtraction: 0.45 + print(a * b) # Multiplication: 15184.35 + print(a / b) # Division: 1.00365853658536585365853658537 + + # === Rounding & Precision === + print(a.round(1)) # Round to 1 decimal place: 123.5 + print(a.quantize(Decimal("0.01"))) # Format to 2 decimal places: 123.45 + print(a.round(0, RoundingMode.ROUND_DOWN)) # Round down to integer: 123 + + # === Comparison === + print(a > b) # Greater than: True + print(a == Decimal("123.45")) # Equality: True + print(a.is_zero()) # Check for zero: False + print(Decimal("0").is_zero()) # Check for zero: True + + # === Type Conversions === + print(Float64(a)) # To float: 123.45 + print(a.to_int()) # To integer: 123 + print(a.to_str()) # To string: "123.45" + print(a.coefficient()) # Get coefficient: 12345 + print(a.scale()) # Get scale: 2 + + # === Mathematical Functions === + print(Decimal("2").sqrt()) # Square root: 1.4142135623730950488016887242 + print(Decimal("100").root(3)) # Cube root: 4.6415888336127788924100763509 + print(Decimal("2.71828").ln()) # Natural log: 0.9999999999999999999999999995 + print(Decimal("10").log10()) # Base-10 log: 1 + print(Decimal("16").log(Decimal("2"))) # Log base 2: 4 + print(Decimal("10").exp()) # e^10: 22026.4657948067165463556 + print(Decimal("2").power(10)) # Power: 1024 + + # === Sign Handling === + print(-a) # Negation: -123.45 + print(abs(Decimal("-123.45"))) # Absolute value: 123.45 + print(Decimal("123.45").is_negative()) # Check if negative: False + + # === Special Values === + print(Decimal.PI()) # π constant: 3.1415926535897932384626433833 + print(Decimal.E()) # e constant: 2.7182818284590452353602874714 + print(Decimal.ONE()) # Value 1: 1 + print(Decimal.ZERO()) # Value 0: 0 + print(Decimal.MAX()) # Maximum value: 79228162514264337593543950335 + + # === Convenience Methods === + print(Decimal("123.400").is_integer()) # Check if integer: True + print(a.number_of_significant_digits()) # Count significant digits: 5 + print(Decimal("12.34").to_str_scientific()) # Scientific notation: 1.234E+1 ``` -This precision advantage becomes increasingly important in financial, scientific, and engineering calculations where small rounding errors can compound into significant discrepancies. +[Click here for 8 key examples](./docs/examples.md) highlighting the most important features of the `Decimal` type. ## Objective @@ -230,36 +130,53 @@ For brevity, you can refer to it as "deci" (derived from the Latin root "decimus ## Status -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). +Rome wasn't built in a day. DeciMojo is currently under active development. For the 128-bit `Decimal` type, it has successfully progressed through the **"make it work"** phase and is now well into the **"make it right"** phase with many optimizations already in place. Bug reports and feature requests are welcome! If you encounter issues, please [file them here](https://github.com/forfudan/decimojo/issues). + +### Make it Work ✅ (COMPLETED) -### Make it Work ✅ (MOSTLY COMPLETED) +- Core decimal implementation with a robust 128-bit representation (96-bit coefficient + 32-bit flags) +- Comprehensive arithmetic operations (+, -, *, /, %, **) with proper overflow handling +- Type conversions to/from various formats (String, Int, Float64, etc.) +- Proper representation of special values (NaN, Infinity) +- Full suite of comparison operators with correct decimal semantics -- Core decimal implementation exists and functions -- Basic arithmetic operations (+, -, *, /) are implemented -- Type conversions to/from various formats work -- String representation and parsing are functional -- Construction from different sources (strings, numbers) is supported +### Make it Right 🔄 (MOSTLY COMPLETED) -### Make it Right 🔄 (IN PROGRESS) +- Reorganized codebase with modular structure (decimal, arithmetics, comparison, exponential) +- Edge case handling for all operations (division by zero, zero to negative power) +- Scale and precision management with sophisticated rounding strategies +- Financial calculations with banker's rounding (ROUND_HALF_EVEN) +- High-precision advanced mathematical functions (sqrt, root, ln, exp, log10, power) +- Proper implementation of traits (Absable, Comparable, Floatable, Roundable, etc.) -- Edge case handling is being addressed (division by zero, zero to negative power) -- Scale and precision management shows sophistication -- Financial calculations demonstrate proper rounding -- High precision support is implemented (up to 28 decimal places) -- The examples show robust handling of various scenarios +### Make it Fast ⚡ (SIGNIFICANT PROGRESS) -### Make it Fast ⏳ (IN PROGRESS & FUTURE WORK) +DeciMojo delivers exceptional performance compared to Python's `decimal` module while maintaining precise calculations. This performance difference stems from fundamental design choices: -- Core arithmetic operations (+, -, *, /) have been optimized for performance, with comprehensive benchmarking reports available comparing performance against Python's built-in decimal module ([PR#16](https://github.com/forfudan/decimojo/pull/16), [PR#20](https://github.com/forfudan/decimojo/pull/20), [PR#21](https://github.com/forfudan/decimojo/pull/21)). -- Regular benchmarking against Python's `decimal` module (see `bench/` folder) -- Performance optimization for other functions is progressing gradually but is not currently a priority +- **DeciMojo**: Uses a fixed 128-bit representation (96-bit coefficient + 32-bit flags) with a maximum of 28 decimal places, optimized for modern hardware and Mojo's performance capabilities +- **Python decimal**: Implements arbitrary precision that can represent numbers with unlimited significant digits but requires dynamic memory allocation and more complex algorithms + +This architectural difference explains our benchmarking results: + +- Core arithmetic operations (+, -, *, /) achieve 100x-3500x speedup over Python's decimal module +- Special case handling (powers of 0, 1, etc.) shows up to 3500x performance improvement +- Advanced mathematical functions (sqrt, ln, exp) demonstrate 5x-600x better performance +- Only specific edge cases (like computing 10^(1/100)) occasionally perform better in Python due to its arbitrary precision algorithms + +Regular benchmarks against Python's `decimal` module are available in the `bench/` folder, documenting both the performance advantages and the few specific operations where different approaches are needed. + +### Future Extensions 🚀 (PLANNED) + +- **BigInt**: Arbitrary-precision integer type with unlimited digits +- **BigDecimal**: Arbitrary-precision decimal type with configurable precision +- **BigComplex**: Arbitrary-precision complex number type built on BigDecimal ## Tests and benches 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 against `python.decimal` module. The log files are saved in `benches/logs/`. +- Use `magic run test` to run tests. +- Use `magic run bench_decimal` to generate logs for benchmarking tests against `python.decimal` module. The log files are saved in `benches/decimal/logs/`. ## Citation @@ -279,3 +196,6 @@ If you find DeciMojo useful for your research, consider listing it in your citat ## License This repository and its contributions are licensed under the Apache License v2.0. + +[^fixed_precision]: The `Decimal` type can represent values with up to 29 significant digits and a maximum of 28 digits after the decimal point. When a value exceeds the maximum representable value (`2^96 - 1`), DeciMojo either raises an error or rounds the value to fit within these constraints. For example, the significant digits of `8.8888888888888888888888888888` (29 eights total with 28 after the decimal point) exceeds the maximum representable value (`2^96 - 1`) and is automatically rounded to `8.888888888888888888888888889` (28 eights total with 27 after the decimal point). DeciMojo's `Decimal` type is similar to `System.Decimal` (C#/.NET), `rust_decimal` in Rust, `DECIMAL/NUMERIC` in SQL Server, etc. +[^arbitrary_precision]: Similar to `decimal` and `mpmath` in Python, `java.math.BigDecimal` in Java, etc. diff --git a/benches/bench.mojo b/benches/decimal/bench.mojo similarity index 100% rename from benches/bench.mojo rename to benches/decimal/bench.mojo diff --git a/benches/bench_add.mojo b/benches/decimal/bench_add.mojo similarity index 100% rename from benches/bench_add.mojo rename to benches/decimal/bench_add.mojo diff --git a/benches/bench_comparison.mojo b/benches/decimal/bench_comparison.mojo similarity index 100% rename from benches/bench_comparison.mojo rename to benches/decimal/bench_comparison.mojo diff --git a/benches/bench_divide.mojo b/benches/decimal/bench_divide.mojo similarity index 100% rename from benches/bench_divide.mojo rename to benches/decimal/bench_divide.mojo diff --git a/benches/bench_exp.mojo b/benches/decimal/bench_exp.mojo similarity index 98% rename from benches/bench_exp.mojo rename to benches/decimal/bench_exp.mojo index 148d5f4..1b27b9d 100644 --- a/benches/bench_exp.mojo +++ b/benches/decimal/bench_exp.mojo @@ -73,7 +73,7 @@ fn run_benchmark( var py_decimal = pydecimal.Decimal(input_value) # Execute the operations once to verify correctness - var mojo_result = dm.exponential.exp(mojo_decimal) + var mojo_result = mojo_decimal.exp() var py_result = py_decimal.exp() # Display results for verification @@ -83,7 +83,7 @@ fn run_benchmark( # Benchmark Mojo implementation var t0 = perf_counter_ns() for _ in range(iterations): - _ = dm.exponential.exp(mojo_decimal) + _ = mojo_decimal.exp() var mojo_time = (perf_counter_ns() - t0) / iterations if mojo_time == 0: mojo_time = 1 # Prevent division by zero diff --git a/benches/bench_floor_divide.mojo b/benches/decimal/bench_floor_divide.mojo similarity index 100% rename from benches/bench_floor_divide.mojo rename to benches/decimal/bench_floor_divide.mojo diff --git a/benches/bench_from_float.mojo b/benches/decimal/bench_from_float.mojo similarity index 100% rename from benches/bench_from_float.mojo rename to benches/decimal/bench_from_float.mojo diff --git a/benches/bench_from_int.mojo b/benches/decimal/bench_from_int.mojo similarity index 100% rename from benches/bench_from_int.mojo rename to benches/decimal/bench_from_int.mojo diff --git a/benches/bench_from_string.mojo b/benches/decimal/bench_from_string.mojo similarity index 100% rename from benches/bench_from_string.mojo rename to benches/decimal/bench_from_string.mojo diff --git a/benches/bench_ln.mojo b/benches/decimal/bench_ln.mojo similarity index 98% rename from benches/bench_ln.mojo rename to benches/decimal/bench_ln.mojo index 7ce4674..c950b90 100644 --- a/benches/bench_ln.mojo +++ b/benches/decimal/bench_ln.mojo @@ -73,7 +73,7 @@ fn run_benchmark( var py_decimal = pydecimal.Decimal(input_value) # Execute the operations once to verify correctness - var mojo_result = dm.exponential.ln(mojo_decimal) + var mojo_result = dm.decimal.exponential.ln(mojo_decimal) var py_result = py_decimal.ln() # Display results for verification @@ -83,7 +83,7 @@ fn run_benchmark( # Benchmark Mojo implementation var t0 = perf_counter_ns() for _ in range(iterations): - _ = dm.exponential.ln(mojo_decimal) + _ = dm.decimal.exponential.ln(mojo_decimal) var mojo_time = (perf_counter_ns() - t0) / iterations if mojo_time == 0: mojo_time = 1 # Prevent division by zero diff --git a/benches/bench_log10.mojo b/benches/decimal/bench_log10.mojo similarity index 100% rename from benches/bench_log10.mojo rename to benches/decimal/bench_log10.mojo diff --git a/benches/bench_modulo.mojo b/benches/decimal/bench_modulo.mojo similarity index 100% rename from benches/bench_modulo.mojo rename to benches/decimal/bench_modulo.mojo diff --git a/benches/bench_multiply.mojo b/benches/decimal/bench_multiply.mojo similarity index 99% rename from benches/bench_multiply.mojo rename to benches/decimal/bench_multiply.mojo index f6f2ef9..be46f76 100644 --- a/benches/bench_multiply.mojo +++ b/benches/decimal/bench_multiply.mojo @@ -299,7 +299,7 @@ fn main() raises: # Case 11: Decimal multiplication with many digits after the decimal point var case11_a_mojo = Decimal.E() - var case11_b_mojo = dm.constants.E0D5() + var case11_b_mojo = dm.decimal.constants.E0D5() var case11_a_py = pydecimal.Decimal("1").exp() var case11_b_py = pydecimal.Decimal("0.5").exp() run_benchmark( diff --git a/benches/bench_power.mojo b/benches/decimal/bench_power.mojo similarity index 98% rename from benches/bench_power.mojo rename to benches/decimal/bench_power.mojo index f8547b1..9860dc0 100644 --- a/benches/bench_power.mojo +++ b/benches/decimal/bench_power.mojo @@ -78,7 +78,7 @@ fn run_benchmark( var py_exponent = pydecimal.Decimal(exponent_value) # Execute the operations once to verify correctness - var mojo_result = dm.exponential.power(mojo_base, mojo_exponent) + var mojo_result = dm.decimal.exponential.power(mojo_base, mojo_exponent) var py_result = py_base**py_exponent # Display results for verification @@ -88,7 +88,7 @@ fn run_benchmark( # Benchmark Mojo implementation var t0 = perf_counter_ns() for _ in range(iterations): - _ = dm.exponential.power(mojo_base, mojo_exponent) + _ = dm.decimal.exponential.power(mojo_base, mojo_exponent) var mojo_time = (perf_counter_ns() - t0) / iterations if mojo_time == 0: mojo_time = 1 # Prevent division by zero diff --git a/benches/bench_quantize.mojo b/benches/decimal/bench_quantize.mojo similarity index 100% rename from benches/bench_quantize.mojo rename to benches/decimal/bench_quantize.mojo diff --git a/benches/bench_root.mojo b/benches/decimal/bench_root.mojo similarity index 98% rename from benches/bench_root.mojo rename to benches/decimal/bench_root.mojo index 6953c9e..a7c145e 100644 --- a/benches/bench_root.mojo +++ b/benches/decimal/bench_root.mojo @@ -82,7 +82,7 @@ fn run_benchmark( var py_result: PythonObject # Execute the operations once to verify correctness - var mojo_result = dm.exponential.root(mojo_decimal, nth_root) + var mojo_result = dm.decimal.exponential.root(mojo_decimal, nth_root) # Handle Python calculation, accounting for negative odd root limitation if is_negative_odd_root: @@ -127,7 +127,7 @@ fn run_benchmark( # Benchmark Mojo implementation var t0 = perf_counter_ns() for _ in range(iterations): - _ = dm.exponential.root(mojo_decimal, nth_root) + _ = dm.decimal.exponential.root(mojo_decimal, nth_root) var mojo_time = (perf_counter_ns() - t0) / iterations if mojo_time == 0: mojo_time = 1 # Prevent division by zero diff --git a/benches/bench_round.mojo b/benches/decimal/bench_round.mojo similarity index 100% rename from benches/bench_round.mojo rename to benches/decimal/bench_round.mojo diff --git a/benches/bench_sqrt.mojo b/benches/decimal/bench_sqrt.mojo similarity index 100% rename from benches/bench_sqrt.mojo rename to benches/decimal/bench_sqrt.mojo diff --git a/benches/bench_subtract.mojo b/benches/decimal/bench_subtract.mojo similarity index 100% rename from benches/bench_subtract.mojo rename to benches/decimal/bench_subtract.mojo diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..a43b82e --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,162 @@ +# Examples on Decimal + +Here are 8 key examples highlighting the most important features of the `Decimal` type in its current state: + +## 1. Fixed-Point Precision for Financial Calculations + +```mojo +from decimojo import dm, Decimal + +# The classic floating-point problem +print(0.1 + 0.2) # 0.30000000000000004 (not exactly 0.3) + +# Decimal solves this with exact representation +var d1 = Decimal("0.1") +var d2 = Decimal("0.2") +var sum = d1 + d2 +print(sum) # Exactly 0.3 + +# Financial calculation example - computing tax +var price = Decimal("19.99") +var tax_rate = Decimal("0.0725") +var tax = price * tax_rate # Exactly 1.449275 +var total = price + tax # Exactly 21.439275 +``` + +## 2. Basic Arithmetic with Proper Banker's Rounding + +```mojo +# Addition with different scales +var a = Decimal("123.45") +var b = Decimal("67.8") +print(a + b) # 191.25 (preserves highest precision) + +# Subtraction with negative result +var c = Decimal("50") +var d = Decimal("75.25") +print(c - d) # -25.25 + +# Multiplication with banker's rounding (round to even) +var e = Decimal("12.345") +var f = Decimal("5.67") +print(round(e * f, 2)) # 69.96 (rounds to nearest even) + +# Division with banker's rounding +var g = Decimal("10") +var h = Decimal("3") +print(round(g / h, 2)) # 3.33 (rounded banker's style) +``` + +## 3. Scale and Precision Management + +```mojo +# Scale refers to number of decimal places +var d1 = Decimal("123.45") +print(d1.scale()) # 2 + +# Precision control with explicit rounding +var d2 = Decimal("123.456") +print(d2.round_to_scale(1)) # 123.5 (banker's rounding) + +# High precision is preserved (up to 28 decimal places) +var precise = Decimal("0.1234567890123456789012345678") +print(precise) # 0.1234567890123456789012345678 +``` + +## 4. Sign Handling and Absolute Value + +```mojo +# Negation operator +var pos = Decimal("123.45") +var neg = -pos +print(neg) # -123.45 + +# Absolute value +var abs_val = abs(Decimal("-987.65")) +print(abs_val) # 987.65 + +# Sign checking +print(Decimal("-123.45").is_negative()) # True +print(Decimal("0").is_negative()) # False + +# Sign preservation in multiplication +print(Decimal("-5") * Decimal("3")) # -15 +print(Decimal("-5") * Decimal("-3")) # 15 +``` + +## 5. Advanced Mathematical Operations + +```mojo +# Highly accurate square root implementation +var root2 = Decimal("2").sqrt() +print(root2) # 1.4142135623730950488016887242 + +# Square root of imperfect squares +var root_15_9999 = Decimal("15.9999").sqrt() +print(root_15_9999) # 3.9999874999804686889646053305 + +# Integer powers with fast binary exponentiation +var cubed = Decimal("3") ** 3 +print(cubed) # 27 + +# Negative powers (reciprocals) +var recip = Decimal("2") ** (-1) +print(recip) # 0.5 +``` + +## 6. Robust Edge Case Handling + +```mojo +# Division by zero is properly caught +try: + var result = Decimal("10") / Decimal("0") +except: + print("Division by zero properly detected") + +# Zero raised to negative power +try: + var invalid = Decimal("0") ** (-1) +except: + print("Zero to negative power properly detected") + +# Overflow detection and prevention +var max_val = Decimal.MAX() +try: + var overflow = max_val * Decimal("2") +except: + print("Overflow correctly detected") +``` + +## 7. Equality and Comparison Operations + +```mojo +# Equal values with different scales +var a = Decimal("123.4500") +var b = Decimal("123.45") +print(a == b) # True (numeric value equality) + +# Comparison operators +var c = Decimal("100") +var d = Decimal("200") +print(c < d) # True +print(c <= d) # True +print(c > d) # False +print(c >= d) # False +print(c != d) # True +``` + +## 8. Real World Financial Examples + +```mojo +# Monthly loan payment calculation with precise interest +var principal = Decimal("200000") # $200,000 loan +var annual_rate = Decimal("0.05") # 5% interest rate +var monthly_rate = annual_rate / Decimal("12") +var num_payments = Decimal("360") # 30 years + +# Monthly payment formula: P * r(1+r)^n/((1+r)^n-1) +var numerator = monthly_rate * (Decimal("1") + monthly_rate) ** 360 +var denominator = (Decimal("1") + monthly_rate) ** 360 - Decimal("1") +var payment = principal * (numerator / denominator) +print("Monthly payment: $" + String(round(payment, 2))) # $1,073.64 +``` diff --git a/mojoproject.toml b/mojoproject.toml index d622a45..4d9663b 100644 --- a/mojoproject.toml +++ b/mojoproject.toml @@ -19,56 +19,12 @@ format = "magic run mojo format ./" package = "magic run format && magic run mojo package src/decimojo && cp decimojo.mojopkg tests/ && cp decimojo.mojopkg benches/ && rm decimojo.mojopkg" p = "clear && magic run package" -# delete the package files in tests folder -delete_package = "rm tests/decimojo.mojopkg && rm benches/decimojo.mojopkg" - -# debugs (run the testing files only) -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" +# clean the package files in tests folder +clean = "rm tests/decimojo.mojopkg && rm benches/decimojo.mojopkg" # tests (use the mojo testing tool) -test = "magic run package && magic run mojo test tests && magic run delete_package" -t = "clear && magic run test" -test_arith = "magic run package && magic run mojo test tests/test_arithmetics.mojo && magic run delete_package" -test_multiply = "magic run package && magic run mojo test tests/test_multiply.mojo && magic run delete_package" -test_divide = "magic run package && magic run mojo test tests/test_divide.mojo && magic run delete_package" -test_floor_divide = "magic run package && magic run mojo test tests/test_floor_divide.mojo && magic run delete_package" -test_modulo = "magic run package && magic run mojo test tests/test_modulo.mojo && magic run delete_package" -test_sqrt = "magic run package && magic run mojo test tests/test_sqrt.mojo && magic run delete_package" -test_root = "magic run package && magic run mojo test tests/test_root.mojo && magic run delete_package" -test_round = "magic run package && magic run mojo test tests/test_round.mojo && magic run delete_package" -test_quantize = "magic run package && magic run mojo test tests/test_quantize.mojo && magic run delete_package" -test_from_components = "magic run package && magic run mojo test tests/test_from_components.mojo && magic run delete_package" -test_from_float = "magic run package && magic run mojo test tests/test_from_float.mojo && magic run delete_package" -test_from_string = "magic run package && magic run mojo test tests/test_from_string.mojo && magic run delete_package" -test_from_int = "magic run package && magic run mojo test tests/test_from_int.mojo && magic run delete_package" -test_to_float = "magic run package && magic run mojo test tests/test_to_float.mojo && magic run delete_package" -test_comparison = "magic run package && magic run mojo test tests/test_comparison.mojo && magic run delete_package" -test_factorial = "magic run package && magic run mojo test tests/test_factorial.mojo && magic run delete_package" -test_exp = "magic run package && magic run mojo test tests/test_exp.mojo && magic run delete_package" -test_ln = "magic run package && magic run mojo test tests/test_ln.mojo && magic run delete_package" -test_log = "magic run package && magic run mojo test tests/test_log.mojo && magic run delete_package" -test_log10 = "magic run package && magic run mojo test tests/test_log10.mojo && magic run delete_package" -test_power = "magic run package && magic run mojo test tests/test_power.mojo && magic run delete_package" +test = "magic run package && magic run mojo test tests --filter" +t = "clear && magic run package && magic run mojo test tests --filter" # benches -bench = "magic run package && cd benches && magic run mojo bench.mojo && cd .. && magic run delete_package" -b = "clear && magic run bench" -bench_multiply = "magic run package && cd benches && magic run mojo bench_multiply.mojo && cd .. && magic run delete_package" -bench_divide = "magic run package && cd benches && magic run mojo bench_divide.mojo && cd .. && magic run delete_package" -bench_floor_divide = "magic run package && cd benches && magic run mojo bench_floor_divide.mojo && cd .. && magic run delete_package" -bench_modulo = "magic run package && cd benches && magic run mojo bench_modulo.mojo && cd .. && magic run delete_package" -bench_sqrt = "magic run package && cd benches && magic run mojo bench_sqrt.mojo && cd .. && magic run delete_package" -bench_root = "magic run package && cd benches && magic run mojo bench_root.mojo && cd .. && magic run delete_package" -bench_round = "magic run package && cd benches && magic run mojo bench_round.mojo && cd .. && magic run delete_package" -bench_quantize = "magic run package && cd benches && magic run mojo bench_quantize.mojo && cd .. && magic run delete_package" -bench_from_float = "magic run package && cd benches && magic run mojo bench_from_float.mojo && cd .. && magic run delete_package" -bench_from_string = "magic run package && cd benches && magic run mojo bench_from_string.mojo && cd .. && magic run delete_package" -bench_from_int = "magic run package && cd benches && magic run mojo bench_from_int.mojo && cd .. && magic run delete_package" -bench_comparison = "magic run package && cd benches && magic run mojo bench_comparison.mojo && cd .. && magic run delete_package" -bench_exp = "magic run package && cd benches && magic run mojo bench_exp.mojo && cd .. && magic run delete_package" -bench_ln = "magic run package && cd benches && magic run mojo bench_ln.mojo && cd .. && magic run delete_package" -bench_log10 = "magic run package && cd benches && magic run mojo bench_log10.mojo && cd .. && magic run delete_package" -bench_power = "magic run package && cd benches && magic run mojo bench_power.mojo && cd .. && magic run delete_package" +bench_decimal = "magic run package && cd benches/decimal && magic run mojo -I ../ bench.mojo && cd ../.. && magic run clean" \ No newline at end of file diff --git a/src/decimojo/__init__.mojo b/src/decimojo/__init__.mojo index 3db9daa..24d45a2 100644 --- a/src/decimojo/__init__.mojo +++ b/src/decimojo/__init__.mojo @@ -27,11 +27,11 @@ from decimojo.prelude import dm, Decimal, RoundingMode ``` """ -from .decimal import Decimal +from .decimal.decimal import Decimal from .rounding_mode import RoundingMode -from .arithmetics import ( +from .decimal.arithmetics import ( add, subtract, absolute, @@ -42,7 +42,7 @@ from .arithmetics import ( modulo, ) -from .comparison import ( +from .decimal.comparison import ( greater, greater_equal, less, @@ -51,10 +51,10 @@ from .comparison import ( not_equal, ) -from .exponential import power, root, sqrt, exp, ln, log, log10 +from .decimal.exponential import power, root, sqrt, exp, ln, log, log10 -from .rounding import round, quantize +from .decimal.rounding import round, quantize -from .special import ( +from .decimal.special import ( factorial, ) diff --git a/src/decimojo/decimal/__init__.mojo b/src/decimojo/decimal/__init__.mojo new file mode 100644 index 0000000..e69de29 diff --git a/src/decimojo/arithmetics.mojo b/src/decimojo/decimal/arithmetics.mojo similarity index 99% rename from src/decimojo/arithmetics.mojo rename to src/decimojo/decimal/arithmetics.mojo index 4a2abf4..230c135 100644 --- a/src/decimojo/arithmetics.mojo +++ b/src/decimojo/decimal/arithmetics.mojo @@ -37,7 +37,7 @@ Implements functions for mathematical operations on Decimal objects. import time import testing -from decimojo.decimal import Decimal +from decimojo.decimal.decimal import Decimal from decimojo.rounding_mode import RoundingMode import decimojo.utility diff --git a/src/decimojo/comparison.mojo b/src/decimojo/decimal/comparison.mojo similarity index 99% rename from src/decimojo/comparison.mojo rename to src/decimojo/decimal/comparison.mojo index 7b0b2c3..e658584 100644 --- a/src/decimojo/comparison.mojo +++ b/src/decimojo/decimal/comparison.mojo @@ -44,7 +44,7 @@ Implements functions for comparison operations on Decimal objects. import testing -from decimojo.decimal import Decimal +from decimojo.decimal.decimal import Decimal import decimojo.utility diff --git a/src/decimojo/constants.mojo b/src/decimojo/decimal/constants.mojo similarity index 99% rename from src/decimojo/constants.mojo rename to src/decimojo/decimal/constants.mojo index 93612ad..9fe2eed 100644 --- a/src/decimojo/constants.mojo +++ b/src/decimojo/decimal/constants.mojo @@ -23,6 +23,8 @@ """Useful constants for Decimal type.""" +from decimojo.decimal.decimal import Decimal + # ===----------------------------------------------------------------------=== # # # Integer and decimal constants diff --git a/src/decimojo/decimal.mojo b/src/decimojo/decimal/decimal.mojo similarity index 93% rename from src/decimojo/decimal.mojo rename to src/decimojo/decimal/decimal.mojo index 74eed37..893df20 100644 --- a/src/decimojo/decimal.mojo +++ b/src/decimojo/decimal/decimal.mojo @@ -29,11 +29,11 @@ mathematical methods that do not implement a trait. from memory import UnsafePointer import testing -import decimojo.arithmetics -import decimojo.comparison -import decimojo.constants -import decimojo.exponential -import decimojo.rounding +import decimojo.decimal.arithmetics +import decimojo.decimal.comparison +import decimojo.decimal.constants +import decimojo.decimal.exponential +import decimojo.decimal.rounding from decimojo.rounding_mode import RoundingMode import decimojo.utility @@ -47,7 +47,7 @@ struct Decimal( Roundable, Writable, ): - """Represents a fixed-point decimal number with 96-bit precision. + """Represents a 128-bit fixed-point decimal number. Notes: @@ -192,13 +192,13 @@ struct Decimal( @staticmethod fn PI() -> Decimal: """Returns the value of pi (π) as a Decimal.""" - return decimojo.constants.PI() + return decimojo.decimal.constants.PI() @always_inline @staticmethod fn E() -> Decimal: """Returns the value of Euler's number (e) as a Decimal.""" - return decimojo.constants.E() + return decimojo.decimal.constants.E() # ===------------------------------------------------------------------=== # # Constructors and life time dunder methods @@ -1141,14 +1141,14 @@ struct Decimal( """Returns the absolute value of this Decimal. See `absolute()` for more information. """ - return decimojo.arithmetics.absolute(self) + return decimojo.decimal.arithmetics.absolute(self) @always_inline fn __neg__(self) -> Self: """Returns the negation of this Decimal. See `negative()` for more information. """ - return decimojo.arithmetics.negative(self) + return decimojo.decimal.arithmetics.negative(self) # ===------------------------------------------------------------------=== # # Basic binary arithmetic operation dunders @@ -1158,51 +1158,51 @@ struct Decimal( @always_inline fn __add__(self, other: Self) raises -> Self: - return decimojo.arithmetics.add(self, other) + return decimojo.decimal.arithmetics.add(self, other) @always_inline fn __add__(self, other: Int) raises -> Self: - return decimojo.arithmetics.add(self, Decimal(other)) + return decimojo.decimal.arithmetics.add(self, Decimal(other)) @always_inline fn __sub__(self, other: Self) raises -> Self: - return decimojo.arithmetics.subtract(self, other) + return decimojo.decimal.arithmetics.subtract(self, other) @always_inline fn __sub__(self, other: Int) raises -> Self: - return decimojo.arithmetics.subtract(self, Decimal(other)) + return decimojo.decimal.arithmetics.subtract(self, Decimal(other)) @always_inline fn __mul__(self, other: Self) raises -> Self: - return decimojo.arithmetics.multiply(self, other) + return decimojo.decimal.arithmetics.multiply(self, other) @always_inline fn __mul__(self, other: Int) raises -> Self: - return decimojo.arithmetics.multiply(self, Decimal(other)) + return decimojo.decimal.arithmetics.multiply(self, Decimal(other)) @always_inline fn __truediv__(self, other: Self) raises -> Self: - return decimojo.arithmetics.divide(self, other) + return decimojo.decimal.arithmetics.divide(self, other) @always_inline fn __truediv__(self, other: Int) raises -> Self: - return decimojo.arithmetics.divide(self, Decimal(other)) + return decimojo.decimal.arithmetics.divide(self, Decimal(other)) @always_inline fn __floordiv__(self, other: Self) raises -> Self: - return decimojo.arithmetics.floor_divide(self, other) + return decimojo.decimal.arithmetics.floor_divide(self, other) @always_inline fn __floordiv__(self, other: Int) raises -> Self: - return decimojo.arithmetics.floor_divide(self, Decimal(other)) + return decimojo.decimal.arithmetics.floor_divide(self, Decimal(other)) @always_inline fn __mod__(self, other: Self) raises -> Self: - return decimojo.arithmetics.modulo(self, other) + return decimojo.decimal.arithmetics.modulo(self, other) @always_inline fn __mod__(self, other: Int) raises -> Self: - return decimojo.arithmetics.modulo(self, Decimal(other)) + return decimojo.decimal.arithmetics.modulo(self, Decimal(other)) @always_inline fn __pow__(self, exponent: Self) raises -> Self: @@ -1221,27 +1221,27 @@ struct Decimal( @always_inline fn __radd__(self, other: Int) raises -> Self: - return decimojo.arithmetics.add(Decimal(other), self) + return decimojo.decimal.arithmetics.add(Decimal(other), self) @always_inline fn __rsub__(self, other: Int) raises -> Self: - return decimojo.arithmetics.subtract(Decimal(other), self) + return decimojo.decimal.arithmetics.subtract(Decimal(other), self) @always_inline fn __rmul__(self, other: Int) raises -> Self: - return decimojo.arithmetics.multiply(Decimal(other), self) + return decimojo.decimal.arithmetics.multiply(Decimal(other), self) @always_inline fn __rtruediv__(self, other: Int) raises -> Self: - return decimojo.arithmetics.divide(Decimal(other), self) + return decimojo.decimal.arithmetics.divide(Decimal(other), self) @always_inline fn __rfloordiv__(self, other: Int) raises -> Self: - return decimojo.arithmetics.floor_divide(Decimal(other), self) + return decimojo.decimal.arithmetics.floor_divide(Decimal(other), self) @always_inline fn __rmod__(self, other: Int) raises -> Self: - return decimojo.arithmetics.modulo(Decimal(other), self) + return decimojo.decimal.arithmetics.modulo(Decimal(other), self) # ===------------------------------------------------------------------=== # # Basic binary augmented arithmetic assignments dunders @@ -1252,43 +1252,43 @@ struct Decimal( @always_inline fn __iadd__(mut self, other: Self) raises: - self = decimojo.arithmetics.add(self, other) + self = decimojo.decimal.arithmetics.add(self, other) @always_inline fn __iadd__(mut self, other: Int) raises: - self = decimojo.arithmetics.add(self, Decimal(other)) + self = decimojo.decimal.arithmetics.add(self, Decimal(other)) @always_inline fn __isub__(mut self, other: Self) raises: - self = decimojo.arithmetics.subtract(self, other) + self = decimojo.decimal.arithmetics.subtract(self, other) @always_inline fn __isub__(mut self, other: Int) raises: - self = decimojo.arithmetics.subtract(self, Decimal(other)) + self = decimojo.decimal.arithmetics.subtract(self, Decimal(other)) @always_inline fn __imul__(mut self, other: Self) raises: - self = decimojo.arithmetics.multiply(self, other) + self = decimojo.decimal.arithmetics.multiply(self, other) @always_inline fn __imul__(mut self, other: Int) raises: - self = decimojo.arithmetics.multiply(self, Decimal(other)) + self = decimojo.decimal.arithmetics.multiply(self, Decimal(other)) @always_inline fn __itruediv__(mut self, other: Self) raises: - self = decimojo.arithmetics.divide(self, other) + self = decimojo.decimal.arithmetics.divide(self, other) @always_inline fn __itruediv__(mut self, other: Int) raises: - self = decimojo.arithmetics.divide(self, Decimal(other)) + self = decimojo.decimal.arithmetics.divide(self, Decimal(other)) @always_inline fn __ifloordiv__(mut self, other: Self) raises: - self = decimojo.arithmetics.floor_divide(self, other) + self = decimojo.decimal.arithmetics.floor_divide(self, other) @always_inline fn __ifloordiv__(mut self, other: Int) raises: - self = decimojo.arithmetics.floor_divide(self, Decimal(other)) + self = decimojo.decimal.arithmetics.floor_divide(self, Decimal(other)) # ===------------------------------------------------------------------=== # # Basic binary comparison operation dunders @@ -1300,42 +1300,42 @@ struct Decimal( """Greater than comparison operator. See `greater()` for more information. """ - return decimojo.comparison.greater(self, other) + return decimojo.decimal.comparison.greater(self, other) @always_inline fn __lt__(self, other: Decimal) -> Bool: """Less than comparison operator. See `less()` for more information. """ - return decimojo.comparison.less(self, other) + return decimojo.decimal.comparison.less(self, other) @always_inline fn __ge__(self, other: Decimal) -> Bool: """Greater than or equal comparison operator. See `greater_equal()` for more information. """ - return decimojo.comparison.greater_equal(self, other) + return decimojo.decimal.comparison.greater_equal(self, other) @always_inline fn __le__(self, other: Decimal) -> Bool: """Less than or equal comparison operator. See `less_equal()` for more information. """ - return decimojo.comparison.less_equal(self, other) + return decimojo.decimal.comparison.less_equal(self, other) @always_inline fn __eq__(self, other: Decimal) -> Bool: """Equality comparison operator. See `equal()` for more information. """ - return decimojo.comparison.equal(self, other) + return decimojo.decimal.comparison.equal(self, other) @always_inline fn __ne__(self, other: Decimal) -> Bool: """Inequality comparison operator. See `not_equal()` for more information. """ - return decimojo.comparison.not_equal(self, other) + return decimojo.decimal.comparison.not_equal(self, other) # ===------------------------------------------------------------------=== # # Other dunders that implements traits @@ -1352,7 +1352,7 @@ struct Decimal( Error: Calling `round()` failed. """ try: - return decimojo.rounding.round( + return decimojo.decimal.rounding.round( self, ndigits=ndigits, rounding_mode=RoundingMode.ROUND_HALF_EVEN, @@ -1364,7 +1364,7 @@ struct Decimal( fn __round__(self) -> Self: """**OVERLOAD**.""" try: - return decimojo.rounding.round( + return decimojo.decimal.rounding.round( self, ndigits=0, rounding_mode=RoundingMode.ROUND_HALF_EVEN ) except e: @@ -1387,7 +1387,7 @@ struct Decimal( (2) Raises an error if the operation would result in overflow. See `round()` for more information. """ - return decimojo.rounding.round( + return decimojo.decimal.rounding.round( self, ndigits=ndigits, rounding_mode=rounding_mode ) @@ -1400,55 +1400,55 @@ struct Decimal( """Quantizes this Decimal to the specified exponent. See `quantize()` for more information. """ - return decimojo.rounding.quantize(self, exp, rounding_mode) + return decimojo.decimal.rounding.quantize(self, exp, rounding_mode) @always_inline fn exp(self) raises -> Self: """Calculates the exponential of this Decimal. See `exp()` for more information. """ - return decimojo.exponential.exp(self) + return decimojo.decimal.exponential.exp(self) @always_inline fn ln(self) raises -> Self: """Calculates the natural logarithm of this Decimal. See `ln()` for more information. """ - return decimojo.exponential.ln(self) + return decimojo.decimal.exponential.ln(self) @always_inline fn log10(self) raises -> Decimal: """Computes the base-10 logarithm of this Decimal.""" - return decimojo.exponential.log10(self) + return decimojo.decimal.exponential.log10(self) @always_inline fn log(self, base: Decimal) raises -> Decimal: """Computes the logarithm of this Decimal with an arbitrary base.""" - return decimojo.exponential.log(self, base) + return decimojo.decimal.exponential.log(self, base) @always_inline fn power(self, exponent: Int) raises -> Decimal: """Raises this Decimal to the power of an integer.""" - return decimojo.exponential.power(self, Decimal(exponent)) + return decimojo.decimal.exponential.power(self, Decimal(exponent)) @always_inline fn power(self, exponent: Decimal) raises -> Decimal: """Raises this Decimal to the power of another Decimal.""" - return decimojo.exponential.power(self, exponent) + return decimojo.decimal.exponential.power(self, exponent) @always_inline fn root(self, n: Int) raises -> Self: """Calculates the n-th root of this Decimal. See `root()` for more information. """ - return decimojo.exponential.root(self, n) + return decimojo.decimal.exponential.root(self, n) @always_inline fn sqrt(self) raises -> Self: """Calculates the square root of this Decimal. See `sqrt()` for more information. """ - return decimojo.exponential.sqrt(self) + return decimojo.decimal.exponential.sqrt(self) # ===------------------------------------------------------------------=== # # Other methods diff --git a/src/decimojo/exponential.mojo b/src/decimojo/decimal/exponential.mojo similarity index 89% rename from src/decimojo/exponential.mojo rename to src/decimojo/decimal/exponential.mojo index 5a04e7d..90e4fa9 100644 --- a/src/decimojo/exponential.mojo +++ b/src/decimojo/decimal/exponential.mojo @@ -23,8 +23,8 @@ import math as builtin_math import testing import time -import decimojo.constants -import decimojo.special +import decimojo.decimal.constants +import decimojo.decimal.special import decimojo.utility # ===----------------------------------------------------------------------=== # @@ -69,7 +69,7 @@ fn power(base: Decimal, exponent: Decimal) raises -> Decimal: # CASE: If the exponent is simple fractions # 0.5 - if exponent == decimojo.constants.M0D5(): + if exponent == decimojo.decimal.constants.M0D5(): try: return sqrt(base) except e: @@ -484,91 +484,91 @@ fn exp(x: Decimal) raises -> Decimal: var x_int = Int(x) if x.is_one(): - return decimojo.constants.E() + return decimojo.decimal.constants.E() elif x_int < 1: - var M0D5 = decimojo.constants.M0D5() - var M0D25 = decimojo.constants.M0D25() + var M0D5 = decimojo.decimal.constants.M0D5() + var M0D25 = decimojo.decimal.constants.M0D25() if x < M0D25: # 0 < x < 0.25 return exp_series(x) elif x < M0D5: # 0.25 <= x < 0.5 - exp_chunk = decimojo.constants.E0D25() + exp_chunk = decimojo.decimal.constants.E0D25() remainder = x - M0D25 else: # 0.5 <= x < 1 - exp_chunk = decimojo.constants.E0D5() + exp_chunk = decimojo.decimal.constants.E0D5() remainder = x - M0D5 elif x_int == 1: # 1 <= x < 2, chunk = 1 - exp_chunk = decimojo.constants.E() + exp_chunk = decimojo.decimal.constants.E() remainder = x - x_int elif x_int == 2: # 2 <= x < 3, chunk = 2 - exp_chunk = decimojo.constants.E2() + exp_chunk = decimojo.decimal.constants.E2() remainder = x - x_int elif x_int == 3: # 3 <= x < 4, chunk = 3 - exp_chunk = decimojo.constants.E3() + exp_chunk = decimojo.decimal.constants.E3() remainder = x - x_int elif x_int == 4: # 4 <= x < 5, chunk = 4 - exp_chunk = decimojo.constants.E4() + exp_chunk = decimojo.decimal.constants.E4() remainder = x - x_int elif x_int == 5: # 5 <= x < 6, chunk = 5 - exp_chunk = decimojo.constants.E5() + exp_chunk = decimojo.decimal.constants.E5() remainder = x - x_int elif x_int == 6: # 6 <= x < 7, chunk = 6 - exp_chunk = decimojo.constants.E6() + exp_chunk = decimojo.decimal.constants.E6() remainder = x - x_int elif x_int == 7: # 7 <= x < 8, chunk = 7 - exp_chunk = decimojo.constants.E7() + exp_chunk = decimojo.decimal.constants.E7() remainder = x - x_int elif x_int == 8: # 8 <= x < 9, chunk = 8 - exp_chunk = decimojo.constants.E8() + exp_chunk = decimojo.decimal.constants.E8() remainder = x - x_int elif x_int == 9: # 9 <= x < 10, chunk = 9 - exp_chunk = decimojo.constants.E9() + exp_chunk = decimojo.decimal.constants.E9() remainder = x - x_int elif x_int == 10: # 10 <= x < 11, chunk = 10 - exp_chunk = decimojo.constants.E10() + exp_chunk = decimojo.decimal.constants.E10() remainder = x - x_int elif x_int == 11: # 11 <= x < 12, chunk = 11 - exp_chunk = decimojo.constants.E11() + exp_chunk = decimojo.decimal.constants.E11() remainder = x - x_int elif x_int == 12: # 12 <= x < 13, chunk = 12 - exp_chunk = decimojo.constants.E12() + exp_chunk = decimojo.decimal.constants.E12() remainder = x - x_int elif x_int == 13: # 13 <= x < 14, chunk = 13 - exp_chunk = decimojo.constants.E13() + exp_chunk = decimojo.decimal.constants.E13() remainder = x - x_int elif x_int == 14: # 14 <= x < 15, chunk = 14 - exp_chunk = decimojo.constants.E14() + exp_chunk = decimojo.decimal.constants.E14() remainder = x - x_int elif x_int == 15: # 15 <= x < 16, chunk = 15 - exp_chunk = decimojo.constants.E15() + exp_chunk = decimojo.decimal.constants.E15() remainder = x - x_int elif x_int < 32: # 16 <= x < 32, chunk = 16 num_chunks = x_int >> 4 - exp_chunk = decimojo.constants.E16() + exp_chunk = decimojo.decimal.constants.E16() remainder = x - (num_chunks << 4) else: # chunk = 32 num_chunks = x_int >> 5 - exp_chunk = decimojo.constants.E32() + exp_chunk = decimojo.decimal.constants.E32() remainder = x - (num_chunks << 5) # Calculate e^(chunk * num_chunks) = (e^chunk)^num_chunks @@ -665,7 +665,7 @@ fn ln(x: Decimal) raises -> Decimal: return Decimal.ZERO() # Special cases for common values - if x == decimojo.constants.E(): + if x == decimojo.decimal.constants.E(): return Decimal.ONE() # For values close to 1, use series expansion directly @@ -680,28 +680,28 @@ fn ln(x: Decimal) raises -> Decimal: var q: Int = 0 # Step 1: handle powers of 10 for large values - if x >= decimojo.constants.M10(): + if x >= decimojo.decimal.constants.M10(): # Repeatedly divide by 10 until m < 10 - while m >= decimojo.constants.M10(): - m = m / decimojo.constants.M10() + while m >= decimojo.decimal.constants.M10(): + m = m / decimojo.decimal.constants.M10() q += 1 elif x < Decimal(1, 0, 0, 1 << 16): # Repeatedly multiply by 10 until m >= 0.1 while m < Decimal(1, 0, 0, 1 << 16): - m = m * decimojo.constants.M10() + m = m * decimojo.decimal.constants.M10() q -= 1 # Now 0.1 <= m < 10 # Step 2: normalize to [0.5, 2) using powers of 2 - if m >= decimojo.constants.M2(): + if m >= decimojo.decimal.constants.M2(): # Repeatedly divide by 2 until m < 2 - while m >= decimojo.constants.M2(): - m = m / decimojo.constants.M2() + while m >= decimojo.decimal.constants.M2(): + m = m / decimojo.decimal.constants.M2() p += 1 elif m < Decimal(5, 0, 0, 1 << 16): # Repeatedly multiply by 2 until m >= 0.5 while m < Decimal(5, 0, 0, 1 << 16): - m = m * decimojo.constants.M2() + m = m * decimojo.decimal.constants.M2() p -= 1 # Now 0.5 <= m < 2 @@ -714,41 +714,41 @@ fn ln(x: Decimal) raises -> Decimal: ln_m = ( ln_series( (m - Decimal(9, 0, 0, 1 << 16)) - * decimojo.constants.INV0D9() + * decimojo.decimal.constants.INV0D9() ) - + decimojo.constants.LN0D9() + + decimojo.decimal.constants.LN0D9() ) elif m >= Decimal(8, 0, 0, 1 << 16): ln_m = ( ln_series( (m - Decimal(8, 0, 0, 1 << 16)) - * decimojo.constants.INV0D8() + * decimojo.decimal.constants.INV0D8() ) - + decimojo.constants.LN0D8() + + decimojo.decimal.constants.LN0D8() ) elif m >= Decimal(7, 0, 0, 1 << 16): ln_m = ( ln_series( (m - Decimal(7, 0, 0, 1 << 16)) - * decimojo.constants.INV0D7() + * decimojo.decimal.constants.INV0D7() ) - + decimojo.constants.LN0D7() + + decimojo.decimal.constants.LN0D7() ) elif m >= Decimal(6, 0, 0, 1 << 16): ln_m = ( ln_series( (m - Decimal(6, 0, 0, 1 << 16)) - * decimojo.constants.INV0D6() + * decimojo.decimal.constants.INV0D6() ) - + decimojo.constants.LN0D6() + + decimojo.decimal.constants.LN0D6() ) else: # 0.5 <= m < 0.6 ln_m = ( ln_series( (m - Decimal(5, 0, 0, 1 << 16)) - * decimojo.constants.INV0D5() + * decimojo.decimal.constants.INV0D5() ) - + decimojo.constants.LN0D5() + + decimojo.decimal.constants.LN0D5() ) else: @@ -759,73 +759,73 @@ fn ln(x: Decimal) raises -> Decimal: ln_m = ( ln_series( (m - Decimal(11, 0, 0, 1 << 16)) - * decimojo.constants.INV1D1() + * decimojo.decimal.constants.INV1D1() ) - + decimojo.constants.LN1D1() + + decimojo.decimal.constants.LN1D1() ) elif m < Decimal(13, 0, 0, 1 << 16): # 1.2 <= m < 1.3 ln_m = ( ln_series( (m - Decimal(12, 0, 0, 1 << 16)) - * decimojo.constants.INV1D2() + * decimojo.decimal.constants.INV1D2() ) - + decimojo.constants.LN1D2() + + decimojo.decimal.constants.LN1D2() ) elif m < Decimal(14, 0, 0, 1 << 16): # 1.3 <= m < 1.4 ln_m = ( ln_series( (m - Decimal(13, 0, 0, 1 << 16)) - * decimojo.constants.INV1D3() + * decimojo.decimal.constants.INV1D3() ) - + decimojo.constants.LN1D3() + + decimojo.decimal.constants.LN1D3() ) elif m < Decimal(15, 0, 0, 1 << 16): # 1.4 <= m < 1.5 ln_m = ( ln_series( (m - Decimal(14, 0, 0, 1 << 16)) - * decimojo.constants.INV1D4() + * decimojo.decimal.constants.INV1D4() ) - + decimojo.constants.LN1D4() + + decimojo.decimal.constants.LN1D4() ) elif m < Decimal(16, 0, 0, 1 << 16): # 1.5 <= m < 1.6 ln_m = ( ln_series( (m - Decimal(15, 0, 0, 1 << 16)) - * decimojo.constants.INV1D5() + * decimojo.decimal.constants.INV1D5() ) - + decimojo.constants.LN1D5() + + decimojo.decimal.constants.LN1D5() ) elif m < Decimal(17, 0, 0, 1 << 16): # 1.6 <= m < 1.7 ln_m = ( ln_series( (m - Decimal(16, 0, 0, 1 << 16)) - * decimojo.constants.INV1D6() + * decimojo.decimal.constants.INV1D6() ) - + decimojo.constants.LN1D6() + + decimojo.decimal.constants.LN1D6() ) elif m < Decimal(18, 0, 0, 1 << 16): # 1.7 <= m < 1.8 ln_m = ( ln_series( (m - Decimal(17, 0, 0, 1 << 16)) - * decimojo.constants.INV1D7() + * decimojo.decimal.constants.INV1D7() ) - + decimojo.constants.LN1D7() + + decimojo.decimal.constants.LN1D7() ) elif m < Decimal(19, 0, 0, 1 << 16): # 1.8 <= m < 1.9 ln_m = ( ln_series( (m - Decimal(18, 0, 0, 1 << 16)) - * decimojo.constants.INV1D8() + * decimojo.decimal.constants.INV1D8() ) - + decimojo.constants.LN1D8() + + decimojo.decimal.constants.LN1D8() ) else: # 1.9 <= m < 2 ln_m = ( ln_series( (m - Decimal(19, 0, 0, 1 << 16)) - * decimojo.constants.INV1D9() + * decimojo.decimal.constants.INV1D9() ) - + decimojo.constants.LN1D9() + + decimojo.decimal.constants.LN1D9() ) # Combine result: ln(x) = ln(m) + p*ln(2) + q*ln(10) @@ -833,11 +833,11 @@ fn ln(x: Decimal) raises -> Decimal: # Add power of 2 contribution if p != 0: - result = result + Decimal(p) * decimojo.constants.LN2() + result = result + Decimal(p) * decimojo.decimal.constants.LN2() # Add power of 10 contribution if q != 0: - result = result + Decimal(q) * decimojo.constants.LN10() + result = result + Decimal(q) * decimojo.decimal.constants.LN10() return result @@ -886,7 +886,7 @@ fn ln_series(z: Decimal) raises -> Decimal: neg = not neg # Alternate sign if i <= 20: - term = term * z * decimojo.constants.N_DIVIDE_NEXT(i) + term = term * z * decimojo.decimal.constants.N_DIVIDE_NEXT(i) else: term = term * z * Decimal(i) / Decimal(i + 1) @@ -1010,4 +1010,4 @@ fn log10(x: Decimal) raises -> Decimal: pass # Use the identity: log10(x) = ln(x) / ln(10) - return ln(x) / decimojo.constants.LN10() + return ln(x) / decimojo.decimal.constants.LN10() diff --git a/src/decimojo/rounding.mojo b/src/decimojo/decimal/rounding.mojo similarity index 99% rename from src/decimojo/rounding.mojo rename to src/decimojo/decimal/rounding.mojo index e7ae548..d62a755 100644 --- a/src/decimojo/rounding.mojo +++ b/src/decimojo/decimal/rounding.mojo @@ -34,7 +34,7 @@ Implements functions for mathematical operations on Decimal objects. import testing -from decimojo.decimal import Decimal +from decimojo.decimal.decimal import Decimal from decimojo.rounding_mode import RoundingMode import decimojo.utility diff --git a/src/decimojo/special.mojo b/src/decimojo/decimal/special.mojo similarity index 100% rename from src/decimojo/special.mojo rename to src/decimojo/decimal/special.mojo diff --git a/src/decimojo/prelude.mojo b/src/decimojo/prelude.mojo index aa0743f..1e3ff10 100644 --- a/src/decimojo/prelude.mojo +++ b/src/decimojo/prelude.mojo @@ -35,5 +35,5 @@ from decimojo.prelude import * """ import decimojo as dm -from decimojo.decimal import Decimal +from decimojo.decimal.decimal import Decimal from decimojo.rounding_mode import RoundingMode diff --git a/src/decimojo/utility.mojo b/src/decimojo/utility.mojo index 636d54d..6e3aaa5 100644 --- a/src/decimojo/utility.mojo +++ b/src/decimojo/utility.mojo @@ -25,7 +25,7 @@ from memory import UnsafePointer import time -from decimojo.decimal import Decimal +from decimojo.decimal.decimal import Decimal # UNSAFE diff --git a/tests/test_arithmetics.mojo b/tests/decimal/test_arithmetics.mojo similarity index 100% rename from tests/test_arithmetics.mojo rename to tests/decimal/test_arithmetics.mojo diff --git a/tests/test_comparison.mojo b/tests/decimal/test_comparison.mojo similarity index 99% rename from tests/test_comparison.mojo rename to tests/decimal/test_comparison.mojo index f7b655a..38393e5 100644 --- a/tests/test_comparison.mojo +++ b/tests/decimal/test_comparison.mojo @@ -3,7 +3,7 @@ Test Decimal logic operations for comparison, including basic comparisons, edge cases, special handling for zero values, and operator overloads. """ from decimojo.prelude import dm, Decimal, RoundingMode -from decimojo.comparison import ( +from decimojo.decimal.comparison import ( greater, greater_equal, less, diff --git a/tests/test_divide.mojo b/tests/decimal/test_divide.mojo similarity index 100% rename from tests/test_divide.mojo rename to tests/decimal/test_divide.mojo diff --git a/tests/test_exp.mojo b/tests/decimal/test_exp.mojo similarity index 99% rename from tests/test_exp.mojo rename to tests/decimal/test_exp.mojo index 6e259fa..0a8bb2a 100644 --- a/tests/test_exp.mojo +++ b/tests/decimal/test_exp.mojo @@ -6,7 +6,7 @@ and edge cases to ensure proper calculation of e^x. import testing from decimojo.prelude import dm, Decimal, RoundingMode -from decimojo.exponential import exp +from decimojo.decimal.exponential import exp fn test_basic_exp_values() raises: diff --git a/tests/test_factorial.mojo b/tests/decimal/test_factorial.mojo similarity index 99% rename from tests/test_factorial.mojo rename to tests/decimal/test_factorial.mojo index 55d3d7b..aae3c55 100644 --- a/tests/test_factorial.mojo +++ b/tests/decimal/test_factorial.mojo @@ -7,7 +7,7 @@ in the range 0 to 27, which is the maximum range supported by Decimal. import testing from decimojo.prelude import dm, Decimal, RoundingMode -from decimojo.special import factorial, factorial_reciprocal +from decimojo.decimal.special import factorial, factorial_reciprocal fn test_basic_factorials() raises: diff --git a/tests/test_floor_divide.mojo b/tests/decimal/test_floor_divide.mojo similarity index 100% rename from tests/test_floor_divide.mojo rename to tests/decimal/test_floor_divide.mojo diff --git a/tests/test_from_components.mojo b/tests/decimal/test_from_components.mojo similarity index 100% rename from tests/test_from_components.mojo rename to tests/decimal/test_from_components.mojo diff --git a/tests/test_from_float.mojo b/tests/decimal/test_from_float.mojo similarity index 100% rename from tests/test_from_float.mojo rename to tests/decimal/test_from_float.mojo diff --git a/tests/test_from_int.mojo b/tests/decimal/test_from_int.mojo similarity index 100% rename from tests/test_from_int.mojo rename to tests/decimal/test_from_int.mojo diff --git a/tests/test_from_string.mojo b/tests/decimal/test_from_string.mojo similarity index 100% rename from tests/test_from_string.mojo rename to tests/decimal/test_from_string.mojo diff --git a/tests/test_ln.mojo b/tests/decimal/test_ln.mojo similarity index 97% rename from tests/test_ln.mojo rename to tests/decimal/test_ln.mojo index b493880..5924715 100644 --- a/tests/test_ln.mojo +++ b/tests/decimal/test_ln.mojo @@ -5,8 +5,8 @@ and edge cases to ensure proper calculation of the natural logarithm. """ import testing -from decimojo.prelude import dm, Decimal, RoundingMode -from decimojo.exponential import ln +from decimojo import Decimal, RoundingMode +from decimojo.decimal.exponential import ln fn test_basic_ln_values() raises: @@ -15,14 +15,14 @@ fn test_basic_ln_values() raises: # Test case 1: ln(1) = 0 var one = Decimal(1) - var result1 = ln(one) + var result1 = one.ln() testing.assert_equal( String(result1), "0", "ln(1) should be 0, got " + String(result1) ) # Test case 2: ln(e) = 1 var e = Decimal("2.718281828459045235360287471") - var result_e = ln(e) + var result_e = e.ln() testing.assert_true( String(result_e).startswith("1.00000000000000000000"), "ln(e) should be approximately 1, got " + String(result_e), @@ -109,7 +109,7 @@ fn test_mathematical_identities() raises: # Test case 10: ln(e^x) = x var x = Decimal(5) - var ln_e_to_x = ln(dm.exponential.exp(x)) + var ln_e_to_x = ln(x.exp()) testing.assert_true( abs(ln_e_to_x - x) < Decimal("0.0000000001"), "ln(e^x) should equal x within tolerance", diff --git a/tests/test_log.mojo b/tests/decimal/test_log.mojo similarity index 100% rename from tests/test_log.mojo rename to tests/decimal/test_log.mojo diff --git a/tests/test_log10.mojo b/tests/decimal/test_log10.mojo similarity index 100% rename from tests/test_log10.mojo rename to tests/decimal/test_log10.mojo diff --git a/tests/test_modulo.mojo b/tests/decimal/test_modulo.mojo similarity index 100% rename from tests/test_modulo.mojo rename to tests/decimal/test_modulo.mojo diff --git a/tests/test_multiply.mojo b/tests/decimal/test_multiply.mojo similarity index 100% rename from tests/test_multiply.mojo rename to tests/decimal/test_multiply.mojo diff --git a/tests/test_power.mojo b/tests/decimal/test_power.mojo similarity index 99% rename from tests/test_power.mojo rename to tests/decimal/test_power.mojo index 9e1a237..50c6170 100644 --- a/tests/test_power.mojo +++ b/tests/decimal/test_power.mojo @@ -4,7 +4,7 @@ Comprehensive tests for the power function of the Decimal type. import testing from decimojo.prelude import dm, Decimal, RoundingMode -from decimojo.exponential import power +from decimojo.decimal.exponential import power fn test_integer_powers() raises: diff --git a/tests/test_quantize.mojo b/tests/decimal/test_quantize.mojo similarity index 100% rename from tests/test_quantize.mojo rename to tests/decimal/test_quantize.mojo diff --git a/tests/test_root.mojo b/tests/decimal/test_root.mojo similarity index 99% rename from tests/test_root.mojo rename to tests/decimal/test_root.mojo index 03d905b..356bf8c 100644 --- a/tests/test_root.mojo +++ b/tests/decimal/test_root.mojo @@ -6,7 +6,7 @@ and edge cases to ensure proper calculation of x^(1/n). import testing from decimojo.prelude import dm, Decimal, RoundingMode -from decimojo.exponential import root +from decimojo.decimal.exponential import root fn test_basic_root_calculations() raises: diff --git a/tests/test_round.mojo b/tests/decimal/test_round.mojo similarity index 100% rename from tests/test_round.mojo rename to tests/decimal/test_round.mojo diff --git a/tests/test_sqrt.mojo b/tests/decimal/test_sqrt.mojo similarity index 100% rename from tests/test_sqrt.mojo rename to tests/decimal/test_sqrt.mojo diff --git a/tests/test_to_float.mojo b/tests/decimal/test_to_float.mojo similarity index 100% rename from tests/test_to_float.mojo rename to tests/decimal/test_to_float.mojo diff --git a/tests/test_to_int.mojo b/tests/decimal/test_to_int.mojo similarity index 100% rename from tests/test_to_int.mojo rename to tests/decimal/test_to_int.mojo diff --git a/tests/test_to_string.mojo b/tests/decimal/test_to_string.mojo similarity index 100% rename from tests/test_to_string.mojo rename to tests/decimal/test_to_string.mojo diff --git a/tests/test_utility.mojo b/tests/decimal/test_utility.mojo similarity index 100% rename from tests/test_utility.mojo rename to tests/decimal/test_utility.mojo