diff --git a/README.md b/README.md index a9cd6a2a..8e20f358 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The core types are[^auxiliary]: | Type | Other names | Information | Internal representation | | --------- | -------------------- | ---------------------------------------- | ----------------------- | | `BInt` | `BigInt` | Equivalent to Python's `int` | Base-2^32 | -| `Decimal` | `BDec`, `BigDecimal` | Equivalent to Python's `decimal.Decimal` | Base-10^9 | +| `Decimal` | `BigDecimal`, `BDec` | Equivalent to Python's `decimal.Decimal` | Base-10^9 | | `Dec128` | `Decimal128` | 128-bit fixed-precision decimal type | Triple 32-bit words | **Decimo** combines "**Deci**mal" and "**Mo**jo" - reflecting its purpose and implementation language. "Decimo" is also a Latin word meaning "tenth" and is the root of the word "decimal". @@ -94,7 +94,7 @@ from decimo import * This will import the following types or aliases into your namespace: - `BInt` (alias of `BigInt`): An arbitrary-precision signed integer type, equivalent to Python's `int`. -- `Decimal` or `BDec` (aliases of `BigDecimal`): An arbitrary-precision decimal type, equivalent to Python's `decimal.Decimal`. +- `Decimal` (also available as `BigDecimal` or `BDec`): An arbitrary-precision decimal type, equivalent to Python's `decimal.Decimal`. - `Dec128` (alias of `Decimal128`): A 128-bit fixed-precision decimal type. - `RoundingMode`: An enumeration for rounding modes. - `ROUND_DOWN`, `ROUND_HALF_UP`, `ROUND_HALF_EVEN`, `ROUND_UP`: Constants for common rounding modes. @@ -108,10 +108,10 @@ from decimo.prelude import * fn main() raises: - var a = BDec("123456789.123456789") # BDec is an alias for BigDecimal + var a = Decimal("123456789.123456789") var b = Decimal( "1234.56789" - ) # Decimal is a Python-like alias for BigDecimal + ) # === Basic Arithmetic === # print(a + b) # 123458023.691346789 @@ -342,4 +342,4 @@ This repository and its contributions are licensed under the Apache License v2.0 [^bigint]: The `BigInt` implementation uses a base-2^32 representation with a little-endian format, where the least significant word is stored at index 0. Each word is a `UInt32`, allowing for efficient storage and arithmetic operations on large integers. This design choice optimizes performance for binary computations while still supporting arbitrary precision. [^auxiliary]: The auxiliary types include a base-10 arbitrary-precision signed integer type (`BigInt10`) and a base-10 arbitrary-precision unsigned integer type (`BigUInt`) supporting unlimited digits[^bigint10]. `BigUInt` is used as the internal representation for `BigInt10` and `Decimal`. [^bigint10]: The BigInt10 implementation uses a base-10 representation for users (maintaining decimal semantics), while internally using an optimized base-10^9 storage system for efficient calculations. This approach balances human-readable decimal operations with high-performance computing. It provides both floor division (round toward negative infinity) and truncate division (round toward zero) semantics, enabling precise handling of division operations with correct mathematical behavior regardless of operand signs. -[^arbitrary]: Built on top of our completed BigInt10 implementation, BigDecimal will support arbitrary precision for both the integer and fractional parts, similar to `decimal` and `mpmath` in Python, `java.math.BigDecimal` in Java, etc. +[^arbitrary]: Built on top of our completed BigInt10 implementation, Decimal supports arbitrary precision for both the integer and fractional parts, similar to `decimal` and `mpmath` in Python, `java.math.BigDecimal` in Java, etc. diff --git a/benches/cli/bench_cli.sh b/benches/cli/bench_cli.sh index 4b6aeb10..506580ac 100644 --- a/benches/cli/bench_cli.sh +++ b/benches/cli/bench_cli.sh @@ -330,7 +330,7 @@ bench_compare "exp(1)" 50 \ "${PY_MP};print(mp.exp(1))" # NOTE: mpmath diverges from decimo & WolframAlpha at digit ~21 for sin(near-pi). -# See docs/internal_notes.md. Kept here as a reference comparison. +# See docs/internal/internal_notes.md. Kept here as a reference comparison. bench_compare "sin(3.1415926535897932384626433833)" 50 \ "sin(3.1415926535897932384626433833)" \ "s(3.1415926535897932384626433833)" \ diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 799e644d..00000000 --- a/docs/api.md +++ /dev/null @@ -1,81 +0,0 @@ -# API Reference - -## Initialization of BigUInt - -There are three issues associated with the initialization of `BigUInt`: - -1. The embedded list of words is **empty**. In this sense, the coefficient of the `BigUInt` is **uninitialized**. This situation, in most cases, is not desirable because it can lead to bugs and unexpected behavior if users try to perform arithmetic operations on an uninitialized `BigUInt`. However, in some cases, users may want to create an uninitialized `BigUInt` for performance reasons, e.g., when they want to fill the words with their own values later. Therefore, we can allow users to create an uninitialized `BigUInt` by providing a key-word only argument, e.g., `uninitialized=True`. -1. There are **leading zero words** in the embedded list of words, e.g., 000000000_123456789. This situation is not a safety issue, but it can lead to performance issues because it increases the number of words that need to be processed during arithmetic operations. In some cases, users may want to keep these leading zero words for specific applications, e.g., aligning the number of words for two `BigUInt` operations. -1. The value of a word is greater than **999_999_999**. This situation is a safety issue because it violates the invariant that each word should be smaller than a billion. It can lead to bugs and unexpected behavior if users try to perform arithmetic operations on a `BigUInt` with invalid words. - -To make sure that the users construct `BigUInt` safely by default, the default constructor of `BigUInt` will check for these issues so that the `BigUInt` is always non-empty, has no leading zero words, and all words are smaller than a billion. We also allow users, mainly developers, to create unsafe `BigUInt` instances if they want to, but they must explicitly choose to do so by providing a key-word only argument, e.g., `uninitialized=True`, or use specific methods, e.g., `from_list_unsafe()`. - -Note: Mojo now supports keyword-only arguments of the same data type. - -| Method | non-empty | no leading zero words | all words valid | notes | -| ---------------------------------------- | --------- | --------------------- | --------------- | -------------------------------------- | -| `BigUInt(var words: List[UInt32])` | ✓ | ✓ | ✓ | Validating constructor for word lists. | -| `BigUInt(*, uninitialized_capacity=Int)` | ✗ | ? | ? | Length of words list is 0 | -| `BigUInt(*, unsafe_uninit_length=Int)` | ✗ | ? | ? | Length of words list not 0 | -| `BigUInt(*, raw_words: List[UInt32])` | ✓ | ✗ | ✗ | | -| `BigUInt(value: Int)` | ✓ | ✓ | ✓ | | -| `BigUInt(value: Scalar)` | ✓ | ✓ | ✓ | Only unsigned scalars are supported. | - -## Initialization of BigDecimal - -### Python Interoperability: `from_python_decimal()` - -Method Signature is - -```mojo -@staticmethod -fn from_python_decimal(value: PythonObject) raises -> BigDecimal -``` - ---- - -Why use `as_tuple()` instead of direct memory copy (memcpy)? - -Python's `decimal` module (libmpdec) internally uses a base-10^9 representation on 64-bit systems (base 10^4 on 32-bit), which happens to match BigDecimal's internal representation. This raises the question: why not directly memcpy the internal limbs for better performance? - -Direct memcpy is theoretically possible because: - -- On 64-bit systems: libmpdec uses base 10^9, same as BigDecimal -- Both use `uint32_t` limbs for storage -- Direct memory mapping would avoid digit decomposition overhead - -However, this approach is **NOT** used due to significant practical issues: - -1. No mature API for direct access. -1. Using direct memory access would require unsafe pointer manipulation, breaking Decimo's current design principles of using safe Mojo as much as possible. -1. Platform dependency. 32-bit systems use base 10^4 (incompatible with BigDecimal's 10^9). This would require runtime platform detection. -1. Maintenance burden. CPython internal structure (`mpd_t`) may change between versions. -1. Marginal performance gain. `as_tuple()` overhead: O(n) where n = number of digits. Direct memcpy: O(m) where m = number of limbs. Theoretical speedup: ~10x. But how often are users really converting Python decimals to BigDecimal? - ---- - -The `as_tuple()` API returns a tuple of `(sign, digits, exponent)`: - -- `sign`: 0 for positive, 1 for negative -- `digits`: Tuple of individual decimal digits (0-9) -- `exponent`: Power of 10 to multiply by - -`as_tuple()` performs limb → digits decomposition internally. The digits returned are individual base-10 digits, not the base-10^9 limbs stored internally. - -Example: - -```python -# Python -from decimal import Decimal -d = Decimal("123456789012345678") -print(d.as_tuple()) -# DecimalTuple(sign=0, digits=(1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8), exponent=0) -``` - -The `as_tuple()` approach provides: - -- Safe: No unsafe pointer manipulation -- Stable: Public API guaranteed across Python versions -- Portable: Works on all platforms (32/64-bit, CPython/PyPy/etc.) -- Clean: Maintainable, readable code -- Adequate performance: O(n) is acceptable for typical use cases diff --git a/docs/internal_notes.md b/docs/internal/internal_notes.md similarity index 100% rename from docs/internal_notes.md rename to docs/internal/internal_notes.md diff --git a/docs/v0.8.0_benchmark_report.md b/docs/internal/v0.8.0_benchmark_report.md similarity index 100% rename from docs/v0.8.0_benchmark_report.md rename to docs/internal/v0.8.0_benchmark_report.md diff --git a/docs/plans/todo.md b/docs/plans/todo.md new file mode 100644 index 00000000..96ce7c3a --- /dev/null +++ b/docs/plans/todo.md @@ -0,0 +1,23 @@ +# TODO + +This is a to-do list for Decimo. + +- [ ] When Mojo supports **global variables**, implement a type `Context` and a global variable `context` for the `Decimal` class to store the precision of the decimal number and other configurations. This will allow users to set the precision globally, rather than having to set it for each function of the `Decimal` class. +- [ ] When Mojo supports **enum types**, implement an enum type for the rounding mode. +- [ ] Implement a complex number class `BigComplex` that uses `Decimal` for the real and imaginary parts. This will allow users to perform high-precision complex number arithmetic. +- [ ] Implement different methods for adding decimo types with `Int` types so that an implicit conversion is not required. +- [ ] Use debug mode to check for unnecessary zero words before all arithmetic operations. This will help ensure that there are no zero words, which can simplify the speed of checking for zero because we only need to check the first word. +- [ ] Check the `floor_divide()` function of `BigUInt`. Currently, the speed of division between imilar-sized numbers are okay, but the speed of 2n-by-n, 4n-by-n, and 8n-by-n divisions decreases disproportionally. This is likely due to the segmentation of the dividend in the Burnikel-Ziegler algorithm. +- [x] Consider using `Decimal` as the struct name instead of `BigDecimal`, and use `comptime BigDecimal = Decimal` to create an alias for the `Decimal` struct. This just switches the alias and the struct name, but it may be more intuitive to use `Decimal` as the struct name since it is more consistent with Python's `decimal.Decimal`. Moreover, hovering over `Decimal` will show the docstring of the struct, which is more intuitive than hovering over `BigDecimal` to see the docstring of the struct. +- [x] (PR #127, #128, #131) Make all default constructor "safe", which means that the words are checked and normalized to ensure that there are no zero words and that the number is in a valid state. This will help prevent bugs and ensure that all `BigUInt` instances are in a consistent state. Also allow users to create "unsafe" `BigUInt` instances if they want to, but there must be a key-word only argument, e.g., `raw_words`. + +- [x] (#31) The `exp()` function performs slower than Python's counterpart in specific cases. Detailed investigation reveals the bottleneck stems from multiplication operations between decimals with significant fractional components. These operations currently rely on UInt256 arithmetic, which introduces performance overhead. Optimization of the `multiply()` function is required to address these performance bottlenecks, particularly for high-precision decimal multiplication with many digits after the decimal point. Internally, also use `Decimal` instead of `BigDecimal` or `BDec` to be consistent. +- [x] Implement different methods for augmented arithmetic assignments to improve memeory-efficiency and performance. +- [x] Implement a method `remove_leading_zeros` for `BigUInt`, which removes the zero words from the most significant end of the number. +- [x] Use debug mode to check for uninitialized `BigUInt` before all arithmetic operations. This will help ensure that there are no uninitialized `BigUInt`. + +## Roadmap for Decimo + +- [x] Re-implement some methods of `BigUInt` to improve the performance, since it is the building block of `BigDecimal` and `BigInt10`. +- [x] Refine the methods of `BigDecimal` to improve the performance. +- [x] Implement the big **binary** integer type (`BigInt`) using base-2^32 internal representation. The new `BigInt` (alias `BInt`) replaces the previous base-10^9 implementation (now `BigInt10`) and delivers significantly improved performance. \ No newline at end of file diff --git a/docs/readme_unreleased.md b/docs/readme_unreleased.md index 7ab047dc..588d600c 100644 --- a/docs/readme_unreleased.md +++ b/docs/readme_unreleased.md @@ -37,10 +37,10 @@ The core types are[^auxiliary]: - A 128-bit fixed-point decimal implementation (`Dec128`) supporting up to 29 significant digits with a maximum of 28 decimal places[^fixed]. - An arbitrary-precision floating-point implementation (`Float`) backed by the GNU MPFR library, supporting computations with configurable precision and a wide exponent range. Unlike `Decimal`, which uses base-10 arithmetic, `Float` uses binary floating-point internally. This type is optional and requires MPFR/GMP to be installed on the user's system. -| Type | Other names | Information | Internal representation | +| Type | Alternative names | Information | Internal representation | | --------- | -------------------- | ---------------------------------------- | ----------------------- | -| `BInt` | `BigInt` | Equivalent to Python's `int` | Base-2^32 | -| `Decimal` | `BDec`, `BigDecimal` | Equivalent to Python's `decimal.Decimal` | Base-10^9 | +| `BInt` | `BigInt`, `Integer` | Equivalent to Python's `int` | Base-2^32 | +| `Decimal` | `BigDecimal`, `BDec` | Equivalent to Python's `decimal.Decimal` | Base-10^9 | | `Dec128` | `Decimal128` | 128-bit fixed-precision decimal type | Triple 32-bit words | | `Float` | `BigFloat` | Arbitrary-precision floating-point type | MPFR/GMP | @@ -65,7 +65,7 @@ Then, you can install Decimo using any of these methods: 1. In the `mojoproject.toml` file of your project, add the following dependency: ```toml - decimo = "==0.9.0" + decimo = "==0.10.0" ``` Then run `pixi install` to download and install the package. @@ -86,6 +86,7 @@ The following table summarizes the package versions and their corresponding Mojo | `decimojo` | v0.7.0 | ==0.26.1 | pixi | | `decimo` | v0.8.0 | ==0.26.1 | pixi | | `decimo` | v0.9.0 | ==0.26.2 | pixi | +| `decimo` | v0.10.0 | ==0.26.2 | pixi | ## Quick start @@ -97,9 +98,9 @@ from decimo import * This will import the following types or aliases into your namespace: -- `BInt` (alias of `BigInt`): An arbitrary-precision signed integer type, equivalent to Python's `int`. -- `Decimal` or `BDec` (aliases of `BigDecimal`): An arbitrary-precision decimal type, equivalent to Python's `decimal.Decimal`. -- `Dec128` (alias of `Decimal128`): A 128-bit fixed-precision decimal type. +- `BInt` (and its aliases `BigInt`, `Integer`): An arbitrary-precision signed integer type, equivalent to Python's `int`. +- `Decimal` (and its aliases `BigDecimal`, `BDec`): An arbitrary-precision decimal type, equivalent to Python's `decimal.Decimal`. +- `Dec128` (and its alias `Decimal128`): A 128-bit fixed-precision decimal type. - `RoundingMode`: An enumeration for rounding modes. - `ROUND_DOWN`, `ROUND_HALF_UP`, `ROUND_HALF_EVEN`, `ROUND_UP`: Constants for common rounding modes. @@ -112,10 +113,8 @@ from decimo.prelude import * fn main() raises: - var a = BDec("123456789.123456789") # BDec is an alias for BigDecimal - var b = Decimal( - "1234.56789" - ) # Decimal is a Python-like alias for BigDecimal + var a = Decimal("123456789.123456789") + var b = Decimal("1234.56789") # === Basic Arithmetic === # print(a + b) # 123458023.691346789 @@ -322,7 +321,7 @@ Bug reports and feature requests are welcome! If you encounter issues, please [f decimo/ ├── src/ # All source code │ ├── decimo/ # Core library (mojo package) -│ │ ├── bigdecimal/ # Arbitrary-precision decimal (Decimal/BDec) +│ │ ├── bigdecimal/ # Arbitrary-precision decimal (Decimal) │ │ ├── bigint/ # Arbitrary-precision signed integer (BInt) │ │ ├── bigint10/ # Base-10 signed integer (BigInt10) │ │ ├── biguint/ # Base-10 unsigned integer (BigUInt) @@ -333,7 +332,7 @@ decimo/ │ └── calculator/ # Calculator engine (mojo package) │ ├── tokenizer.mojo # Lexer: expression → tokens │ ├── parser.mojo # Shunting-yard: infix → RPN -│ └── evaluator.mojo # RPN evaluator using BigDecimal +│ └── evaluator.mojo # RPN evaluator using Decimal ├── tests/ # Unit tests (one subfolder per module) │ ├── bigdecimal/ │ ├── bigint/ @@ -367,7 +366,7 @@ If you find Decimo useful, consider listing it in your citations. year = {2026}, title = {Decimo: An arbitrary-precision integer and decimal library for Mojo}, url = {https://github.com/forfudan/decimo}, - version = {0.9.0}, + version = {0.10.0}, note = {Computer Software} } ``` @@ -382,4 +381,4 @@ The `BigFloat` type optionally uses the [GNU MPFR Library](https://www.mpfr.org/ [^bigint]: The `BigInt` implementation uses a base-2^32 representation with a little-endian format, where the least significant word is stored at index 0. Each word is a `UInt32`, allowing for efficient storage and arithmetic operations on large integers. This design choice optimizes performance for binary computations while still supporting arbitrary precision. [^auxiliary]: The auxiliary types include a base-10 arbitrary-precision signed integer type (`BigInt10`) and a base-10 arbitrary-precision unsigned integer type (`BigUInt`) supporting unlimited digits[^bigint10]. `BigUInt` is used as the internal representation for `BigInt10` and `Decimal`. [^bigint10]: The BigInt10 implementation uses a base-10 representation for users (maintaining decimal semantics), while internally using an optimized base-10^9 storage system for efficient calculations. This approach balances human-readable decimal operations with high-performance computing. It provides both floor division (round toward negative infinity) and truncate division (round toward zero) semantics, enabling precise handling of division operations with correct mathematical behavior regardless of operand signs. -[^arbitrary]: Built on top of our completed BigInt10 implementation, BigDecimal will support arbitrary precision for both the integer and fractional parts, similar to `decimal` and `mpmath` in Python, `java.math.BigDecimal` in Java, etc. +[^arbitrary]: Built on top of our completed BigInt10 implementation, Decimal supports arbitrary precision for both the integer and fractional parts, similar to `decimal` and `mpmath` in Python, `java.math.BigDecimal` in Java, etc. diff --git a/docs/readme_zht.md b/docs/readme_zht.md index 06639e9c..4b8a77ea 100644 --- a/docs/readme_zht.md +++ b/docs/readme_zht.md @@ -28,7 +28,7 @@ Decimo 爲 Mojo 提供任意精度整數和小數運算庫,爲金融建模、 | 類型 | 別名 | 信息 | 內部表示 | | --------- | -------------------- | ---------------------------------- | ------------ | | `BInt` | `BigInt` | 等價於 Python 的 `int` | Base-2^32 | -| `Decimal` | `BDec`, `BigDecimal` | 等價於 Python 的 `decimal.Decimal` | Base-10^9 | +| `Decimal` | `BigDecimal`, `BDec` | 等價於 Python 的 `decimal.Decimal` | Base-10^9 | | `Dec128` | `Decimal128` | 128 位定點精度小數類型 | 三個 32 位字 | 輔助類型包括基於 10 進制的任意精度有符號整數類型 (`BigInt10`) 和任意精度無符號整數類型 (`BigUInt`),支持無限位數[^bigint10]。`BigUInt` 是 `BigInt10` 和 `Decimal` 的內部表示。 @@ -92,24 +92,24 @@ from decimo import * 這將導入以下類型或別名到您的命名空間: - `BInt`(`BigInt` 的別名):任意精度有符號整數類型,等價於 Python 的 `int`。 -- `Decimal` 或 `BDec`(`BigDecimal` 的別名):任意精度小數類型,等價於 Python 的 `decimal.Decimal`。 +- `Decimal`(也可用 `BigDecimal` 或 `BDec`):任意精度小數類型,等價於 Python 的 `decimal.Decimal`。 - `Dec128`(`Decimal128` 的別名):128 位定點精度小數類型。 - `RoundingMode`:捨入模式的枚舉。 - `ROUND_DOWN`、`ROUND_HALF_UP`、`ROUND_HALF_EVEN`、`ROUND_UP`:常用捨入模式的常量。 --- -以下是一些展示 `BigDecimal` 類型(別名:`BDec` 和 `Decimal`)任意精度特性的例子。對於某些數學運算,默認精度(有效數字位數)設為 `28`。您可以通過向函數傳遞 `precision` 參數來更改精度。當 Mojo 支持全局變量時,此默認精度將可以全局配置。 +以下是一些展示 `Decimal` 類型任意精度特性的例子。對於某些數學運算,默認精度(有效數字位數)設為 `28`。您可以通過向函數傳遞 `precision` 參數來更改精度。當 Mojo 支持全局變量時,此默認精度將可以全局配置。 ```mojo from decimo.prelude import * fn main() raises: - var a = BDec("123456789.123456789") # BDec 是 BigDecimal 的別名 + var a = Decimal("123456789.123456789") var b = Decimal( "1234.56789" - ) # Decimal 是類似 Python 的 BigDecimal 別名 + ) # === 基本算術 === # print(a + b) # 123458023.691346789 @@ -339,4 +339,4 @@ fn main() raises: [^fixed]: `Decimal128` 類型可以表示最多 29 位有效數字,小數點後最多 28 位數字的值。當數值超過最大可表示值(`2^96 - 1`)時,Decimo 會拋出錯誤或將數值捨入以符合這些約束。例如,`8.8888888888888888888888888888`(總共 29 個 8,小數點後 28 位)的有效數字超過了最大可表示值(`2^96 - 1`),會自動捨入爲 `8.888888888888888888888888889`(總共 28 個 8,小數點後 27 位)。Decimo 的 `Decimal128` 類型類似於 `System.Decimal`(C#/.NET)、Rust 中的 `rust_decimal`、SQL Server 中的 `DECIMAL/NUMERIC` 等。 [^bigint]: `BigInt` 使用 base-2^32 表示,採用小端格式,最低有效字存儲在索引 0。每個字是一個 `UInt32`,允許對大整數進行高效存儲和算術運算。這種設計優化了二進制計算的性能,同時支持任意精度。 [^bigint10]: BigInt10 使用基於 10 的表示(保持十進制語義),而內部使用優化的基於 10^9 的存儲系統進行高效計算。這種方法在人類可讀的十進制操作與高性能計算之間取得平衡。它提供向下整除(向負無窮舍入)和截斷除法(向零舍入)語義,無論操作數符號如何,都能確保除法操作具有正確的數學行爲。 -[^arbitrary]: 建立在已完成的 BigInt10 實現之上,BigDecimal 支持整數和小數部分的任意精度,類似於 Python 中的 `decimal` 和 `mpmath`、Java 中的 `java.math.BigDecimal` 等。 +[^arbitrary]: 建立在已完成的 BigInt10 實現之上,Decimal 支持整數和小數部分的任意精度,類似於 Python 中的 `decimal` 和 `mpmath`、Java 中的 `java.math.BigDecimal` 等。 diff --git a/docs/todo.md b/docs/todo.md index b9da8ae2..f7501ded 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -2,17 +2,17 @@ This is a to-do list for Decimo. -- [ ] When Mojo supports **global variables**, implement a type `Context` and a global variable `context` for the `BigDecimal` class to store the precision of the decimal number and other configurations. This will allow users to set the precision globally, rather than having to set it for each function of the `BigDecimal` class. +- [ ] When Mojo supports **global variables**, implement a type `Context` and a global variable `context` for the `Decimal` class to store the precision of the decimal number and other configurations. This will allow users to set the precision globally, rather than having to set it for each function of the `Decimal` class. - [ ] When Mojo supports **enum types**, implement an enum type for the rounding mode. -- [ ] Implement a complex number class `BigComplex` that uses `BigDecimal` for the real and imaginary parts. This will allow users to perform high-precision complex number arithmetic. +- [ ] Implement a complex number class `BigComplex` that uses `Decimal` for the real and imaginary parts. This will allow users to perform high-precision complex number arithmetic. - [ ] Implement different methods for adding decimo types with `Int` types so that an implicit conversion is not required. - [ ] Use debug mode to check for unnecessary zero words before all arithmetic operations. This will help ensure that there are no zero words, which can simplify the speed of checking for zero because we only need to check the first word. -- [ ] Check the `floor_divide()` function of `BigUInt`. Currently, the speed of division between imilar-sized numbers are okay, but the speed of 2n-by-n, 4n-by-n, and 8n-by-n divisions decreases unproportionally. This is likely due to the segmentation of the dividend in the Burnikel-Ziegler algorithm. -- [ ] Consider using `Decimal` as the struct name instead of `BigDecimal`, and use `comptime BigDecimal = Decimal` to create an alias for the `Decimal` struct. This just switches the alias and the struct name, but it may be more intuitive to use `Decimal` as the struct name since it is more consistent with Python's `decimal.Decimal`. Moreover, hovering over `Decimal` will show the docstring of the struct, which is more intuitive than hovering over `BigDecimal` to see the docstring of the struct. +- [ ] Check the `floor_divide()` function of `BigUInt`. Currently, the speed of division between similar-sized numbers are okay, but the speed of 2n-by-n, 4n-by-n, and 8n-by-n divisions decreases disproportionally. This is likely due to the segmentation of the dividend in the Burnikel-Ziegler algorithm. +- [x] Consider using `Decimal` as the struct name instead of `BigDecimal`, and use `comptime BigDecimal = Decimal` to create an alias for the `Decimal` struct. This just switches the alias and the struct name, but it may be more intuitive to use `Decimal` as the struct name since it is more consistent with Python's `decimal.Decimal`. Moreover, hovering over `Decimal` will show the docstring of the struct, which is more intuitive than hovering over `BigDecimal` to see the docstring of the struct. - [x] (PR #127, #128, #131) Make all default constructor "safe", which means that the words are checked and normalized to ensure that there are no zero words and that the number is in a valid state. This will help prevent bugs and ensure that all `BigUInt` instances are in a consistent state. Also allow users to create "unsafe" `BigUInt` instances if they want to, but there must be a key-word only argument, e.g., `raw_words`. - [x] (#31) The `exp()` function performs slower than Python's counterpart in specific cases. Detailed investigation reveals the bottleneck stems from multiplication operations between decimals with significant fractional components. These operations currently rely on UInt256 arithmetic, which introduces performance overhead. Optimization of the `multiply()` function is required to address these performance bottlenecks, particularly for high-precision decimal multiplication with many digits after the decimal point. Internally, also use `Decimal` instead of `BigDecimal` or `BDec` to be consistent. -- [x] Implement different methods for augmented arithmetic assignments to improve memeory-efficiency and performance. +- [x] Implement different methods for augmented arithmetic assignments to improve memory-efficiency and performance. - [x] Implement a method `remove_leading_zeros` for `BigUInt`, which removes the zero words from the most significant end of the number. - [x] Use debug mode to check for uninitialized `BigUInt` before all arithmetic operations. This will help ensure that there are no uninitialized `BigUInt`. @@ -20,4 +20,4 @@ This is a to-do list for Decimo. - [x] Re-implement some methods of `BigUInt` to improve the performance, since it is the building block of `BigDecimal` and `BigInt10`. - [x] Refine the methods of `BigDecimal` to improve the performance. -- [x] Implement the big **binary** integer type (`BigInt`) using base-2^32 internal representation. The new `BigInt` (alias `BInt`) replaces the previous base-10^9 implementation (now `BigInt10`) and delivers significantly improved performance. \ No newline at end of file +- [x] Implement the big **binary** integer type (`BigInt`) using base-2^32 internal representation. The new `BigInt` (alias `BInt`) replaces the previous base-10^9 implementation (now `BigInt10`) and delivers significantly improved performance. diff --git a/docs/user_manual.md b/docs/user_manual.md index 9ddc92cf..98c73714 100644 --- a/docs/user_manual.md +++ b/docs/user_manual.md @@ -9,93 +9,40 @@ your Mojo file: from decimo.prelude import * ``` +- [Installation](#installation) +- [Quick Start](#quick-start) - [Part I — BigInt (`BInt`)](#part-i--bigint-bint) - [Overview](#overview) - [Construction](#construction) - - [From zero](#from-zero) - - [From `Int`](#from-int) - - [From `String`](#from-string) - - [From `Scalar` (any integral SIMD type)](#from-scalar-any-integral-simd-type) - - [Summary of constructors](#summary-of-constructors) - [Arithmetic Operations](#arithmetic-operations) - - [Binary operators](#binary-operators) - - [Unary operators](#unary-operators) - - [In-place operators](#in-place-operators) - [Division Semantics](#division-semantics) - [Comparison](#comparison) - [Bitwise Operations](#bitwise-operations) - [Shift Operations](#shift-operations) - [Mathematical Functions](#mathematical-functions) - - [Exponentiation](#exponentiation) - - [Integer square root](#integer-square-root) - [Number Theory](#number-theory) - - [GCD — Greatest Common Divisor](#gcd--greatest-common-divisor) - - [LCM — Least Common Multiple](#lcm--least-common-multiple) - - [Extended GCD](#extended-gcd) - - [Modular Exponentiation](#modular-exponentiation) - - [Modular Inverse](#modular-inverse) - [Conversion and Output](#conversion-and-output) - - [String conversions](#string-conversions) - - [Numeric conversions](#numeric-conversions) - [Query Methods](#query-methods) - [Constants and Factory Methods](#constants-and-factory-methods) -- [Part II — BigDecimal (`Decimal`)](#part-ii--bigdecimal-decimal) - - [Overview](#overview-1) +- [Part II — Decimal](#part-ii--decimal) + - [Overview — Decimal](#overview--decimal) - [How Precision Works](#how-precision-works) - - [Construction](#construction-1) - - [From zero](#from-zero-1) - - [From `Int`](#from-int-1) - - [From `String`](#from-string-1) - - [From integral scalars](#from-integral-scalars) - - [From floating-point — `from_float()`](#from-floating-point--from_float) - - [From Python — `from_python_decimal()`](#from-python--from_python_decimal) - - [Summary of constructors](#summary-of-constructors-1) - - [Arithmetic Operations](#arithmetic-operations-1) + - [Construction — Decimal](#construction--decimal) + - [Decimal Arithmetic](#decimal-arithmetic) - [Division Methods](#division-methods) - - [`true_divide()` — recommended for decimal division](#true_divide--recommended-for-decimal-division) - - [Operator `/` — true division with default precision](#operator---true-division-with-default-precision) - - [Operator `//` — truncated (integer) division](#operator---truncated-integer-division) - - [Comparison](#comparison-1) + - [Decimal Comparison](#decimal-comparison) - [Rounding and Formatting](#rounding-and-formatting) - - [`round()` — round to decimal places](#round--round-to-decimal-places) - - [`quantize()` — match scale of another decimal](#quantize--match-scale-of-another-decimal) - - [`normalize()` — remove trailing zeros](#normalize--remove-trailing-zeros) - - [`__ceil__`, `__floor__`, `__trunc__`](#__ceil__-__floor__-__trunc__) - [RoundingMode](#roundingmode) - [Mathematical Functions — Roots and Powers](#mathematical-functions--roots-and-powers) - - [Square root](#square-root) - - [Cube root](#cube-root) - - [Nth root](#nth-root) - - [Power / exponentiation](#power--exponentiation) - [Mathematical Functions — Exponential and Logarithmic](#mathematical-functions--exponential-and-logarithmic) - - [Exponential (e^x)](#exponential-ex) - - [Natural logarithm](#natural-logarithm) - - [Logarithm with arbitrary base](#logarithm-with-arbitrary-base) - - [Base-10 logarithm](#base-10-logarithm) - [Mathematical Functions — Trigonometric](#mathematical-functions--trigonometric) - - [Basic functions](#basic-functions) - - [Reciprocal functions](#reciprocal-functions) - - [Inverse functions](#inverse-functions) - [Mathematical Constants](#mathematical-constants) - - [π (pi)](#π-pi) - - [e (Euler's number)](#e-eulers-number) - - [Conversion and Output](#conversion-and-output-1) - - [String output](#string-output) - - [`repr()`](#repr) - - [Numeric conversions](#numeric-conversions-1) - - [Query Methods](#query-methods-1) - - [`as_tuple()` — Python-compatible decomposition](#as_tuple--python-compatible-decomposition) - - [Other methods](#other-methods) + - [Decimal Conversion and Output](#decimal-conversion-and-output) + - [Decimal Query Methods](#decimal-query-methods) - [Python Interoperability](#python-interoperability) - - [From Python](#from-python) - - [Matching Python's API](#matching-pythons-api) - [Appendix A — Import Paths](#appendix-a--import-paths) - [Appendix B — Traits Implemented](#appendix-b--traits-implemented) - - [BigInt](#bigint) - - [BigDecimal](#bigdecimal) - [Appendix C — Complete API Tables](#appendix-c--complete-api-tables) - - [BigInt — All Operators](#bigint--all-operators) - - [BigDecimal — Mathematical Functions](#bigdecimal--mathematical-functions) ## Installation @@ -114,7 +61,7 @@ pixi add decimo Or add it manually to `pixi.toml`: ```toml -decimo = "==0.9.0" +decimo = "==0.10.0" ``` Then run `pixi install`. @@ -141,29 +88,29 @@ fn main() raises: print(Decimal.pi(precision=1000)) # 1000 digits of π ``` -# Part I — BigInt (`BInt`) +## Part I — BigInt (`BInt`) -## Overview +### Overview -`BigInt` (alias `BInt`) is an arbitrary-precision signed integer type — the Mojo-native equivalent of Python's `int`. It supports unlimited-precision integer arithmetic, bitwise operations, and number-theoretic functions. +`BigInt` (aliases `BInt`, `Integer`) is an arbitrary-precision signed integer type — the Mojo-native equivalent of Python's `int`. It supports unlimited-precision integer arithmetic, bitwise operations, and number-theoretic functions. | Property | Value | | ----------------- | ---------------------------- | | Full name | `BigInt` | -| Short alias | `BInt` | +| Aliases | `BInt`, `Integer` | | Internal base | 2^32 (binary representation) | | Word type | `UInt32` (little-endian) | | Python equivalent | `int` | -## Construction +### Construction -### From zero +#### From zero ```mojo var x = BInt() # 0 ``` -### From `Int` +#### From `Int` ```mojo var x = BInt(42) @@ -173,7 +120,7 @@ var z: BInt = 42 # Implicit conversion from Int The constructor is marked `@implicit`, so Mojo can automatically convert `Int` to `BInt` where expected. -### From `String` +#### From `String` ```mojo var a = BInt("12345678901234567890") # Basic decimal string @@ -186,7 +133,7 @@ var f = BInt("1991_10,18") # Mixed separators (= 19911018) > **Note:** The string must represent an integer. `BInt("1.5")` raises an error. Scientific notation like `"1.23e5"` is accepted only if the result is an integer. -### From `Scalar` (any integral SIMD type) +#### From `Scalar` (any integral SIMD type) ```mojo var x = BInt(UInt32(42)) @@ -196,22 +143,31 @@ var z: BInt = UInt32(99) # Implicit conversion Accepts any integral scalar type (`Int8` through `Int256`, `UInt8` through `UInt256`, etc.). -### Summary of constructors +#### Summary of constructors -| Constructor | Description | -| --------------------------- | ------------------------------------- | -| `BInt()` | Zero | -| `BInt(value: Int)` | From `Int` (implicit) | -| `BInt(value: String)` | From decimal string (raises) | -| `BInt(value: Scalar)` | From integral scalar (implicit) | -| `BInt.from_uint64(value)` | From `UInt64` | -| `BInt.from_uint128(value)` | From `UInt128` | -| `BInt.from_string(value)` | Explicit factory from string (raises) | -| `BInt.from_bigint10(value)` | Convert from `BigInt10` | +| Constructor | Description | +| ---------------------------------- | ------------------------------------- | +| `BInt()` | Zero | +| `BInt(value: Int)` | From `Int` (implicit) | +| `BInt(value: String)` | From decimal string (raises) | +| `BInt(value: Scalar)` | From integral scalar (implicit) | +| `BInt.from_int(value)` | Explicit factory from `Int` | +| `BInt.from_integral_scalar(value)` | From any integral scalar type | +| `BInt.from_string(value)` | Explicit factory from string (raises) | +| `BInt.from_bigint10(value)` | Convert from `BigInt10` | -## Arithmetic Operations +#### Unsafe constructors -### Binary operators +These constructors skip validation for performance-sensitive code. The caller must ensure the data is valid. + +| Constructor | Description | +| ----------------------------------------- | ----------------------------------------- | +| `BInt(uninitialized_capacity=n)` | Empty words list with reserved capacity | +| `BInt(raw_words=List[UInt32], sign=Bool)` | From raw words, no leading-zero stripping | + +### Arithmetic Operations + +#### Binary operators | Expression | Description | Raises? | | ------------- | --------------------------------- | ------------------ | @@ -223,7 +179,7 @@ Accepts any integral scalar type (`Int8` through `Int256`, `UInt8` through `UInt | `divmod(a,b)` | Floor quotient and remainder | Yes (zero div) | | `a ** b` | Exponentiation | Yes (negative exp) | -### Unary operators +#### Unary operators | Expression | Description | | ---------- | ------------------------------ | @@ -233,7 +189,7 @@ Accepts any integral scalar type (`Int8` through `Int256`, `UInt8` through `UInt | `bool(a)` | `True` if nonzero | | `~a` | Bitwise NOT (two's complement) | -### In-place operators +#### In-place operators `+=`, `-=`, `*=`, `//=`, `%=`, `<<=`, `>>=` are all supported and perform true in-place mutation to reduce memory allocation. @@ -248,7 +204,7 @@ print(a % b) # 9615 print(BInt(2) ** 10) # 1024 ``` -## Division Semantics +### Division Semantics BigInt supports two division conventions: @@ -272,7 +228,7 @@ print(a.truncate_divide(b)) # -3 print(a.truncate_modulo(b)) # 1 ``` -## Comparison +### Comparison All six comparison operators (`==`, `!=`, `>`, `>=`, `<`, `<=`) are supported. Each accepts both `BInt` and `Int` as the right operand. @@ -290,7 +246,7 @@ a.compare(b) # Returns Int8: 1, 0, or -1 a.compare_magnitudes(b) # Compares |a| vs |b| ``` -## Bitwise Operations +### Bitwise Operations All bitwise operations follow **Python's two's complement semantics** for negative numbers. @@ -315,7 +271,7 @@ print(~a) # -13 print(BInt(-1) & BInt(255)) # 255 ``` -## Shift Operations +### Shift Operations | Operator | Description | | -------- | ----------------------------------- | @@ -328,9 +284,9 @@ print(x << 100) # 1267650600228229401496703205376 (= 2^100) print(BInt(1024) >> 5) # 32 ``` -## Mathematical Functions +### Mathematical Functions -### Exponentiation +#### Exponentiation ```mojo print(BInt(2).power(100)) # 2^100 @@ -339,7 +295,7 @@ print(BInt(2) ** 100) # Same via ** operator Both `power(exponent: Int)` and `power(exponent: BigInt)` are supported. The exponent must be non-negative. -### Integer square root +#### Integer square root ```mojo var x = BInt("100000000000000000000") @@ -349,7 +305,7 @@ print(x.isqrt()) # Same as sqrt() Raises if the value is negative. -## Number Theory +### Number Theory All number-theory operations are available as both **instance methods** and **free functions**: @@ -357,7 +313,7 @@ All number-theory operations are available as both **instance methods** and **fr from decimo import BInt, gcd, lcm, extended_gcd, mod_pow, mod_inverse ``` -### GCD — Greatest Common Divisor +#### GCD — Greatest Common Divisor ```mojo var a = BInt(48) @@ -366,14 +322,14 @@ print(a.gcd(b)) # 6 print(gcd(a, b)) # 6 (free function) ``` -### LCM — Least Common Multiple +#### LCM — Least Common Multiple ```mojo print(BInt(12).lcm(BInt(18))) # 36 print(lcm(BInt(12), BInt(18))) # 36 ``` -### Extended GCD +#### Extended GCD Returns `(g, x, y)` such that `a*x + b*y = g`: @@ -382,7 +338,7 @@ var result = BInt(35).extended_gcd(BInt(15)) # result = (5, 1, -2) — since 35×1 + 15×(−2) = 5 ``` -### Modular Exponentiation +#### Modular Exponentiation Computes $(base^{exp}) \mod m$ efficiently without computing the full power: @@ -391,7 +347,7 @@ print(BInt(2).mod_pow(BInt(100), BInt(1000000007))) print(mod_pow(BInt(2), BInt(100), BInt(1000000007))) # free function ``` -### Modular Inverse +#### Modular Inverse Finds $x$ such that $(a \cdot x) \equiv 1 \pmod{m}$: @@ -400,9 +356,9 @@ print(BInt(3).mod_inverse(BInt(7))) # 5 (since 3×5 = 15 ≡ 1 mod 7) print(mod_inverse(BInt(3), BInt(7))) # 5 ``` -## Conversion and Output +### Conversion and Output -### String conversions +#### String conversions | Method | Example output | | ---------------------------------- | ------------------- | @@ -414,7 +370,7 @@ print(mod_inverse(BInt(3), BInt(7))) # 5 | `x.to_binary_string()` | `"0b110101"` | | `x.to_string(line_width=20)` | Multi-line output | -### Numeric conversions +#### Numeric conversions | Method | Description | | ----------------- | ------------------------------------------------- | @@ -428,7 +384,7 @@ print(x.to_string_with_separators()) # 123_456_789_012_345_678_901_234_567_890 print(x.to_hex_string()) # 0x... ``` -## Query Methods +### Query Methods | Method | Return | Description | | ---------------------- | ------ | ------------------------------------- | @@ -449,7 +405,7 @@ print(x.number_of_digits()) # 2 print(x.is_positive()) # True ``` -## Constants and Factory Methods +### Constants and Factory Methods | Method / Constant | Value | | ---------------------- | ----- | @@ -460,27 +416,23 @@ print(x.is_positive()) # True | `BigInt.ONE` | 1 | | `BigInt.BITS_PER_WORD` | 32 | -# Part II — BigDecimal (`Decimal`) +## Part II — Decimal -## Overview +### Overview — Decimal -`BigDecimal` (aliases `Decimal`, `BDec`) is an arbitrary-precision decimal type — the Mojo-native equivalent of Python's `decimal.Decimal`. It can represent numbers with unlimited digits and decimal places, making it suitable for financial modeling, scientific computing, and applications where floating-point errors are unacceptable. +`Decimal` is an arbitrary-precision decimal type — the Mojo-native equivalent of Python's `decimal.Decimal`. It can represent numbers with unlimited digits and decimal places, making it suitable for financial modeling, scientific computing, and applications where floating-point errors are unacceptable. | Property | Value | | ----------------- | --------------------------------------- | -| Full name | `BigDecimal` | -| Aliases | `Decimal`, `BDec` | +| Name | `Decimal` | +| Aliases | `BigDecimal`, `BDec` | | Internal base | Base-10^9 (each word stores ≤ 9 digits) | | Default precision | 28 significant digits | | Python equivalent | `decimal.Decimal` | -`Decimal`, `BDec`, and `BigDecimal` are all the same type — use whichever you prefer: +`Decimal`, `BigDecimal`, and `BDec` are all the same type. We recommend `Decimal` for consistency with Python's `decimal.Decimal`. -- `Decimal` — familiar to Python users. -- `BDec` — short and concise. -- `BigDecimal` — full explicit name. - -## How Precision Works +### How Precision Works - **Addition, subtraction, multiplication** are always **exact** — no precision loss. - **Division** and **mathematical functions** (`sqrt`, `ln`, `exp`, etc.) accept an optional `precision` parameter specifying the number of **significant digits** in the result. @@ -495,31 +447,43 @@ print(x.sqrt(precision=1000)) # 1000 significant digits > **Note:** The default precision of 28 will be configurable globally in a future version when Mojo supports global variables. -## Construction +### Construction — Decimal + +Decimal can be constructed from various types of input using the `Decimal()` constructor or factory methods. Among these, the most common way is from a **string representation** of the decimal number, which is the most accurate way to create a Decimal without any precision loss. -### From zero +#### From `String` (Decimal) + +It is highly recommended to construct `Decimal` from a string. Please consider using this method whenever possible. ```mojo -var x = Decimal() # 0 +var a = Decimal("123456789.123456789") # Basic decimal string +var b = Decimal("1.23E+10") # Scientific notation +var c = Decimal("-0.000001") # Negative +var d = Decimal("1_000_000.50") # Separator support ``` -### From `Int` +#### From zero (Decimal) ```mojo -var x = Decimal(42) -var y: Decimal = 100 # Implicit conversion +var x = Decimal("0") # Explicitly from string +var y = Decimal() # Default constructor creates zero, same as Decimal("0") ``` -### From `String` +#### From `Int` (Decimal) + +Although you can construct a `Decimal` from an `Int` directly, it is still risky if the `Int` is so large that it exceeds the maximum value of `Int` (which is 2^63-1). ```mojo -var a = Decimal("123456789.123456789") # Plain notation -var b = BDec("1.23E+10") # Scientific notation -var c = Decimal("-0.000001") # Negative -var d = Decimal("1_000_000.50") # Separator support +# These work +var x = Decimal(42) # From Int +var y: Decimal = 100 # Implicit conversion (IntLiteral -> Int -> Decimal) + +# This is dangerous! +var z: Decimal = 9223372036854775808 # 2^63, exceeds Int range! +print(z) # Prints -9223372036854775808, overflowed! ``` -### From integral scalars +#### From integral scalars ```mojo var x = Decimal(Int64(123456789)) @@ -528,16 +492,24 @@ var y = Decimal(UInt128(99999999999999)) Works with all integral SIMD types. **Floating-point scalars are rejected at compile time** — use `from_float()` instead. -### From floating-point — `from_float()` +#### From floating-point — `from_float()` + +When constructing a `Decimal` from a floating-point number, the number is first converted to its string representation and then parsed as a `Decimal`. + +Note that not all decimal numbers can be represented exactly as binary floating-point. You may lose precision without awareness. + +To make the conversion from float to `Decimal` more explicit so that you are aware of the potential precision issues, the `Decimal()` constructor does not accept floating-point numbers directly. Instead, to create a `Decimal` from a float, you must use the `from_float()` factory method. + +Consider never using `Decimal.from_float()` in performance-sensitive code, but use string construction instead. ```mojo var x = Decimal.from_float(3.14159) -var y = BDec.from_float(Float64(2.71828)) +var y = Decimal.from_float(Float64(2.71828)) ``` -> **Why no implicit Float64 constructor?** Implicit conversion from float would silently introduce floating-point artifacts (e.g., `0.1` → `0.1000000000000000055...`). The `from_float()` method makes this explicit. +#### From Python — `from_python_decimal()` -### From Python — `from_python_decimal()` +You can always safely construct a `Decimal` from a Python `decimal.Decimal` using the `from_python_decimal()` method without worrying about precision loss. ```mojo from python import Python @@ -545,23 +517,38 @@ from python import Python var decimal = Python.import_module("decimal") var py_dec = decimal.Decimal("123.456") -var a = BigDecimal.from_python_decimal(py_dec) -var b = BigDecimal(py=py_dec) # Alternative keyword syntax +var a = Decimal.from_python_decimal(py_dec) +var b = Decimal(py=py_dec) # Alternative keyword-only syntax ``` -### Summary of constructors +#### Summary of Decimal constructors | Constructor | Description | | ------------------------------------- | ------------------------------- | | `Decimal()` | Zero | | `Decimal(value: Int)` | From `Int` (implicit) | +| `Decimal(value: UInt)` | From `UInt` (implicit) | | `Decimal(value: String)` | From string (raises) | | `Decimal(value: Scalar)` | From integral scalar (implicit) | +| `Decimal(py=py_obj)` | From Python `Decimal` (raises) | +| `Decimal.from_int(value)` | Explicit factory from `Int` | +| `Decimal.from_uint(value)` | Explicit factory from `UInt` | +| `Decimal.from_integral_scalar(value)` | From any integral scalar type | | `Decimal.from_float(value)` | From floating-point (raises) | +| `Decimal.from_string(value)` | Explicit factory from string | | `Decimal.from_python_decimal(py_obj)` | From Python `Decimal` (raises) | -| `Decimal(coefficient, scale, sign)` | From raw components | -## Arithmetic Operations +#### Unsafe Decimal constructors + +These constructors skip validation for performance-sensitive code. The caller must ensure the data is valid. + +| Constructor | Description | +| --------------------------------------------------------- | -------------------------------------- | +| `Decimal(coefficient: BigUInt, scale: Int, sign: Bool)` | From raw components | +| `Decimal.from_raw_components(words, scale=0, sign=False)` | From raw `List[UInt32]` words (unsafe) | +| `Decimal.from_raw_components(word, scale=0, sign=False)` | From a single `UInt32` word (unsafe) | + +### Decimal Arithmetic Addition, subtraction, and multiplication are always **exact** (no precision loss). @@ -593,11 +580,11 @@ print(a - b) # 123455554.555566789 print(a * b) # 152415787654.32099750190521 ``` -## Division Methods +### Division Methods Division is the primary operation where precision matters. Decimo provides several variants: -### `true_divide()` — recommended for decimal division +#### `true_divide()` — recommended for decimal division ```mojo var a = Decimal("1") @@ -607,20 +594,20 @@ print(a.true_divide(b, precision=50)) # 50 significant digits print(a.true_divide(b, precision=200)) # 200 significant digits ``` -### Operator `/` — true division with default precision +#### Operator `/` — true division with default precision ```mojo var result = a / b # Same as a.true_divide(b, precision=28) ``` -### Operator `//` — truncated (integer) division +#### Operator `//` — truncated (integer) division ```mojo print(Decimal("7") // Decimal("4")) # 1 print(Decimal("-7") // Decimal("4")) # -1 (toward zero) ``` -## Comparison +### Decimal Comparison All six comparison operators are supported: @@ -640,9 +627,9 @@ a.max(b) # Returns the larger value a.min(b) # Returns the smaller value ``` -## Rounding and Formatting +### Rounding and Formatting -### `round()` — round to decimal places +#### `round()` — round to decimal places ```mojo var x = Decimal("123.456") @@ -660,7 +647,7 @@ Also works with `round()` builtin: print(round(Decimal("123.456"), 2)) # 123.46 ``` -### `quantize()` — match scale of another decimal +#### `quantize()` — match scale of another decimal Adjusts the scale (number of decimal places) to match the scale of `exp`. The actual value of `exp` is ignored — only its scale matters. @@ -675,13 +662,13 @@ var price = Decimal("19.999") print(price.quantize(Decimal("0.01"))) # 20.00 ``` -### `normalize()` — remove trailing zeros +#### `normalize()` — remove trailing zeros ```mojo print(Decimal("1.2345000").normalize()) # 1.2345 ``` -### `__ceil__`, `__floor__`, `__trunc__` +#### `__ceil__`, `__floor__`, `__trunc__` ```mojo from math import ceil, floor, trunc @@ -690,7 +677,7 @@ print(floor(Decimal("1.9"))) # 1 print(trunc(Decimal("-1.9"))) # -1 ``` -## RoundingMode +### RoundingMode Seven rounding modes are available: @@ -714,32 +701,32 @@ print(x.round(0, ROUND_CEILING)) # 3 print(x.round(0, ROUND_FLOOR)) # 2 ``` -## Mathematical Functions — Roots and Powers +### Mathematical Functions — Roots and Powers All mathematical functions accept an optional `precision` parameter (default=28). -### Square root +#### Square root ```mojo print(Decimal("2").sqrt()) # 1.414213562373095048801688724 print(Decimal("2").sqrt(precision=100)) # 100 significant digits ``` -### Cube root +#### Cube root ```mojo print(Decimal("27").cbrt()) # 3 print(Decimal("2").cbrt(precision=50)) ``` -### Nth root +#### Nth root ```mojo print(Decimal("256").root(Decimal("8"))) # 2 print(Decimal("100").root(Decimal("3"))) # 4.641588833612778892... ``` -### Power / exponentiation +#### Power / exponentiation ```mojo print(Decimal("2").power(Decimal("10"))) # 1024 @@ -747,16 +734,16 @@ print(Decimal("2").power(Decimal("0.5"), precision=50)) # sqrt(2) to 50 digits print(Decimal("2") ** 10) # 1024 ``` -## Mathematical Functions — Exponential and Logarithmic +### Mathematical Functions — Exponential and Logarithmic -### Exponential (e^x) +#### Exponential (e^x) ```mojo print(Decimal("1").exp()) # e ≈ 2.718281828459045235360287471 print(Decimal("10").exp(precision=50)) # e^10 to 50 digits ``` -### Natural logarithm +#### Natural logarithm ```mojo print(Decimal("10").ln(precision=50)) # ln(10) to 50 digits @@ -772,25 +759,25 @@ var r1 = x1.ln(100, cache) var r2 = x2.ln(100, cache) # Reuses cached ln(2) and ln(1.25) ``` -### Logarithm with arbitrary base +#### Logarithm with arbitrary base ```mojo print(Decimal("100").log(Decimal("10"))) # 2 print(Decimal("8").log(Decimal("2"))) # 3 ``` -### Base-10 logarithm +#### Base-10 logarithm ```mojo print(Decimal("1000").log10()) # 3 (exact for powers of 10) print(Decimal("2").log10(precision=50)) ``` -## Mathematical Functions — Trigonometric +### Mathematical Functions — Trigonometric All trigonometric functions take inputs in **radians** and accept an optional `precision` parameter. -### Basic functions +#### Basic functions ```mojo print(Decimal("0.5").sin(precision=50)) @@ -798,7 +785,7 @@ print(Decimal("0.5").cos(precision=50)) print(Decimal("0.5").tan(precision=50)) ``` -### Reciprocal functions +#### Reciprocal functions ```mojo print(Decimal("1").cot(precision=50)) # cos/sin @@ -806,15 +793,15 @@ print(Decimal("1").csc(precision=50)) # 1/sin print(Decimal("1").sec(precision=50)) # 1/cos ``` -### Inverse functions +#### Inverse functions ```mojo print(Decimal("1").arctan(precision=50)) # π/4 to 50 digits ``` -## Mathematical Constants +### Mathematical Constants -### π (pi) +#### π (pi) Computed using the **Chudnovsky algorithm** with binary splitting: @@ -823,7 +810,7 @@ print(Decimal.pi(precision=100)) # 100 digits of π print(Decimal.pi(precision=1000)) # 1000 digits of π ``` -### e (Euler's number) +#### e (Euler's number) Computed as `exp(1)`: @@ -832,9 +819,9 @@ print(Decimal.e(precision=100)) # 100 digits of e print(Decimal.e(precision=1000)) # 1000 digits of e ``` -## Conversion and Output +### Decimal Conversion and Output -### String output +#### String output The `to_string()` method provides flexible formatting: @@ -858,20 +845,20 @@ x.to_eng_string() # to_string(engineering=True) x.to_string_with_separators("_") # to_string(delimiter="_") ``` -### `repr()` +#### `repr()` ```mojo print(repr(Decimal("123.45"))) # BigDecimal("123.45") ``` -### Numeric conversions +#### Decimal numeric conversions ```mojo var n = Int(Decimal("123.99")) # 123 (truncates) var f = Float64(Decimal("3.14")) # 3.14 (may lose precision) ``` -## Query Methods +### Decimal Query Methods | Method | Return | Description | | ---------------------- | ------ | ----------------------------------------- | @@ -885,14 +872,14 @@ var f = Float64(Decimal("3.14")) # 3.14 (may lose precision) | `x.adjusted()` | `Int` | Adjusted exponent (≈ floor(log10(\|x\|))) | | `x.same_quantum(y)` | `Bool` | `True` if both have same scale | -### `as_tuple()` — Python-compatible decomposition +#### `as_tuple()` — Python-compatible decomposition ```mojo var sign, digits, exp = Decimal("7.25").as_tuple() # sign=False, digits=[7, 2, 5], exp=-2 ``` -### Other methods +#### Other methods ```mojo x.copy_abs() # Copy with positive sign @@ -902,9 +889,9 @@ x.fma(a, b) # Fused multiply-add: x*a+b (exact) x.scaleb(n) # Multiply by 10^n (O(1), adjusts scale only) ``` -## Python Interoperability +### Python Interoperability -### From Python +#### From Python ```mojo from python import Python @@ -912,12 +899,12 @@ from python import Python var decimal = Python.import_module("decimal") var py_val = decimal.Decimal("3.14159265358979323846") -var d = BigDecimal.from_python_decimal(py_val) +var d = Decimal.from_python_decimal(py_val) # Or: -var d = BigDecimal(py=py_val) +var d = Decimal(py=py_val) ``` -### Matching Python's API +#### Matching Python's API Many methods mirror Python's `decimal.Decimal` API: @@ -933,26 +920,27 @@ Many methods mirror Python's `decimal.Decimal` API: | `d.adjusted()` | `x.adjusted()` | | `d.same_quantum(other)` | `x.same_quantum(other)` | -## Appendix A — Import Paths +### Appendix A — Import Paths ```mojo # Recommended: import everything commonly needed from decimo.prelude import * -# Brings in: BInt, Decimal, BDec, Dec128, RoundingMode, -# ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_UP, ROUND_CEILING, ROUND_FLOOR +# Brings in: BigInt, BInt, Integer, Decimal, BigDecimal, BDec, Dec128, +# RoundingMode, ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, +# ROUND_UP, ROUND_CEILING, ROUND_FLOOR # Or import specific types -from decimo import BInt, BigInt -from decimo import Decimal, BDec, BigDecimal +from decimo import BInt, BigInt, Integer +from decimo import Decimal # also available as BigDecimal or BDec from decimo import RoundingMode # Number-theory free functions from decimo import gcd, lcm, extended_gcd, mod_pow, mod_inverse ``` -## Appendix B — Traits Implemented +### Appendix B — Traits Implemented -### BigInt +#### BigInt | Trait | What it enables | | ------------------ | -------------------------------- | @@ -966,7 +954,7 @@ from decimo import gcd, lcm, extended_gcd, mod_pow, mod_inverse | `Stringable` | `String(x)`, `str(x)` | | `Writable` | `print(x)`, writer protocol | -### BigDecimal +#### Decimal | Trait | What it enables | | ------------------ | -------------------------------- | @@ -981,9 +969,9 @@ from decimo import gcd, lcm, extended_gcd, mod_pow, mod_inverse | `Stringable` | `String(x)`, `str(x)` | | `Writable` | `print(x)`, writer protocol | -## Appendix C — Complete API Tables +### Appendix C — Complete API Tables -### BigInt — All Operators +#### BigInt — All Operators | Operator / Method | Accepts | Raises? | Description | | ------------------- | -------------------- | ------- | ---------------------- | @@ -1008,7 +996,7 @@ from decimo import gcd, lcm, extended_gcd, mod_pow, mod_inverse | `a.mod_pow(e, m)` | `BInt`/`Int`, `BInt` | Yes | Modular exponentiation | | `a.mod_inverse(m)` | `BInt` | Yes | Modular inverse | -### BigDecimal — Mathematical Functions +#### Decimal — Mathematical Functions | Function | Signature | Default | Description | | -------- | ---------------------------- | ------- | -------------------- | diff --git a/docs/examples_on_bigdecimal.mojo b/examples/examples_on_bigdecimal.mojo similarity index 95% rename from docs/examples_on_bigdecimal.mojo rename to examples/examples_on_bigdecimal.mojo index b9e61781..8ccff547 100644 --- a/docs/examples_on_bigdecimal.mojo +++ b/examples/examples_on_bigdecimal.mojo @@ -2,10 +2,8 @@ from decimo.prelude import * def main() raises: - var b = Decimal( - "1234.56789" - ) # Decimal is a Python-like alias for BigDecimal - var a = BDec("123456789.123456789") # BDec is another alias for BigDecimal + var a = Decimal("123456789.123456789") + var b = Decimal("1234.56789") # === Basic Arithmetic === # print(a + b) # 123458023.691346789 diff --git a/docs/examples_on_bigint.mojo b/examples/examples_on_bigint.mojo similarity index 100% rename from docs/examples_on_bigint.mojo rename to examples/examples_on_bigint.mojo diff --git a/docs/examples_on_decimal128.mojo b/examples/examples_on_decimal128.mojo similarity index 100% rename from docs/examples_on_decimal128.mojo rename to examples/examples_on_decimal128.mojo diff --git a/pixi.toml b/pixi.toml index 0496ead4..1929e948 100644 --- a/pixi.toml +++ b/pixi.toml @@ -6,7 +6,7 @@ license = "Apache-2.0" name = "decimo" platforms = ["osx-arm64", "linux-64"] readme = "README.md" -version = "0.9.0" +version = "0.10.0" [dependencies] # argmojo = ">=0.4.0" # CLI argument parsing for the Decimo calculator (TODO: waiting for argmojo 0.4.0 compatible with mojo 0.26.2) diff --git a/python/README.md b/python/README.md index a2f008dc..43967d0a 100644 --- a/python/README.md +++ b/python/README.md @@ -14,7 +14,7 @@ ## What is decimo? `decimo` is an arbitrary-precision decimal and integer library, originally written in [Mojo](https://www.modular.com/mojo). -This package exposes `decimo`'s `BigDecimal` type to Python via a Mojo-built CPython extension module (`_decimo.so`), +This package exposes `decimo`'s `Decimal` type to Python via a Mojo-built CPython extension module (`_decimo.so`), with a thin Python wrapper providing full Pythonic operator support. ```python @@ -30,13 +30,13 @@ print(a / b) # 0.12499999... ## Status -| Feature | Status | -| ------------------------------------------------------ | ------------- | -| `Decimal` (BigDecimal) arithmetic (`+`, `-`, `*`, `/`) | ✓ Working | -| Comparison operators | ✓ Working | -| Unary `-`, `abs()`, `bool()` | ✓ Working | -| Pre-built wheels on PyPI | ? Coming soon | -| `BigInt` / `Decimal128` Python bindings | ? Planned | +| Feature | Status | +| ----------------------------------------- | ------------- | +| `Decimal` arithmetic (`+`, `-`, `*`, `/`) | ✓ Working | +| Comparison operators | ✓ Working | +| Unary `-`, `abs()`, `bool()` | ✓ Working | +| Pre-built wheels on PyPI | ? Coming soon | +| `BigInt` / `Decimal128` Python bindings | ? Planned | ## Building from source diff --git a/src/decimo/bigdecimal/bigdecimal.mojo b/src/decimo/bigdecimal/bigdecimal.mojo index d7dc9f93..83fd6222 100644 --- a/src/decimo/bigdecimal/bigdecimal.mojo +++ b/src/decimo/bigdecimal/bigdecimal.mojo @@ -33,10 +33,37 @@ from decimo.bigdecimal.rounding import round_to_precision from decimo.bigint10.bigint10 import BigInt10 import decimo.str -comptime BDec = BigDecimal -"""An arbitrary-precision decimal, similar to Python's `decimal.Decimal`.""" +# Type aliases for the arbitrary-precision decimal type. +# The names BigDecimal, Decimal, and BDec can be used interchangeably. +# The preferred name that is exposed to users is Decimal. comptime Decimal = BigDecimal -"""An arbitrary-precision decimal, similar to Python's `decimal.Decimal`.""" +"""An arbitrary-precision decimal, similar to Python's `decimal.Decimal`. + +Notes: + +Internal Representation: + +- A base-10 unsigned integer (BigUInt) for coefficient. +- A Int value for the scale +- A Bool value for the sign. + +Final value: +(-1)**sign * coefficient * 10^(-scale) +""" +comptime BDec = BigDecimal +"""An arbitrary-precision decimal, similar to Python's `decimal.Decimal`. + +Notes: + +Internal Representation: + +- A base-10 unsigned integer (BigUInt) for coefficient. +- A Int value for the scale +- A Bool value for the sign. + +Final value: +(-1)**sign * coefficient * 10^(-scale) +""" comptime PRECISION = 28 # Same as Python's decimal module default precision of 28 places. """Default precision for BigDecimal operations. @@ -44,6 +71,28 @@ This will be configurable in future when Mojo supports global variables. """ +# [Mojo Miji] +# The name BigDecimal, Decimal, and BDec can be used interchangeably. +# They are just aliases for the same struct. +# +# Initially, I chose BigDecimal as the canonical name because it is the most +# descriptive and unambiguous. +# +# However, now I prefer to use Decimal as the canonical name for two reasons: +# First, it is more familiar to Python users (`decimal.Decimal`). +# Second, BigDecimal is a bit long to type, and in most cases, users just use +# the Decimal alias. Nevertheless, Mojo does not provide a complete docstring +# when users hover over the alias, so it is important to name the struct itself +# with a more popular name to provide better discoverability and documentation. +# +# Nevertheless, there is a Mojo compiler issue (still in v0.26.2) that, when +# I change the struct name to Decimal and make BigDecimal an alias, the compile +# fails at setting `--debug-level=full`. I could not tell why this happens, +# but it seems that the compiler gets confused about the alias and the struct +# name when other modules import BigDecimal instead of Decimal. +# +# Thus, for now I will keep the struct name as BigDecimal and make Decimal an +# alias, and wait for the Mojo compiler to fix this issue in the future. struct BigDecimal( Absable, Comparable, @@ -188,7 +237,7 @@ struct BigDecimal( " avoid unintentional loss of precision. If you want to create" " a BigDecimal from a floating-point number, please consider" " wrapping it with quotation marks or using the" - " `BigDecimal.from_float()` (or `BDec.from_float()`) method" + " `Decimal.from_float()` method" " instead." "\n***********************************************************" ) diff --git a/tests/test_bigdecimal.sh b/tests/test_bigdecimal.sh index 43f07cb9..b66a2d95 100755 --- a/tests/test_bigdecimal.sh +++ b/tests/test_bigdecimal.sh @@ -2,5 +2,6 @@ set -e for f in tests/bigdecimal/*.mojo; do + echo "=== $f ===" pixi run mojo run -I src -D ASSERT=all --debug-level=full "$f" done diff --git a/tests/test_bigint.sh b/tests/test_bigint.sh index 88790488..7bdf8066 100755 --- a/tests/test_bigint.sh +++ b/tests/test_bigint.sh @@ -2,5 +2,6 @@ set -e for f in tests/bigint/*.mojo; do + echo "=== $f ===" pixi run mojo run -I src -D ASSERT=all --debug-level=full "$f" done diff --git a/tests/test_bigint10.sh b/tests/test_bigint10.sh index a5043d71..18dbba16 100755 --- a/tests/test_bigint10.sh +++ b/tests/test_bigint10.sh @@ -2,5 +2,6 @@ set -e for f in tests/bigint10/*.mojo; do + echo "=== $f ===" pixi run mojo run -I src -D ASSERT=all --debug-level=full "$f" done diff --git a/tests/test_biguint.sh b/tests/test_biguint.sh index 309884ff..0d80345f 100755 --- a/tests/test_biguint.sh +++ b/tests/test_biguint.sh @@ -2,5 +2,6 @@ set -e for f in tests/biguint/*.mojo; do + echo "=== $f ===" pixi run mojo run -I src -D ASSERT=all --debug-level=full "$f" done diff --git a/tests/test_decimal128.sh b/tests/test_decimal128.sh index e229f0c1..a7edaa25 100755 --- a/tests/test_decimal128.sh +++ b/tests/test_decimal128.sh @@ -2,5 +2,6 @@ set -e for f in tests/decimal128/*.mojo; do + echo "=== $f ===" pixi run mojo run -I src -D ASSERT=all --debug-level=full "$f" done diff --git a/tests/test_toml.sh b/tests/test_toml.sh index 7fe978a3..38523abc 100755 --- a/tests/test_toml.sh +++ b/tests/test_toml.sh @@ -2,5 +2,6 @@ set -e for f in tests/toml/*.mojo; do + echo "=== $f ===" pixi run mojo run -I src -D ASSERT=all --debug-level=full "$f" done