From 9e3ff9ea37c1aba4320faed030322423cfefd8ff Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Wed, 12 Aug 2026 23:27:51 +0200 Subject: [PATCH] [doc] Update documents for release --- .github/workflows/run_tests.yaml | 25 ++ README.md | 33 +- docs/changelog.md | 40 ++- docs/readme_unreleased.md | 465 ---------------------------- docs/readme_zht.md | 3 +- docs/user_manual.md | 92 +++++- pixi.toml | 2 +- src/decimo/__init__.mojo | 4 +- src/decimo/expression/__init__.mojo | 2 +- 9 files changed, 173 insertions(+), 493 deletions(-) delete mode 100644 docs/readme_unreleased.md diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index d1c33a4..df3610f 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -234,6 +234,31 @@ jobs: sleep 5 done + # ── Test: Expression engine ─────────────────────────────────────────────── + test-expression: + name: Test Expression + needs: build + runs-on: macos-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-decimo + - name: Run tests (with retry for Mojo compiler intermittent crashes) + run: | + for attempt in 1 2 3; do + echo "=== test attempt $attempt ===" + if bash tests/test.sh expression; then + echo "=== tests passed on attempt $attempt ===" + break + fi + if [ "$attempt" -eq 3 ]; then + echo "=== tests failed after 3 attempts ===" + exit 1 + fi + echo "=== test run crashed, retrying in 5s... ===" + sleep 5 + done + # ── Test: Numeral systems ───────────────────────────────────────────────── test-numerals: name: Test Numerals diff --git a/README.md b/README.md index 65d2de9..b1ef653 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ An arbitrary-precision integer and decimal library for [Mojo](https://www.modula Comes with an interactive arbitrary-precision calculator (REPL + one-shot mode) powered by [ArgMojo](https://github.com/forfudan/argmojo). Install it with `brew install forfudan/tap/decimo`. -[![Version](https://img.shields.io/badge/version-v0.11.0-blue)](https://github.com/forfudan/decimo/releases/tag/v0.11.0) -[![Mojo](https://img.shields.io/badge/mojo-1.0.0b2-orange)](https://docs.modular.com/mojo/manual/) +[![Version](https://img.shields.io/badge/version-v0.12.0-blue)](https://github.com/forfudan/decimo/releases/tag/v0.12.0) +[![Mojo](https://img.shields.io/badge/mojo-1.0.0-orange)](https://docs.modular.com/mojo/manual/) [![pixi](https://img.shields.io/badge/pixi%20add-decimo-purple)](https://prefix.dev/channels/modular-community/packages/decimo) [![CI](https://img.shields.io/github/actions/workflow/status/forfudan/decimo/run_tests.yaml?branch=main&label=tests)](https://github.com/forfudan/decimo/actions/workflows/run_tests.yaml) @@ -74,7 +74,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.11.0" + decimo = "==0.12.0" ``` Then run `pixi install` to download and install the package. @@ -97,6 +97,7 @@ The following table summarizes the package versions and their corresponding Mojo | `decimo` | v0.9.0 | ==0.26.2 | pixi | | `decimo` | v0.10.0 | ==1.0.0b1 | pixi | | `decimo` | v0.11.0 | ==1.0.0b2 | pixi | +| `decimo` | v0.12.0 | ==1.0.0 | pixi | ### Install CLI calculator @@ -406,19 +407,27 @@ decimo/ │ │ ├── bigint/ # Arbitrary-precision signed integer (Integer) │ │ ├── bigint10/ # Base-10 signed integer (BigInt10) │ │ ├── biguint/ # Base-10 unsigned integer (BigUInt) +│ │ ├── bigfloat/ # Arbitrary-precision binary float (MPFR) +│ │ ├── rational/ # Exact rational number (Rational) │ │ ├── decimal128/ # 128-bit fixed-precision decimal (Dec128) +│ │ ├── expression/ # Expression engine behind `decimo.eval()` +│ │ │ ├── tokenizer.mojo # Lexer: expression → tokens +│ │ │ ├── parser.mojo # Shunting-yard: infix → RPN +│ │ │ └── evaluator.mojo # RPN evaluator using Decimal +│ │ ├── numerals/ # Numeral systems (e.g. Chinese numerals) +│ │ ├── toml/ # TOML parser (decimo.toml) │ │ └── ... # Shared utilities (str, errors, rounding) │ └── cli/ # CLI calculator application │ ├── main.mojo # Entry point (ArgMojo CLI) -│ └── calculator/ # Calculator engine (mojo pre-compiled package) -│ ├── tokenizer.mojo # Lexer: expression → tokens -│ ├── parser.mojo # Shunting-yard: infix → RPN -│ └── evaluator.mojo # RPN evaluator using Decimal +│ ├── limo/ # Line editor used by the REPL +│ └── calculator/ # Presentation layer (display, io, repl, settings) ├── tests/ # Unit tests (one subfolder per module) │ ├── bigdecimal/ │ ├── bigint/ │ ├── biguint/ │ ├── decimal128/ +│ ├── expression/ # Expression engine tests +│ ├── numerals/ # Numeral system tests │ ├── cli/ # CLI calculator tests │ └── toml/ ├── benches/ # Benchmarks (one subfolder per module) @@ -426,16 +435,16 @@ decimo/ └── pixi.toml # Project configuration and tasks ``` -`src/decimo/` is a Mojo package — it is compiled with `mojo precompile` and can be imported by external projects. The TOML parser (`decimo.toml`) is included as a subpackage. `src/cli/` is an application that consumes the `decimo` package and compiles to a standalone binary via `mojo build`. +`src/decimo/` is a Mojo package — it is compiled with `mojo precompile` and can be imported by external projects. The expression engine (`decimo.expression`), the numeral systems (`decimo.numerals`), and the TOML parser (`decimo.toml`) are included as subpackages. `src/cli/` is an application that consumes the `decimo` package and compiles to a standalone binary via `mojo build`. ## Tests and benches After cloning the repo onto your local disk, you can: -- Use `pixi run test` to run all tests. -- Use `pixi run test_cli` to run CLI calculator tests. +- Use `pixi run test` to run all tests, or `pixi run test ` for one suite (`pixi run test --list` shows them). +- Use `pixi run testcli` to run CLI calculator tests. - Use `pixi run bench` to run benchmarks. -- Use `pixi run build` to compile the CLI calculator to a `./decimo` binary. +- Use `pixi run buildcli` to compile the CLI calculator to a `./decimo` binary. ## Citation @@ -447,7 +456,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.11.0}, + version = {0.12.0}, note = {Computer Software} } ``` diff --git a/docs/changelog.md b/docs/changelog.md index 9a3ab46..6b8c579 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,25 +2,45 @@ This is a list of changes for the Decimo package (formerly DeciMojo). -## Unreleased (v0.12.0) +## 20260812 (v0.12.0) -Decimo v0.12.0 retargets the codebase to **Mojo v1.0.0**. +Decimo v0.12.0 retargets the codebase to **Mojo v1.0.0**, promotes the CLI's expression evaluator to a first-class part of the library (`decimo.eval()`), and adds Chinese numeral output for `BigInt` and `BigDecimal`. The `from_int()` and `from_uint()` factory methods are removed, which is a breaking change for code that calls them directly. -### ⭐️ New +### ⭐️ New in v0.12.0 -**Expression engine (`decimo.expression`):** +**Expression engine (`decimo.expression`)** (PR #259): -1. The arithmetic-expression engine (tokenizer, shunting-yard parser, and RPN evaluator) that previously lived inside the CLI is now a first-class part of the core library under `decimo/expression/`. Users can evaluate a string in one call with the new high-level API `decimo.eval(expr, precision=50, variables={}, rounding_mode=...)`, e.g. `decimo.eval("100 + e * pi")`. Advanced users can import the individual stages via `from decimo.expression import tokenize, parse_to_rpn, evaluate_rpn`. `eval` optionally accepts a `variables` map so expressions can reference externally supplied named values (e.g. `eval("x^2 + y", variables=vars)`). `decimo.evaluate` is kept as an alias. -1. The CLI now re-uses this shared engine instead of its own copy, eliminating duplicated logic. Its presentation layer (`display`, `io`, `repl`, `settings`, `engine`) stays in the CLI. -1. The expression tokenizer now treats newline (`\n`) and carriage return (`\r`) as whitespace, so `eval` accepts strings with leading/trailing/embedded line breaks (e.g. triple-quoted expressions). +1. The tokenizer, shunting-yard parser, and RPN evaluator that used to live inside the CLI now sit in the core library under `decimo/expression/`. A string can be evaluated in one call with **`decimo.eval(expr, precision=50, variables=..., rounding_mode=...)`**, e.g. `decimo.eval("100 + e * pi")`. The individual stages are still available via `from decimo.expression import tokenize, parse_to_rpn, evaluate_rpn`, and `decimo.evaluate` is kept as an alias of `eval`. +1. `eval()` takes an optional **`variables`** map, so an expression can refer to named values supplied by the caller, e.g. `eval("x^2 + y", variables=vars)`. +1. The tokenizer treats `\n` and `\r` as whitespace, so multi-line (e.g. triple-quoted) expressions are accepted. +1. The CLI re-uses this shared engine instead of its own copy. Its presentation layer (`display`, `io`, `repl`, `settings`, `engine`) stays in the CLI, and the engine tests move from `tests/cli/` to `tests/expression/`. -**Chinese numerals (`decimo.numerals`):** +**Chinese numerals (`decimo.numerals`)** (PR #262): -1. New sub-package **`decimo.numerals`** hosts conversions to non-Latin numeral systems. Each module there renders a decimal *string* rather than a particular numeric type, so the conversions are shared by every Decimo type and are not limited by any integer width. -1. New **`decimal_string_to_chinese()`** in `decimo.numerals.chinese`, plus the **`BigDecimal.to_chinese()`** and **`BigInt.to_chinese()`** methods, write a number in Chinese numerals, e.g. `BigDecimal("1050.07").to_chinese()` gives `一千零五十点零七`. The integer part is split into sections of eight digits that are read with the 十/百/千/万 units and joined by 亿, which multiplies everything read before it — so `1234567890123` gives `一万二千三百四十五亿六千七百八十九万零一百二十三` and each further 亿 raises the magnitude by another 10^8 (亿亿 is 10^16). Integers of arbitrary length are therefore supported without relying on the rarely-agreed-upon 兆/京/垓 units. Runs of zeros collapse into a single 零, a leading 一十 is shortened to 十, and the fractional part is read digit by digit after 点 so the written precision is preserved (`1.50` gives `一点五零`). +1. New sub-package **`decimo.numerals`** hosts conversions to non-Latin numeral systems. Each module renders a decimal *string* rather than a particular numeric type, so the conversions are shared by every Decimo type and are not limited by any integer width. +1. New **`decimal_string_to_chinese()`**, plus the **`BigInt.to_chinese()`** and **`BigDecimal.to_chinese()`** methods, write a number in Chinese numerals — `BigDecimal("1050.07").to_chinese()` gives `一千零五十点零七`. The integer part is split into sections of eight digits, read with the 十/百/千/万 units and joined by 亿, which multiplies everything read before it: `1234567890123` gives `一万二千三百四十五亿六千七百八十九万零一百二十三`, and each further 亿 raises the magnitude by another 10^8 (亿亿 is 10^16). Integers of any length are therefore supported without relying on the rarely-agreed-upon 兆/京/垓 units. Runs of zeros collapse into a single 零, a leading 一十 is shortened to 十, and the fractional part is read digit by digit after 点 so the written precision is preserved (`1.50` gives `一点五零`). 1. A reading is always written out in full, so its cost follows the *written* length of the number rather than the length of the input — `BigDecimal("1E+1000000000")` is a few characters that would expand into a billion digits. All three conversions therefore take a **`max_digits`** budget, defaulting to **`MAX_CHINESE_NUMERAL_DIGITS`** (10 000), and raise a `ValueError` past it. The budget is checked before the digits are expanded, so an absurd magnitude is rejected at no cost; pass `max_digits=0` to lift the cap. 1. The rendering is table-driven through the new **`ChineseNumeralStyle`** struct, which ships with the `simplified()`, `simplified_financial()` (大写: 壹贰叁 / 拾佰仟), `traditional()` (繁體: 萬億點負), and `traditional_financial()` presets; custom tables can be supplied as well. +### 🦋 Changed in v0.12.0 + +**Mojo v1.0.0 migration** (PR #260): + +1. Retarget the codebase to **Mojo v1.0.0** and bump the Pixi dependency to `mojo >=1.0.0,<1.1.0`. +1. **`from_int()` and `from_uint()` are removed** from `BigInt`, `BigUInt`, `BigInt10`, and `BigDecimal`, along with their separate `Int` / `UInt` constructors: `Int` is now an integral scalar, so the generic `from_integral_scalar()` path covers it (and `BigUInt` gains one). `BInt(42)` and `Decimal(42)` are unaffected; direct calls to `from_int()` / `from_uint()` must switch to `from_integral_scalar()`. +1. The power-of-10 lookup tables are emitted once into static storage with `global_constant` instead of being rebuilt at every call site (the alternatives, `materialize` and a `comptime for`, cost either stack traffic or code size). Fixed-size tables and temporaries switch from `List` to `Array` / `InlineArray`, and slice operations in `BigInt` take `ImmSpan` instead of copying. +1. `Decimal128` bitcasting moves from `UnsafePointer` to `Pointer(to=).unsafe_bitcast()`. +1. **Build tasks**: `pixi run argmojo` (new `src/cli/ensure_argmojo.sh`) resolves ArgMojo from the conda package when it is available and otherwise clones and precompiles the pinned upstream v0.8.0 commit into `temp/`, so `pixi run buildcli` works while modular-community catches up. `pixi run clean` no longer fails on a fresh checkout, and `pixi run doc` resolves `limo` from source. + +**Errors** (PR #261): + +1. The base error type `DecimoError` is renamed to **`BaseError`**, which reads better now that every concrete type (`ValueError`, `OverflowError`, …) is derived from it. `DecimoError` remains as a derived alias, so existing code keeps working. + +**Documentation and CI:** + +1. The user manual gains an **Expression Engine** section covering `eval()`, the supported syntax, variables, and the individual stages. The README's project-structure tree is brought up to date, and `docs/readme_unreleased.md` — a duplicate of the README — is removed. +1. CI gains a **`test-expression`** job, since those tests no longer run as part of the CLI suite. + ## 20260701 (v0.11.0) Decimo v0.11.0 retargets the codebase to **Mojo v1.0.0b2**, adds the `factorial()` and `permutation()` functions to `BigInt` and `BigDecimal`, and includes a series of performance improvements for `BigDecimal` and `BigUInt` arithmetic. It also renames the `BigDecimal` `round_to_precision` APIs to `*_inplace`, which is a breaking change for code that calls them directly. diff --git a/docs/readme_unreleased.md b/docs/readme_unreleased.md deleted file mode 100644 index 65d2de9..0000000 --- a/docs/readme_unreleased.md +++ /dev/null @@ -1,465 +0,0 @@ -# Decimo (formerly DeciMojo) - -An arbitrary-precision integer and decimal library for [Mojo](https://www.modular.com/mojo), also with a 128-bit fixed-point decimal type, inspired by Python's `int` and `Decimal`. Install it with `pixi add decimo`. - -Comes with an interactive arbitrary-precision calculator (REPL + one-shot mode) powered by [ArgMojo](https://github.com/forfudan/argmojo). Install it with `brew install forfudan/tap/decimo`. - -[![Version](https://img.shields.io/badge/version-v0.11.0-blue)](https://github.com/forfudan/decimo/releases/tag/v0.11.0) -[![Mojo](https://img.shields.io/badge/mojo-1.0.0b2-orange)](https://docs.modular.com/mojo/manual/) -[![pixi](https://img.shields.io/badge/pixi%20add-decimo-purple)](https://prefix.dev/channels/modular-community/packages/decimo) -[![CI](https://img.shields.io/github/actions/workflow/status/forfudan/decimo/run_tests.yaml?branch=main&label=tests)](https://github.com/forfudan/decimo/actions/workflows/run_tests.yaml) - -| Type | Alias | Information | Layout | -| ------------ | ----------------- | ---------------------------------------- | ------------ | -| `BigInt` | `BInt`, `Integer` | Equivalent to Python's `int` | Base-2^32 | -| `BigDecimal` | `BDec`, `Decimal` | Equivalent to Python's `decimal.Decimal` | Base-10^9 | -| `Decimal128` | `Dec128` | 128-bit fixed-precision decimal type | 32-bit words | -| `BigFloat` | `Float` | Arbitrary-precision floating-point type | MPFR/GMP | - - - - - -## Overview - -### Decimo library - -Decimo provides an arbitrary-precision integer and decimal library for Mojo. It delivers exact calculations for financial modeling, scientific computing, and applications where floating-point approximation errors are unacceptable. Beyond basic arithmetic, the library includes advanced mathematical functions with guaranteed precision. - -For Pythonistas, `decimo.BigInt` to Mojo is like `int` to Python, and `decimo.BigDecimal` to Mojo is like `decimal.Decimal` to Python. `decimo.Decimal128` to Mojo is like `System.Decimal` to C# or `rust_decimal` to Rust. - -The core types are[^auxiliary]: - -- An arbitrary-precision signed integer type `BigInt`[^bigint] (alias `BInt`), which is a Mojo-native equivalent of Python's `int`. -- An arbitrary-precision decimal implementation (`BigDecimal`) (alias `Decimal`) allowing for calculations with unlimited digits and decimal places[^arbitrary], which is a Mojo-native equivalent of Python's `decimal.Decimal`. -- A 128-bit fixed-point decimal implementation (`Decimal128`) (alias `Dec128`) supporting up to 29 significant digits with a maximum of 28 decimal places[^fixed], which is a Mojo-native equivalent of C#'s `System.Decimal` or Rust's `rust_decimal`. -- An arbitrary-precision floating-point implementation (`BigFloat`) backed by the GNU MPFR library, supporting computations with configurable precision and a wide exponent range. Unlike `BigDecimal`, which uses base-10 arithmetic, `BigFloat` uses binary floating-point internally. This type is optional and requires MPFR/GMP to be installed on the user's system. - - -**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". - -### CLI calculator - -`decimo` is a command-line calculator built on the Decimo library and powered by [ArgMojo](https://github.com/forfudan/argmojo). Run it with no arguments for an interactive REPL, or pass an expression / file / piped stdin for one-shot evaluation. The binary is self-contained — no Mojo or Pixi needed on the user's machine. See the [user manual](./docs/user_manual_cli.md) for the full reference, and the [Quick start](#cli-quick-start) below for a taste. - -### TOML parser - -This repository includes a built-in [TOML parser](./docs/readme_toml.md) (`decimo.toml`), a lightweight pure-Mojo implementation supporting TOML v1.0. It parses configuration files and test data, supporting basic types, arrays, and nested tables. While created for Decimo's testing framework, it offers general-purpose structured data parsing with a clean, simple API. - -## Installation - -### Install Decimo library for Mojo projects - -Decimo is available in the modular-community `https://repo.prefix.dev/modular-community` package repository. To access this repository, add it to your `channels` list in your `pixi.toml` file: - -```toml -channels = ["https://conda.modular.com/max", "https://repo.prefix.dev/modular-community", "conda-forge"] -``` - -Then, you can install Decimo using any of these methods: - -1. From the `pixi` CLI, run the command ```pixi add decimo```. This fetches the latest version and makes it immediately available for import. - -1. In the `mojoproject.toml` file of your project, add the following dependency: - - ```toml - decimo = "==0.11.0" - ``` - - Then run `pixi install` to download and install the package. - -1. For the latest development version in the `main` branch, clone [this GitHub repository](https://github.com/forfudan/decimo) and build the package locally using the command `pixi run package`. - -The following table summarizes the package versions and their corresponding Mojo versions: - -| library | version | Mojo version | package manager | -| ---------- | ------- | ------------- | --------------- | -| `decimojo` | v0.1.0 | ==25.1 | magic | -| `decimojo` | v0.2.0 | ==25.2 | magic | -| `decimojo` | v0.3.0 | ==25.2 | magic | -| `decimojo` | v0.3.1 | >=25.2, <25.4 | pixi | -| `decimojo` | v0.4.x | ==25.4 | pixi | -| `decimojo` | v0.5.0 | ==25.5 | pixi | -| `decimojo` | v0.6.0 | ==0.25.7 | pixi | -| `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 | ==1.0.0b1 | pixi | -| `decimo` | v0.11.0 | ==1.0.0b2 | pixi | - -### Install CLI calculator - -The `decimo` CLI is distributed via the [`forfudan/tap`](https://github.com/forfudan/homebrew-tap) Homebrew tap. Pre-built binaries are available for **macOS arm64** (Apple Silicon) and **Linux x86_64**, and ship with the Mojo runtime libraries bundled — you do not need Mojo or Pixi installed. - -```bash -brew install forfudan/tap/decimo -decimo --version -``` - -Or tap once and use the bare formula name: - -```bash -brew tap forfudan/tap -brew install decimo -``` - -To upgrade to a later release: - -```bash -brew update && brew upgrade decimo -``` - -## Quick start - -### CLI quick start - -For an interactive session, just type `decimo`: - -```sh -$ decimo -Decimo — an arbitrary-precision calculator 🔥 -Type ? for help, : for settings, :q to quit. -Precision: 50. Rounding: ROUND_HALF_EVEN. -decimo> x = sqrt(2) -1.4142135623730950488016887242096980785696718753769 -decimo> x ^ 2 -2 -decimo> ans + 1 -3 -decimo> :100 -decimo> pi -3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 -decimo> sqrt(e) / ln(10) + sin(-1.23) :200 e he delimiter _ --226.458_251_870_114_348_807_514_569_584_297_293_353_150_959_525_480_515_507_901_779_719_167_225_208_528_825_475_488_261_072_148_336_432_171_617_635_953_314_758_797_226_777_458_915_435_649_950_836_584_843_137_886_028_274_720_793_979_517_570_004_978_334_405_953_342_64E-3 -decimo> :q -``` - -The REPL keeps the last result in `ans`, lets you define variables (`name = expr`), and exposes settings via `:`-prefixed commands (e.g. `:100` for precision, `:s` for scientific, `:d` for ROUND_DOWN). Input is case-insensitive. Quit with `:q`, `exit`, or Ctrl-D. - -As an innovative feature, Decimo supports multiple settings in a single line. They can either be global (persist across calculations) or local (apply only to the current expression). In the example above, `:200 e he delimiter _` means "evaluate the expression with precision 200 (`200`), scientific notation with engineering exponent (`e`), round half to even (`he`), and use `_` as the digit delimiter in the output (`delimiter _`)". The settings apply only to the current expression and do not affect subsequent calculations. - -For one-shot evaluation, pass an expression on the command line, pipe it via stdin, or read from a file: - -```bash -$ decimo "sqrt(2)" -P 30 -1.41421356237309504880168872421 - -$ echo "1/3" | decimo -P 50 -0.33333333333333333333333333333333333333333333333333 - -$ decimo -F expressions.dm -P 80 -``` - -Useful flags: `-P N` (precision), `-R MODE` (rounding), `-S` / `-E` (scientific / engineering), `--pad`, `--delimiter`, `--completions {bash,zsh,fish}`. Run `decimo --help` for the full list. - -### Library quick start - -You can start using Decimo by importing the `decimo` module. An easy way to do this is to import everything from the `prelude` module, which provides the most commonly used types. - -```mojo -from decimo import * -``` - -This will import the following types or aliases into your namespace: - -- `BigInt` (and its aliases `BInt`, `Integer`): An arbitrary-precision signed integer type, equivalent to Python's `int`. -- `BigDecimal` (and its aliases `BDec`, `Decimal`): An arbitrary-precision decimal type, equivalent to Python's `decimal.Decimal`. -- `Decimal128` (and its alias `Dec128`): 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. - ---- - -Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` (`Decimal`) type. For some mathematical operations, the default precision (number of significant digits) is set to `28`. You can change the precision by passing the `precision` argument to the function. This default precision will be configurable globally in future when Mojo supports global variables. - -```mojo -from decimo.prelude import * - - -def main() raises: - var a = BigDecimal("123456789.123456789") - var b = Decimal("1234.56789") # Alias of BigDecimal - - # === Basic Arithmetic === # - print(a + b) # 123458023.691346789 - print(a - b) # 123455554.555566789 - print(a * b) # 152415787654.32099750190521 - print(a.true_divide(b + 1)) # 99919.06565608207008357913866 - - # === Exponential Functions === # - print(a.sqrt(precision=80)) - # 11111.111066111110969430554981749302328338130654689094538188579359566416821203641 - print(a.cbrt(precision=80)) - # 497.93385938415242742001134219007635925452951248903093962731782327785111102410518 - print(a.root(b, precision=80)) - # 1.0152058862996527138602610522640944903320735973237537866713119992581006582644107 - print(a.power(b, precision=80)) - # 3.3463611024190802340238135400789468682196324482030786573104956727660098625641520E+9989 - print(a.exp(precision=80)) - # 1.8612755889649587035842377856492201091251654136588338983610243887893287518637652E+53616602 - print(a.log(b, precision=80)) - # 2.6173300266565482999078843564152939771708486260101032293924082259819624360226238 - print(a.ln(precision=80)) - # 18.631401767168018032693933348296537542797015174553735308351756611901741276655161 - - # === Trigonometric Functions === # - print(a.sin(precision=200)) - # 0.99985093087193092464780008002600992896256609588456 - # 91036188395766389946401881352599352354527727927177 - # 79589259132243649550891532070326452232864052771477 - # 31418817041042336608522984511928095747763538486886 - print(b.cos(precision=1000)) - # -0.9969577603867772005841841569997528013669868536239849713029893885930748434064450375775817720425329394 - # 9756020177557431933434791661179643984869397089102223199519409695771607230176923201147218218258755323 - # 7563476302904118661729889931783126826250691820526961290122532541861737355873869924820906724540889765 - # 5940445990824482174517106016800118438405307801022739336016834311018727787337447844118359555063575166 - # 5092352912854884589824773945355279792977596081915868398143592738704592059567683083454055626123436523 - # 6998108941189617922049864138929932713499431655377552668020889456390832876383147018828166124313166286 - # 6004871998201597316078894718748251490628361253685772937806895692619597915005978762245497623003811386 - # 0913693867838452088431084666963414694032898497700907783878500297536425463212578556546527017688874265 - # 0785862902484462361413598747384083001036443681873292719322642381945064144026145428927304407689433744 - # 5821277763016669042385158254006302666602333649775547203560187716156055524418512492782302125286330865 - - # === Internal representation of the number === # - ( - Decimal( - "3.141592653589793238462643383279502884197169399375105820974944" - ).power(2, precision=60) - ).print_internal_representation() - # Internal Representation Details of BigDecimal - # ---------------------------------------------- - # number: 9.8696044010893586188344909998 - # 761511353136994072407906264133 - # 5 - # coefficient: 986960440108935861883449099987 - # 615113531369940724079062641335 - # negative: False - # scale: 59 - # word 0: 62641335 - # word 1: 940724079 - # word 2: 113531369 - # word 3: 99987615 - # word 4: 861883449 - # word 5: 440108935 - # word 6: 986960 - # ---------------------------------------------- -``` - ---- - -Here is a comprehensive quick-start guide showcasing each major function of the `BigInt` (`BInt`, `Integer`) type. - -```mojo -from decimo.prelude import * - - -def main() raises: - # === Construction === - var a = BigInt("12345678901234567890") # From string - var b = BigInt(12345) # From integer - var c = BInt("1991_10,18") # From string with separators and spaces - print(a, b, c) - - # === Basic Arithmetic === - print(a + b) # Addition: 12345678901234580235 - print(a - b) # Subtraction: 12345678901234555545 - print(a * b) # Multiplication: 152415787814108380241050 - - # === Division Operations === - print(a // b) # Floor division: 999650944609516 - print(a.truncate_divide(b)) # Truncate division: 999650944609516 - print(a % b) # Modulo: 9615 - - # === Power Operation === - print(BigInt(2).power(10)) # Power: 1024 - print(BigInt(2) ** 10) # Power (using ** operator): 1024 - - # === Comparison === - print(a > b) # Greater than: True - print(a == BigInt("12345678901234567890")) # Equality: True - print(a.is_zero()) # Check for zero: False - - # === Type Conversions === - print(String(a)) # To string: "12345678901234567890" - - # === Sign Handling === - print(-a) # Negation: -12345678901234567890 - print( - abs(BigInt("-12345678901234567890")) - ) # Absolute value: 12345678901234567890 - print(a.is_negative()) # Check if negative: False - - # === Extremely large numbers === - # 3600 digits // 1800 digits - print(BigInt("123456789" * 400) // BigInt("987654321" * 200)) - - # === Greatest common divisor === - print(a.gcd(b)) # Greatest common divisor: 15 - print(a.gcd(c)) # Greatest common divisor: 6 -``` - ---- - -Here is a comprehensive quick-start guide showcasing each major function of the `Decimal128` (`Dec128`) type. - -```mojo -from decimo.prelude import * - - -def main() raises: - # === Construction === - # Decimal128 and Dec128 are aliases - var a = Decimal128("123.45") # From string - var b = Decimal128(123) # From integer - var c = Dec128(123, 2) # Integer with scale (1.23) - var d = Dec128.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.0036585365853658536585365854 - - # === Rounding & Precision === - print(a.round(1)) # Round to 1 decimal place: 123.5 - print(a.quantize(Dec128("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 == Dec128("123.45")) # Equality: True - print(a.is_zero()) # Check for zero: False - print(Dec128("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_string()) # To string: "123.45" - print(a.coefficient()) # Get coefficient: 12345 - print(a.scale()) # Get scale: 2 - - # === Mathematical Functions === - print(Dec128("2").sqrt()) # Square root: 1.4142135623730950488016887242 - print(Dec128("100").root(3)) # Cube root: 4.641588833612778892410076351 - print(Dec128("2.71828").ln()) # Natural log: 0.9999993273472820031578910056 - print(Dec128("10").log10()) # Base-10 log: 1 - print( - Dec128("16").log(Dec128("2")) - ) # Log base 2: 3.9999999999999999999999999999 - print(Dec128("10").exp()) # e^10: 22026.465794806716516957900645 - print(Dec128("2").power(10)) # Power: 1024 - - # === Sign Handling === - print(-a) # Negation: -123.45 - print(abs(Dec128("-123.45"))) # Absolute value: 123.45 - print(Dec128("123.45").is_negative()) # Check if negative: False - - # === Special Values === - print(Dec128.PI()) # π constant: 3.1415926535897932384626433833 - print(Dec128.E()) # e constant: 2.7182818284590452353602874714 - print(Dec128.ONE()) # Value 1: 1 - print(Dec128.ZERO()) # Value 0: 0 - print(Dec128.MAX()) # Maximum value: 79228162514264337593543950335 - - # === Convenience Methods === - print(Dec128("123.400").is_integer()) # Check if integer: False - print(a.number_of_significant_digits()) # Count significant digits: 5 - print( - Dec128("12.34").to_scientific_string() - ) # Scientific notation: 1.234E+1 -``` - -## Objective - -Financial calculations and data analysis require precise decimal arithmetic that floating-point numbers cannot reliably provide. As someone working in finance and credit risk model validation, I needed a dependable correctly-rounded, fixed-precision numeric type when migrating my personal projects from Python to Mojo. - -Since Mojo currently lacks a native Decimal type in its standard library, I decided to create my own implementation to fill that gap. - -This project draws inspiration from several established decimal implementations and documentation, e.g., [Python built-in `Decimal` type](https://docs.python.org/3/library/decimal.html), [Rust `rust_decimal` crate](https://docs.rs/rust_decimal/latest/rust_decimal/index.html), [Microsoft's `Decimal` implementation](https://learn.microsoft.com/en-us/dotnet/api/system.decimal.getbits?view=net-9.0&redirectedfrom=MSDN#System_Decimal_GetBits_System_Decimal_), [General Decimal Arithmetic Specification](https://speleotrove.com/decimal/decarith.html), etc. Many thanks to these predecessors for their contributions and their commitment to open knowledge sharing. - -## Status - -Rome wasn't built in a day. Decimo is currently under active development. It has successfully progressed through the **"make it work"** phase and the **"make it right"**, and is now well into the **"make it fast"** phase. - -The `Integer` type is fully implemented and optimized. It has been benchmarked against Python's `int` and demonstrates superior performance in most cases. - -Bug reports and feature requests are welcome! If you encounter issues, please [file them here](https://github.com/forfudan/decimo/issues). - -## Project structure - -```text -decimo/ -├── src/ # All source code -│ ├── decimo/ # Core library (mojo pre-compiled package) -│ │ ├── bigdecimal/ # Arbitrary-precision decimal (Decimal) -│ │ ├── bigint/ # Arbitrary-precision signed integer (Integer) -│ │ ├── bigint10/ # Base-10 signed integer (BigInt10) -│ │ ├── biguint/ # Base-10 unsigned integer (BigUInt) -│ │ ├── decimal128/ # 128-bit fixed-precision decimal (Dec128) -│ │ └── ... # Shared utilities (str, errors, rounding) -│ └── cli/ # CLI calculator application -│ ├── main.mojo # Entry point (ArgMojo CLI) -│ └── calculator/ # Calculator engine (mojo pre-compiled package) -│ ├── tokenizer.mojo # Lexer: expression → tokens -│ ├── parser.mojo # Shunting-yard: infix → RPN -│ └── evaluator.mojo # RPN evaluator using Decimal -├── tests/ # Unit tests (one subfolder per module) -│ ├── bigdecimal/ -│ ├── bigint/ -│ ├── biguint/ -│ ├── decimal128/ -│ ├── cli/ # CLI calculator tests -│ └── toml/ -├── benches/ # Benchmarks (one subfolder per module) -├── docs/ # Documentation and design notes -└── pixi.toml # Project configuration and tasks -``` - -`src/decimo/` is a Mojo package — it is compiled with `mojo precompile` and can be imported by external projects. The TOML parser (`decimo.toml`) is included as a subpackage. `src/cli/` is an application that consumes the `decimo` package and compiles to a standalone binary via `mojo build`. - -## Tests and benches - -After cloning the repo onto your local disk, you can: - -- Use `pixi run test` to run all tests. -- Use `pixi run test_cli` to run CLI calculator tests. -- Use `pixi run bench` to run benchmarks. -- Use `pixi run build` to compile the CLI calculator to a `./decimo` binary. - -## Citation - -If you find Decimo useful, consider listing it in your citations. - -```tex -@software{Zhu.2026, - author = {Zhu, Yuhao}, - year = {2026}, - title = {Decimo: An arbitrary-precision integer and decimal library for Mojo}, - url = {https://github.com/forfudan/decimo}, - version = {0.11.0}, - note = {Computer Software} -} -``` - -## License - -This repository and its contributions are licensed under the Apache License v2.0. - -The `BigFloat` type optionally uses the [GNU MPFR Library](https://www.mpfr.org/) (LGPLv3+) and [GMP](https://gmplib.org/) (LGPLv3+ or GPLv2+) at runtime. Decimo does not include or distribute any MPFR/GMP source code or binaries — they are loaded via `dlopen` only if the user has independently installed them. All other Decimo types work without any external dependencies. See the [NOTICE](./NOTICE) file for details. - -[^fixed]: The `Dec128` 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`), Decimo 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). Decimo's `Dec128` type is similar to `System.Decimal` (C#/.NET), `rust_decimal` in Rust, `DECIMAL/NUMERIC` in SQL Server, etc. -[^bigint]: The `Integer` 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, 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 2fd7471..be9bf7e 100644 --- a/docs/readme_zht.md +++ b/docs/readme_zht.md @@ -59,7 +59,7 @@ channels = ["https://conda.modular.com/max", "https://repo.prefix.dev/modular-co 1. 在您項目的 `mojoproject.toml` 文件中,添加以下依賴: ```toml - decimo = "==0.11.0" + decimo = "==0.12.0" ``` 然後運行 `pixi install` 來下載並安裝包。 @@ -82,6 +82,7 @@ channels = ["https://conda.modular.com/max", "https://repo.prefix.dev/modular-co | `decimo` | v0.9.0 | ==0.26.2 | pixi | | `decimo` | v0.10.0 | ==1.0.0b1 | pixi | | `decimo` | v0.11.0 | ==1.0.0b2 | pixi | +| `decimo` | v0.12.0 | ==1.0.0 | pixi | ## 快速開始 diff --git a/docs/user_manual.md b/docs/user_manual.md index 8c7008d..97dc793 100644 --- a/docs/user_manual.md +++ b/docs/user_manual.md @@ -42,6 +42,7 @@ from decimo.prelude import * - [Python Interoperability](#python-interoperability) - [A note on result exponents (`Decimal` and `Dec128`)](#a-note-on-result-exponents-decimal-and-dec128) - [Chinese Numerals](#chinese-numerals) + - [Expression Engine](#expression-engine) - [Appendix A — Import Paths](#appendix-a--import-paths) - [Appendix B — Traits Implemented](#appendix-b--traits-implemented) - [Appendix C — Complete API Tables](#appendix-c--complete-api-tables) @@ -63,7 +64,7 @@ pixi add decimo Or add it manually to `pixi.toml`: ```toml -decimo = "==0.11.0" +decimo = "==0.12.0" ``` Then run `pixi install`. @@ -1070,6 +1071,90 @@ _ = BInt("1" + String("0") * 20000).to_chinese() # raises ValueError print(Decimal("1E+20000").to_chinese(max_digits=0)) ``` +### Expression Engine + +`eval()` takes an arithmetic expression as a string and works it out with +`Decimal` arithmetic. It is the same engine that drives the `decimo` CLI +calculator: + +```mojo +from decimo import eval + +print(eval("100 + e * pi")) +# 108.53973422267356706546355086954657449503488853577 +print(eval("sqrt(2) + 1/3", precision=30)) +# 1.74754689570642838213502205754 +``` + +The result is rounded to `precision` significant digits, 50 by default, with +`rounding_mode`, half-even by default: + +```mojo +print(eval("1/3", precision=20)) +# 0.33333333333333333333 +print(eval("1/3", precision=5, rounding_mode=ROUND_CEILING)) +# 0.33334 +``` + +#### What you can write + +The operators are `+`, `-`, `*`, `/`, `^` (power), a unary minus, and +parentheses. The constants are `pi` and `e`. The functions are `sqrt`, `cbrt`, +`root(x, n)`, `ln`, `log(x, base)`, `log10`, `exp`, `sin`, `cos`, `tan`, `cot`, +`csc`, and `abs`. + +Names are case-sensitive: `pi` is the constant, `PI` is an unknown identifier. +(The CLI lower-cases each line before it reaches the engine, which is why `PI` +works there but not here.) Line breaks count as whitespace, so an expression +can span several lines: + +```mojo +print(eval(""" + 100 + 2 * 3 +""")) # 106 +``` + +#### Variables + +Pass a `Dict` and the expression can refer to your own values. Any identifier +that is not a built-in constant or function is looked up there: + +```mojo +from std.collections import Dict + +var vars = Dict[String, Decimal]() +vars["x"] = Decimal.from_string("10") +vars["y"] = Decimal.from_string("3") +print(eval("x^2 + y", variables=vars)) # 103 +``` + +#### Errors + +A syntax error, an unknown name, a division by zero, or a domain error raises, +and the message says where in the expression it went wrong: + +```mojo +try: + _ = eval("1 / 0") +except e: + print(e) # Error at position 2: division by zero +``` + +#### The individual stages + +`eval()` tokenizes, parses to reverse Polish notation, and evaluates. The three +stages are exported as well, in case you want to look at the tokens or the RPN +form: + +```mojo +from decimo.expression import tokenize, parse_to_rpn, evaluate_rpn + +var rpn = parse_to_rpn(tokenize("1 + 2 * 3")) +print(evaluate_rpn(rpn^, precision=50)) # 7 +``` + +`evaluate()` is an alias of `eval()`. + ### Appendix A — Import Paths ```mojo @@ -1094,6 +1179,11 @@ from decimo.numerals import ( decimal_string_to_chinese, MAX_CHINESE_NUMERAL_DIGITS, ) + +# Expression engine: `eval` at the top level, the individual stages in the +# `decimo.expression` sub-package +from decimo import eval +from decimo.expression import tokenize, parse_to_rpn, evaluate_rpn ``` ### Appendix B — Traits Implemented diff --git a/pixi.toml b/pixi.toml index 7675768..dbdd40b 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.11.0" +version = "0.12.0" [dependencies] # CLI argument parsing for the Decimo calculator. The modular-community diff --git a/src/decimo/__init__.mojo b/src/decimo/__init__.mojo index d8ac133..6f8168f 100644 --- a/src/decimo/__init__.mojo +++ b/src/decimo/__init__.mojo @@ -24,7 +24,7 @@ from decimo import Decimal, BInt, RoundingMode ``` """ -comptime DECIMO_VERSION = "0.10.0" +comptime DECIMO_VERSION = "0.12.0" """Canonical semantic version of the Decimo library (no leading `v`). Keep in sync with `pixi.toml`'s `[project].version`. This is the single @@ -34,7 +34,7 @@ should use `DECIMO_VERSION_TAG`. """ comptime DECIMO_VERSION_TAG = "v" + DECIMO_VERSION -"""Display version of the Decimo library, prefixed with `v` (e.g. `v0.10.0`). +"""Display version of the Decimo library, prefixed with `v` (e.g. `v0.12.0`). """ # Core types diff --git a/src/decimo/expression/__init__.mojo b/src/decimo/expression/__init__.mojo index c572bb4..cdec96b 100644 --- a/src/decimo/expression/__init__.mojo +++ b/src/decimo/expression/__init__.mojo @@ -34,7 +34,7 @@ evaluator) for advanced use: ```mojo from decimo.expression import tokenize, parse_to_rpn, evaluate_rpn -var rpn = parse_to_rpn(tokenize("1 + 2 * 3")^) +var rpn = parse_to_rpn(tokenize("1 + 2 * 3")) var value = evaluate_rpn(rpn^, precision=50) ``` """