diff --git a/README.md b/README.md index 56d59dd..5551ef9 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ DeciMojo provides an arbitrary-precision decimal and integer mathematics library The core types are: -- A 128-bit fixed-point decimal implementation (`Decimal`) supporting up to 29 significant digits with a maximum of 28 decimal places[^fixed]. It features a complete set of mathematical functions including logarithms, exponentiation, roots, etc. -- An arbitrary-precision decimal implementation `BigDecimal` allowing for calculations with unlimited digits and decimal places[^arbitrary]. - A base-10 arbitrary-precision signed integer type (`BigInt`) and a base-10 arbitrary-precision unsigned integer type (`BigUInt`) supporting unlimited digits[^integer]. It features comprehensive arithmetic operations, comparison functions, and supports extremely large integer calculations efficiently. +- An arbitrary-precision decimal implementation `BigDecimal` allowing for calculations with unlimited digits and decimal places[^arbitrary]. +- A 128-bit fixed-point decimal implementation (`Decimal`) supporting up to 29 significant digits with a maximum of 28 decimal places[^fixed]. It features a complete set of mathematical functions including logarithms, exponentiation, roots, etc. This repository includes [TOMLMojo](https://github.com/forfudan/decimojo/tree/main/src/tomlmojo), a lightweight TOML parser in pure Mojo. It parses configuration files and test data, supporting basic types, arrays, and nested tables. While created for DeciMojo's testing framework, it offers general-purpose structured data parsing with a clean, simple API. @@ -39,7 +39,7 @@ DeciMojo is available in the [modular-community](https://repo.prefix.dev/modular From the `pixi` CLI, simply run ```pixi add decimojo```. This fetches the latest version and makes it immediately available for import. -For projects with a `mojoproject.toml`file, add the dependency ```decimojo = "==0.4.0"```. Then run `pixi install` to download and install the package. +For projects with a `mojoproject.toml`file, add the dependency ```decimojo = "==0.4.1"```. Then run `pixi install` to download and install the package. For the latest development version, clone the [GitHub repository](https://github.com/forfudan/decimojo) and build the package locally. @@ -49,11 +49,11 @@ For the latest development version, clone the [GitHub repository](https://github | v0.2.0 | ==25.2 | magic | | v0.3.0 | ==25.2 | magic | | v0.3.1 | >=25.2, <25.4 | pixi | -| v0.4.0 | ==25.4 | pixi | +| v0.4.x | ==25.4 | pixi | ## Quick start -Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` type. +Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` type. Note that Mojo does not support global variables at the moment, so we need to pass the `precision` parameter explicitly to each function call. In future, we will add a global precision setting with the default value of, *e.g.*, `28`, to avoid passing it around. ```mojo from decimojo import BDec, RM @@ -218,7 +218,7 @@ If you find DeciMojo useful for your research, consider listing it in your citat year = {2025}, title = {An arbitrary-precision decimal and integer mathematics library for Mojo}, url = {https://github.com/forfudan/decimojo}, - version = {0.4.0}, + version = {0.4.1}, note = {Computer Software} } ``` diff --git a/docs/changelog.md b/docs/changelog.md index a8371de..31b22a8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,85 @@ # DeciMojo changelog -This is a list of RELEASED changes for the DeciMojo Package. For the unreleased changes, please refer to **[changelog_unreleased](https://zhuyuhao.com/decimojo/docs/changelog_unreleased.html)**. +This is a list of RELEASED changes for the DeciMojo Package. + +## 01/07/2025 (v0.4.1) + +Version 0.4.1 of DeciMojo introduces implicit type conversion between built-in integral types and arbitrary-precision types. + +### ⭐️ New + +Now DeciMojo supports implicit type conversion between built-in integeral types (`Int`, `UInt`, `Int8`, `UInt8`, `Int16`, `UInt16`, `Int32`, `UInt32`, `Int64`, `UInt64`, `Int128`,`UInt128`, `Int256`, and `UInt256`) and the arbitrary-precision integer types (`BigUInt`, `BigInt`, and `BigDecimal`). This allows you to use these built-in types directly in arithmetic operations with `BigInt` and `BigUInt` without explicit conversion. The merged type will always be the most compatible one (PR #89, PR #90). + +For example, you can now do the following: + +```mojo +from decimojo.prelude import * + +fn main() raises: + var a = BInt(Int256(-1234567890)) + var b = BigUInt(31415926) + var c = BDec("3.14159265358979323") + + print("a =", a) + print("b =", b) + print("c =", c) + + print(a * b) # Merged to BInt + print(a + c) # Merged to BDec + print(b + c) # Merged to BDec + print(a * Int(-128)) # Merged to BInt + print(b * UInt(8)) # Merged to BUInt + print(c * Int256(987654321123456789)) # Merged to BDec + + var lst = [a, b, c, UInt8(255), Int64(22222), UInt256(1234567890)] + # The list is of the type `List[BigDecimal]` + for i in lst: + print(i, end=", ") +``` + +Running the code will give your the following results: + +```console +a = -1234567890 +b = 31415926 +c = 3.14159265358979323 +-38785093474216140 +-1234567886.85840734641020677 +31415929.14159265358979323 +158024689920 +251327408 +3102807559527666386.46423202534973847 +-1234567890, 31415926, 3.14159265358979323, 255, 22222, 1234567890, +``` + +### 🦋 Changed + +Optimize the case when you increase the value of a `BigInt` object in-place by 1, *i.e.*, `i += 1`. This allows you to iterate faster (PR #89). For example, we can compute the time taken to iterate from `0` to `1_000_000` using `BigInt` and compare it with the built-in `Int` type: + +```mojo +from decimojo.prelude import * + +fn main() raises: + i = BigInt(0) + end = BigInt(1_000_000) + while i < end: + print(i) + i += 1 +``` + +| scenario | Time taken | +| --------------- | ---------- | +| v0.4.0 `BigInt` | 1.102s | +| v0.4.1 `BigInt` | 0.912s | +| Built-in `Int` | 0.893s | + +### 🛠️ Fixed + +Fix a bug in `BigDecimal` where it cannot create a correct value from a integral scalar, e.g., `BDec(UInt16(0))` returns an unitialized `BigDecimal` object (PR #89). + +### 📚 Documentation and testing + +Update the `tests` module and refactor the test files for `BigUInt` (PR #88). ## 25/06/2025 (v0.4.0) diff --git a/docs/changelog_unreleased.md b/docs/changelog_unreleased.md deleted file mode 100644 index 6c0888d..0000000 --- a/docs/changelog_unreleased.md +++ /dev/null @@ -1,3 +0,0 @@ -# DeciMojo unreleased changelog - -This is a list of UNRELEASED changes for the DeciMojo Package. For the released changes, please refer to **[changelog](https://zhuyuhao.com/decimojo/docs/changelog.html)**. diff --git a/docs/readme_zht.md b/docs/readme_zht.md index 0051d53..d4a5c30 100644 --- a/docs/readme_zht.md +++ b/docs/readme_zht.md @@ -20,7 +20,7 @@ DeciMojo 可在 [modular-community](https://repo.prefix.dev/modular-community) 從 `pixi` CLI,只需運行 ```pixi add decimojo```。這會獲取最新版本並使其立即可用於導入。 -對於帶有 `mojoproject.toml` 文件的項目,添加依賴 ```decimojo = "==0.4.0"```。然後運行 `pixi install` 來下載並安裝包。 +對於帶有 `mojoproject.toml` 文件的項目,添加依賴 ```decimojo = "==0.4.1"```。然後運行 `pixi install` 來下載並安裝包。 如需最新的開發版本,請克隆 [GitHub 倉庫](https://github.com/forfudan/decimojo) 並在本地構建包。 @@ -30,7 +30,7 @@ DeciMojo 可在 [modular-community](https://repo.prefix.dev/modular-community) | v0.2.0 | >=25.2 | magic | | v0.3.0 | >=25.2 | magic | | v0.3.1 | >=25.2, <25.4 | pixi | -| v0.4.0 | ==25.4 | pixi | +| v0.4.x | ==25.4 | pixi | ## 快速入門 @@ -218,7 +218,7 @@ DeciMojo 相較於 Python 的 `decimal` 模塊提供了卓越的性能,同時 year = {2025}, title = {DeciMojo: A fixed-point decimal arithmetic library in Mojo}, url = {https://github.com/forfudan/decimojo}, - version = {0.4.0}, + version = {0.4.1}, note = {Computer Software} } ``` diff --git a/pixi.toml b/pixi.toml index e64af3c..8bce78e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" name = "decimojo" platforms = ["osx-arm64", "linux-64"] readme = "README.md" -version = "0.4.0" +version = "0.4.1" [dependencies] max = "==25.4" diff --git a/src/decimojo/bigdecimal/bigdecimal.mojo b/src/decimojo/bigdecimal/bigdecimal.mojo index 3d63926..1b38f14 100644 --- a/src/decimojo/bigdecimal/bigdecimal.mojo +++ b/src/decimojo/bigdecimal/bigdecimal.mojo @@ -114,6 +114,13 @@ struct BigDecimal( """ self = Self.from_int(value) + @implicit + fn __init__(out self, value: UInt): + """Constructs a BigDecimal from an `UInt` object. + See `from_uint()` for more information. + """ + self = Self.from_uint(value) + @implicit fn __init__(out self, value: Scalar): """Constructs a BigDecimal from an integral scalar. @@ -179,6 +186,11 @@ struct BigDecimal( return Self(coefficient=BigUInt(words^), scale=0, sign=sign) + @staticmethod + fn from_uint(value: Int) -> Self: + """Creates a BigDecimal from an unsigned integer.""" + return Self(coefficient=BigUInt.from_uint(value), scale=0, sign=False) + @staticmethod fn from_integral_scalar[dtype: DType, //](value: SIMD[dtype, 1]) -> Self: """Initializes a BigDecimal from an integral scalar. diff --git a/src/decimojo/bigint/arithmetics.mojo b/src/decimojo/bigint/arithmetics.mojo index 13bae1b..33cc7a5 100644 --- a/src/decimojo/bigint/arithmetics.mojo +++ b/src/decimojo/bigint/arithmetics.mojo @@ -52,6 +52,22 @@ fn add(x1: BigInt, x2: BigInt) raises -> BigInt: return BigInt(magnitude^, sign=x1.sign) +fn add_inplace(mut x1: BigInt, x2: BigInt) raises -> None: + """Increments a BigInt number by another BigInt number in place. + + Args: + x1: The first BigInt operand. + x2: The second BigInt operand. + """ + + # If signs are different, delegate to `subtract` + if x1.sign != x2.sign: + x1 = subtract(x1, -x2) + + # Same sign: add magnitudes in place + x1.magnitude += x2.magnitude + + fn subtract(x1: BigInt, x2: BigInt) raises -> BigInt: """Returns the difference of two numbers. diff --git a/src/decimojo/bigint/bigint.mojo b/src/decimojo/bigint/bigint.mojo index 2d5f7b8..342a5bf 100644 --- a/src/decimojo/bigint/bigint.mojo +++ b/src/decimojo/bigint/bigint.mojo @@ -149,6 +149,13 @@ struct BigInt(Absable, IntableRaising, Representable, Stringable, Writable): """ self = Self.from_int(value) + @implicit + fn __init__(out self, value: UInt): + """Initializes a BigInt from an `UInt` object. + See `from_uint()` for more information. + """ + self = Self.from_uint(value) + @implicit fn __init__(out self, value: Scalar): """Constructs a BigInt from an integral scalar. @@ -164,7 +171,6 @@ struct BigInt(Absable, IntableRaising, Representable, Stringable, Writable): # # from_words(*words: UInt32, sign: Bool) -> Self # from_int(value: Int) -> Self - # from_uint128(value: UInt128, sign: Bool = False) -> Self # from_string(value: String) -> Self # ===------------------------------------------------------------------=== # @@ -251,6 +257,11 @@ struct BigInt(Absable, IntableRaising, Representable, Stringable, Writable): return Self(BigUInt(words^), sign) + @staticmethod + fn from_uint(value: UInt) -> Self: + """Creates a BigInt from an unsignd integer.""" + return Self(magnitude=BigUInt.from_uint(value), sign=False) + @staticmethod fn from_integral_scalar[dtype: DType, //](value: SIMD[dtype, 1]) -> Self: """Initializes a BigInt from an integral scalar. @@ -448,54 +459,26 @@ struct BigInt(Absable, IntableRaising, Representable, Stringable, Writable): fn __add__(self, other: Self) raises -> Self: return decimojo.bigint.arithmetics.add(self, other) - @always_inline - fn __add__(self, other: Int) raises -> Self: - return decimojo.bigint.arithmetics.add(self, Self.from_int(other)) - @always_inline fn __sub__(self, other: Self) raises -> Self: return decimojo.bigint.arithmetics.subtract(self, other) - @always_inline - fn __sub__(self, other: Int) raises -> Self: - return decimojo.bigint.arithmetics.subtract(self, Self.from_int(other)) - @always_inline fn __mul__(self, other: Self) raises -> Self: return decimojo.bigint.arithmetics.multiply(self, other) - @always_inline - fn __mul__(self, other: Int) raises -> Self: - return decimojo.bigint.arithmetics.multiply(self, Self.from_int(other)) - @always_inline fn __floordiv__(self, other: Self) raises -> Self: return decimojo.bigint.arithmetics.floor_divide(self, other) - @always_inline - fn __floordiv__(self, other: Int) raises -> Self: - return decimojo.bigint.arithmetics.floor_divide( - self, Self.from_int(other) - ) - @always_inline fn __mod__(self, other: Self) raises -> Self: return decimojo.bigint.arithmetics.floor_modulo(self, other) - @always_inline - fn __mod__(self, other: Int) raises -> Self: - return decimojo.bigint.arithmetics.floor_modulo( - self, Self.from_int(other) - ) - @always_inline fn __pow__(self, exponent: Self) raises -> Self: return self.power(exponent) - @always_inline - fn __pow__(self, exponent: Int) raises -> Self: - return self.power(exponent) - # ===------------------------------------------------------------------=== # # Basic binary right-side arithmetic operation dunders # These methods are called to implement the binary arithmetic operations @@ -535,48 +518,32 @@ struct BigInt(Absable, IntableRaising, Representable, Stringable, Writable): @always_inline fn __iadd__(mut self, other: Self) raises: - self = decimojo.bigint.arithmetics.add(self, other) + decimojo.bigint.arithmetics.add_inplace(self, other) @always_inline fn __iadd__(mut self, other: Int) raises: - self = decimojo.bigint.arithmetics.add(self, Self.from_int(other)) + # Optimize the case `i += 1` + if other == 1: + self.magnitude.add_inplace_by_1() + else: + decimojo.bigint.arithmetics.add_inplace(self, other) @always_inline fn __isub__(mut self, other: Self) raises: self = decimojo.bigint.arithmetics.subtract(self, other) - @always_inline - fn __isub__(mut self, other: Int) raises: - self = decimojo.bigint.arithmetics.subtract(self, Self.from_int(other)) - @always_inline fn __imul__(mut self, other: Self) raises: self = decimojo.bigint.arithmetics.multiply(self, other) - @always_inline - fn __imul__(mut self, other: Int) raises: - self = decimojo.bigint.arithmetics.multiply(self, Self.from_int(other)) - @always_inline fn __ifloordiv__(mut self, other: Self) raises: self = decimojo.bigint.arithmetics.floor_divide(self, other) - @always_inline - fn __ifloordiv__(mut self, other: Int) raises: - self = decimojo.bigint.arithmetics.floor_divide( - self, Self.from_int(other) - ) - @always_inline fn __imod__(mut self, other: Self) raises: self = decimojo.bigint.arithmetics.floor_modulo(self, other) - @always_inline - fn __imod__(mut self, other: Int) raises: - self = decimojo.bigint.arithmetics.floor_modulo( - self, Self.from_int(other) - ) - # ===------------------------------------------------------------------=== # # Basic binary comparison operation dunders # __gt__, __ge__, __lt__, __le__, __eq__, __ne__ diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index 53dcb95..ebf5466 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -137,6 +137,13 @@ struct BigUInt(Absable, IntableRaising, Stringable, Writable): """ self = Self.from_int(value) + @implicit + fn __init__(out self, value: UInt): + """Initializes a BigUInt from an Int. + See `from_uint()` for more information. + """ + self = Self.from_uint(value) + @implicit fn __init__(out self, value: Scalar): """Initializes a BigUInt from an unsigned integral scalar. @@ -243,6 +250,24 @@ struct BigUInt(Absable, IntableRaising, Stringable, Writable): return Self(list_of_words^) + @staticmethod + fn from_uint(value: UInt) -> Self: + """Creates a BigUInt from an `UInt` object.""" + if value == 0: + return Self() + + var list_of_words = List[UInt32]() + var remainder: Int = value + var quotient: Int + + while remainder != 0: + quotient = remainder // 1_000_000_000 + remainder = remainder % 1_000_000_000 + list_of_words.append(UInt32(remainder)) + remainder = quotient + + return Self(list_of_words^) + @staticmethod fn from_unsigned_integral_scalar[ dtype: DType, // @@ -670,6 +695,40 @@ struct BigUInt(Absable, IntableRaising, Stringable, Writable): fn __pow__(self, exponent: Int) raises -> Self: return self.power(exponent) + # ===------------------------------------------------------------------=== # + # Basic binary right-side arithmetic operation dunders + # These methods are called to implement the binary arithmetic operations + # (+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |) + # ===------------------------------------------------------------------=== # + + @always_inline + fn __radd__(self, other: Self) raises -> Self: + return decimojo.biguint.arithmetics.add(self, other) + + @always_inline + fn __rsub__(self, other: Self) raises -> Self: + return decimojo.biguint.arithmetics.subtract(other, self) + + @always_inline + fn __rmul__(self, other: Self) raises -> Self: + return decimojo.biguint.arithmetics.multiply(self, other) + + @always_inline + fn __rfloordiv__(self, other: Self) raises -> Self: + return decimojo.biguint.arithmetics.floor_divide(other, self) + + @always_inline + fn __rmod__(self, other: Self) raises -> Self: + return decimojo.biguint.arithmetics.floor_modulo(other, self) + + @always_inline + fn __rdivmod__(self, other: Self) raises -> Tuple[Self, Self]: + return decimojo.biguint.arithmetics.divmod(other, self) + + @always_inline + fn __rpow__(self, base: Self) raises -> Self: + return base.power(self) + # ===------------------------------------------------------------------=== # # Basic binary augmented arithmetic assignments dunders # These methods are called to implement the binary augmented arithmetic