diff --git a/README.md b/README.md index 3272d24..dbf6543 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A fixed-point decimal arithmetic library implemented in [the Mojo programming language 🔥](https://www.modular.com/mojo). -**[中文·漢字»](./docs/readme_zht)** +**[中文·漢字»](./docs/readme_zht.md)** ## Overview @@ -278,10 +278,4 @@ If you find DeciMojo useful for your research, consider listing it in your citat ## License -Copyright 2025 Yuhao Zhu - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - -[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +This repository and its contributions are licensed under the Apache License v2.0. diff --git a/docs/readme_zht.md b/docs/readme_zht.md index e9e47de..d2e04fd 100644 --- a/docs/readme_zht.md +++ b/docs/readme_zht.md @@ -282,10 +282,4 @@ DeciMojo 結合了 "Decimal" 和 "Mojo" 兩詞,反映了其目的(小數算 ## 許可證 -版權所有 2025 Yuhao Zhu - -根據 Apache 許可證 2.0 版("許可證")獲得許可;除非符合許可證要求,否則您不得使用此文件。您可以在以下位置獲取許可證副本: - -[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) - -除非適用法律要求或書面同意,否則依據許可證分發的軟件是基於"按原樣"分發的,没有任何形式的明示或暗示擔保或條件。請參閲許可證以了解特定語言下的權限和限制。 +本倉庫及其所有貢獻内容均採用 Apache 許可證 2.0 版本授權。 diff --git a/src/decimojo/decimal.mojo b/src/decimojo/decimal.mojo index 3c57bee..85838d4 100644 --- a/src/decimojo/decimal.mojo +++ b/src/decimojo/decimal.mojo @@ -16,42 +16,14 @@ # See the License for the specific language governing permissions and # limitations under the License. # ===----------------------------------------------------------------------=== # -# -# Implements basic object methods for the Decimal type -# which supports correctly-rounded, fixed-point arithmetic. -# -# ===----------------------------------------------------------------------=== # -# -# Organization of files and methods of Decimal: -# - Internal representation fields -# - Constants (aliases) -# - Special values (methods) -# - Constructors and life time methods -# - Constructing methods that are not dunders -# - Output dunders, type-transfer dunders, and other type-transfer methods -# - Basic unary arithmetic operation dunders -# - Basic binary arithmetic operation dunders -# - Basic binary arithmetic operation dunders with reflected operands -# - Basic binary augmented arithmetic operation dunders -# - Basic comparison operation dunders -# - Other dunders that implements traits -# - Mathematical methods that do not implement a trait (not a dunder) -# - Other methods -# - Internal methods -# -# ===----------------------------------------------------------------------=== # -# Docstring style: -# 1. Description -# 2. Parameters -# 3. Args -# 4. Constraints -# 4) Returns -# 5) Raises -# 9) Examples -# ===----------------------------------------------------------------------=== # -""" -Implements basic object methods for working with decimal numbers. +"""Implements basic object methods for the Decimal type. + +This module contains the basic object methods for the Decimal type. +These methods include constructors, life time methods, output dunders, +type-transfer dunders, basic arithmetic operation dunders, comparison +operation dunders, and other dunders that implement traits, as well as +mathematical methods that do not implement a trait. """ from memory import UnsafePointer @@ -66,7 +38,7 @@ from decimojo.rounding_mode import RoundingMode import decimojo.utility -@register_passable +@register_passable("trivial") struct Decimal( Absable, Comparable, @@ -75,11 +47,12 @@ struct Decimal( Roundable, Writable, ): - """ - Correctly-rounded fixed-precision number. + """Represents a fixed-point decimal number with 96-bit precision. + + Notes: + + Internal Representation: - Internal Representation - ----------------------- Each decimal uses a 128-bit on memory, where: - 96 bits for the coefficient (significand), which is 96-bit unsigned integers stored as three 32 bit integer (little-endian). @@ -97,12 +70,31 @@ struct Decimal( The value of the coefficient is: `high * 2**64 + mid * 2**32 + low` The final value is: `(-1)**sign * coefficient * 10**(-scale)` - Reference - --------- + Reference: + - General Decimal Arithmetic Specification Version 1.70 – 7 Apr 2009 (https://speleotrove.com/decimal/decarith.html) - https://learn.microsoft.com/en-us/dotnet/api/system.decimal.getbits?view=net-9.0&redirectedfrom=MSDN#System_Decimal_GetBits_System_Decimal_ """ + # ===------------------------------------------------------------------=== # + # Organization of fields and methods: + # - Internal representation fields + # - Constants (aliases) + # - Special values (methods) + # - Constructors and life time methods + # - Constructing methods that are not dunders + # - Output dunders, type-transfer dunders, and other type-transfer methods + # - Basic unary arithmetic operation dunders + # - Basic binary arithmetic operation dunders + # - Basic binary arithmetic operation dunders with reflected operands + # - Basic binary augmented arithmetic operation dunders + # - Basic comparison operation dunders + # - Other dunders that implements traits + # - Mathematical methods that do not implement a trait (not a dunder) + # - Other methods + # - Internal methods + # ===------------------------------------------------------------------=== # + # Internal representation fields var low: UInt32 """Least significant 32 bits of coefficient.""" @@ -280,13 +272,6 @@ struct Decimal( except e: raise Error("Error in `Decimal__init__()` with Float64: ", e) - fn __copyinit__(out self, other: Self): - """Initializes a Decimal by copying another Decimal.""" - self.low = other.low - self.mid = other.mid - self.high = other.high - self.flags = other.flags - # ===------------------------------------------------------------------=== # # Constructing methods that are not dunders # ===------------------------------------------------------------------=== # @@ -917,45 +902,9 @@ struct Decimal( fn __str__(self) -> String: """Returns string representation of the Decimal. - Preserves trailing zeros after decimal point to match the scale. + See `to_str()` for more information. """ - # Get the coefficient as a string (absolute value) - var coef = String(self.coefficient()) - var scale = self.scale() - var result: String - - # Handle zero as a special case - if coef == "0": - if scale == 0: - result = "0" - else: - result = "0." + "0" * scale - - # For non-zero values, format according to scale - elif scale == 0: - # No decimal places needed - result = coef - elif scale >= len(coef): - # Need leading zeros after decimal point - result = "0." + "0" * (scale - len(coef)) + coef - else: - # Insert decimal point at appropriate position - var insert_pos = len(coef) - scale - result = coef[:insert_pos] + "." + coef[insert_pos:] - - # Ensure we have exactly 'scale' digits after decimal point - var decimal_point_pos = result.find(".") - var current_decimals = len(result) - decimal_point_pos - 1 - - if current_decimals < scale: - # Add trailing zeros if needed - result += "0" * (scale - current_decimals) - - # Add negative sign if needed - if self.is_negative(): - result = "-" + result - - return result + return self.to_str() fn __repr__(self) -> String: """Returns a string representation of the Decimal.""" @@ -1071,6 +1020,102 @@ struct Decimal( return res + fn to_str(self) -> String: + """Returns string representation of the Decimal. + Preserves trailing zeros after decimal point to match the scale. + """ + # Get the coefficient as a string (absolute value) + var coef = String(self.coefficient()) + var scale = self.scale() + var result: String + + # Handle zero as a special case + if coef == "0": + if scale == 0: + result = "0" + else: + result = "0." + "0" * scale + + # For non-zero values, format according to scale + elif scale == 0: + # No decimal places needed + result = coef + elif scale >= len(coef): + # Need leading zeros after decimal point + result = "0." + "0" * (scale - len(coef)) + coef + else: + # Insert decimal point at appropriate position + var insert_pos = len(coef) - scale + result = coef[:insert_pos] + "." + coef[insert_pos:] + + # Ensure we have exactly 'scale' digits after decimal point + var decimal_point_pos = result.find(".") + var current_decimals = len(result) - decimal_point_pos - 1 + + if current_decimals < scale: + # Add trailing zeros if needed + result += "0" * (scale - current_decimals) + + # Add negative sign if needed + if self.is_negative(): + result = "-" + result + + return result + + fn to_str_scientific(self) raises -> String: + """Returns a string representation of this Decimal in scientific notation. + + Returns: + A string representation of this Decimal in scientific notation. + + Raises: + Error: If significant_digits is not between 1 and 28. + + Notes: + + Scientific notation format: M.NNNNe±XX where: + - M is the first significant digit. + - NNNN is the remaining significant digits. + - ±XX is the exponent. + """ + var scale: Int = self.scale() + var coef = self.coefficient() + + # Special case: zero + if self.is_zero(): + if scale == 0: + return String("0") + else: + return String("0E-") + String("0") * self.scale() + + while coef % 10 == 0: + coef = coef // 10 + scale -= 1 + + # 0.00100: coef=100, scale=5 + # => 0.001: coef=1, scale=3, ndigits_fractional_part=0 + # => 1.0e-3: coef=1, exponent=-3 + var ndigits_coef = decimojo.utility.number_of_digits(coef) + var ndigits_fractional_part = ndigits_coef - 1 + var exponent = ndigits_fractional_part - scale + + # Format in scientific notation: + # sign, first digit, decimal point, remaining digits + var coef_str = String(coef) + var result: String = String("-") if self.is_negative() else String("") + if len(coef_str) == 1: + result = result + coef_str + String(".0") + else: + result = result + coef_str[0] + String(".") + coef_str[1:] + + # Add exponent (E+XX or E-XX) + if exponent >= 0: + result += "E+" + String(exponent) + else: + result += "E" + String(exponent) + + return result + # ===------------------------------------------------------------------=== # # Basic unary operation dunders # neg @@ -1166,48 +1211,24 @@ struct Decimal( # (+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |) # ===------------------------------------------------------------------=== # - fn __radd__(self, other: Float64) raises -> Self: - try: - return decimojo.arithmetics.add(Decimal(other), self) - except e: - raise Error("Error in `__radd__()`: ", e) - fn __radd__(self, other: Int) raises -> Self: try: return decimojo.arithmetics.add(Decimal(other), self) except e: raise Error("Error in `__radd__()`: ", e) - fn __rsub__(self, other: Float64) raises -> Self: - try: - return decimojo.arithmetics.subtract(Decimal(other), self) - except e: - raise Error("Error in `__rsub__()`: ", e) - fn __rsub__(self, other: Int) raises -> Self: try: return decimojo.arithmetics.subtract(Decimal(other), self) except e: raise Error("Error in `__rsub__()`: ", e) - fn __rmul__(self, other: Float64) raises -> Self: - try: - return decimojo.arithmetics.multiply(Decimal(other), self) - except e: - raise Error("Error in `__rmul__()`: ", e) - fn __rmul__(self, other: Int) raises -> Self: try: return decimojo.arithmetics.multiply(Decimal(other), self) except e: raise Error("Error in `__rmul__()`: ", e) - fn __rtruediv__(self, other: Float64) raises -> Self: - try: - return decimojo.arithmetics.true_divide(Decimal(other), self) - except e: - raise Error("Error in `__rtruediv__()`: ", e) - fn __rtruediv__(self, other: Int) raises -> Self: try: return decimojo.arithmetics.true_divide(Decimal(other), self) diff --git a/src/decimojo/rounding_mode.mojo b/src/decimojo/rounding_mode.mojo index 4c28163..e424bcc 100644 --- a/src/decimojo/rounding_mode.mojo +++ b/src/decimojo/rounding_mode.mojo @@ -17,6 +17,9 @@ # limitations under the License. # ===----------------------------------------------------------------------=== # +"""Implements the RoundingMode for different rounding modes. +""" + struct RoundingMode: """ @@ -27,6 +30,11 @@ struct RoundingMode: - HALF_UP: Round away from zero if >= 0.5 - HALF_EVEN: Round to nearest even digit if equidistant (banker's rounding) - UP: Round away from zero + + Notes: + + Currently, enum is not available in Mojo. This module provides a workaround + to define a custom enum-like class for rounding modes. """ # alias