Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
8 changes: 1 addition & 7 deletions docs/readme_zht.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 版本授權。
243 changes: 132 additions & 111 deletions src/decimojo/decimal.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -66,7 +38,7 @@ from decimojo.rounding_mode import RoundingMode
import decimojo.utility


@register_passable
@register_passable("trivial")
struct Decimal(
Absable,
Comparable,
Expand All @@ -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).
Expand All @@ -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."""
Expand Down Expand Up @@ -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
# ===------------------------------------------------------------------=== #
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/decimojo/rounding_mode.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
# limitations under the License.
# ===----------------------------------------------------------------------=== #

"""Implements the RoundingMode for different rounding modes.
"""


struct RoundingMode:
"""
Expand All @@ -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
Expand Down
Loading