Skip to content

Commit c74f5d8

Browse files
authored
[decimal] Make Decimal type trivial + Add to_str_scientific() method (#42)
This pull request includes several changes to the `Decimal` type: ### Code simplification and refactoring: * `src/decimojo/decimal.mojo`: Removed redundant comments and reorganized the docstring for the `Decimal` struct to improve clarity. * `src/decimojo/decimal.mojo`: Made `Decimal` a trivial type. Removed the `__copyinit__` method. * `src/decimojo/decimal.mojo`: Removed several reverse arithmetic operation dunders (`__radd__`, `__rsub__`, `__rmul__`, `__rtruediv__`) for Float64 because it is not exact. * `src/decimojo/decimal.mojo`: Moved the string representation logic from the `__str__` method to a new `to_str` method and added a `to_str_scientific` method for scientific notation. ### Enhancements to rounding mode: * `src/decimojo/rounding_mode.mojo`: Added a docstring to the `RoundingMode` struct and included notes about the current workaround for defining custom enum-like classes in Mojo.
1 parent 7448804 commit c74f5d8

File tree

4 files changed

+143
-126
lines changed

4 files changed

+143
-126
lines changed

README.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A fixed-point decimal arithmetic library implemented in [the Mojo programming language 🔥](https://www.modular.com/mojo).
44

5-
**[中文·漢字»](./docs/readme_zht)**
5+
**[中文·漢字»](./docs/readme_zht.md)**
66

77
## Overview
88

@@ -278,10 +278,4 @@ If you find DeciMojo useful for your research, consider listing it in your citat
278278

279279
## License
280280

281-
Copyright 2025 Yuhao Zhu
282-
283-
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
284-
285-
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
286-
287-
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.
281+
This repository and its contributions are licensed under the Apache License v2.0.

docs/readme_zht.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,4 @@ DeciMojo 結合了 "Decimal" 和 "Mojo" 兩詞,反映了其目的(小數算
282282

283283
## 許可證
284284

285-
版權所有 2025 Yuhao Zhu
286-
287-
根據 Apache 許可證 2.0 版("許可證")獲得許可;除非符合許可證要求,否則您不得使用此文件。您可以在以下位置獲取許可證副本:
288-
289-
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
290-
291-
除非適用法律要求或書面同意,否則依據許可證分發的軟件是基於"按原樣"分發的,没有任何形式的明示或暗示擔保或條件。請參閲許可證以了解特定語言下的權限和限制。
285+
本倉庫及其所有貢獻内容均採用 Apache 許可證 2.0 版本授權。

src/decimojo/decimal.mojo

Lines changed: 132 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,14 @@
1616
# See the License for the specific language governing permissions and
1717
# limitations under the License.
1818
# ===----------------------------------------------------------------------=== #
19-
#
20-
# Implements basic object methods for the Decimal type
21-
# which supports correctly-rounded, fixed-point arithmetic.
22-
#
23-
# ===----------------------------------------------------------------------=== #
24-
#
25-
# Organization of files and methods of Decimal:
26-
# - Internal representation fields
27-
# - Constants (aliases)
28-
# - Special values (methods)
29-
# - Constructors and life time methods
30-
# - Constructing methods that are not dunders
31-
# - Output dunders, type-transfer dunders, and other type-transfer methods
32-
# - Basic unary arithmetic operation dunders
33-
# - Basic binary arithmetic operation dunders
34-
# - Basic binary arithmetic operation dunders with reflected operands
35-
# - Basic binary augmented arithmetic operation dunders
36-
# - Basic comparison operation dunders
37-
# - Other dunders that implements traits
38-
# - Mathematical methods that do not implement a trait (not a dunder)
39-
# - Other methods
40-
# - Internal methods
41-
#
42-
# ===----------------------------------------------------------------------=== #
43-
# Docstring style:
44-
# 1. Description
45-
# 2. Parameters
46-
# 3. Args
47-
# 4. Constraints
48-
# 4) Returns
49-
# 5) Raises
50-
# 9) Examples
51-
# ===----------------------------------------------------------------------=== #
5219

53-
"""
54-
Implements basic object methods for working with decimal numbers.
20+
"""Implements basic object methods for the Decimal type.
21+
22+
This module contains the basic object methods for the Decimal type.
23+
These methods include constructors, life time methods, output dunders,
24+
type-transfer dunders, basic arithmetic operation dunders, comparison
25+
operation dunders, and other dunders that implement traits, as well as
26+
mathematical methods that do not implement a trait.
5527
"""
5628

5729
from memory import UnsafePointer
@@ -66,7 +38,7 @@ from decimojo.rounding_mode import RoundingMode
6638
import decimojo.utility
6739

6840

69-
@register_passable
41+
@register_passable("trivial")
7042
struct Decimal(
7143
Absable,
7244
Comparable,
@@ -75,11 +47,12 @@ struct Decimal(
7547
Roundable,
7648
Writable,
7749
):
78-
"""
79-
Correctly-rounded fixed-precision number.
50+
"""Represents a fixed-point decimal number with 96-bit precision.
51+
52+
Notes:
53+
54+
Internal Representation:
8055
81-
Internal Representation
82-
-----------------------
8356
Each decimal uses a 128-bit on memory, where:
8457
- 96 bits for the coefficient (significand), which is 96-bit unsigned
8558
integers stored as three 32 bit integer (little-endian).
@@ -97,12 +70,31 @@ struct Decimal(
9770
The value of the coefficient is: `high * 2**64 + mid * 2**32 + low`
9871
The final value is: `(-1)**sign * coefficient * 10**(-scale)`
9972
100-
Reference
101-
---------
73+
Reference:
74+
10275
- General Decimal Arithmetic Specification Version 1.70 – 7 Apr 2009 (https://speleotrove.com/decimal/decarith.html)
10376
- https://learn.microsoft.com/en-us/dotnet/api/system.decimal.getbits?view=net-9.0&redirectedfrom=MSDN#System_Decimal_GetBits_System_Decimal_
10477
"""
10578

79+
# ===------------------------------------------------------------------=== #
80+
# Organization of fields and methods:
81+
# - Internal representation fields
82+
# - Constants (aliases)
83+
# - Special values (methods)
84+
# - Constructors and life time methods
85+
# - Constructing methods that are not dunders
86+
# - Output dunders, type-transfer dunders, and other type-transfer methods
87+
# - Basic unary arithmetic operation dunders
88+
# - Basic binary arithmetic operation dunders
89+
# - Basic binary arithmetic operation dunders with reflected operands
90+
# - Basic binary augmented arithmetic operation dunders
91+
# - Basic comparison operation dunders
92+
# - Other dunders that implements traits
93+
# - Mathematical methods that do not implement a trait (not a dunder)
94+
# - Other methods
95+
# - Internal methods
96+
# ===------------------------------------------------------------------=== #
97+
10698
# Internal representation fields
10799
var low: UInt32
108100
"""Least significant 32 bits of coefficient."""
@@ -280,13 +272,6 @@ struct Decimal(
280272
except e:
281273
raise Error("Error in `Decimal__init__()` with Float64: ", e)
282274

283-
fn __copyinit__(out self, other: Self):
284-
"""Initializes a Decimal by copying another Decimal."""
285-
self.low = other.low
286-
self.mid = other.mid
287-
self.high = other.high
288-
self.flags = other.flags
289-
290275
# ===------------------------------------------------------------------=== #
291276
# Constructing methods that are not dunders
292277
# ===------------------------------------------------------------------=== #
@@ -917,45 +902,9 @@ struct Decimal(
917902

918903
fn __str__(self) -> String:
919904
"""Returns string representation of the Decimal.
920-
Preserves trailing zeros after decimal point to match the scale.
905+
See `to_str()` for more information.
921906
"""
922-
# Get the coefficient as a string (absolute value)
923-
var coef = String(self.coefficient())
924-
var scale = self.scale()
925-
var result: String
926-
927-
# Handle zero as a special case
928-
if coef == "0":
929-
if scale == 0:
930-
result = "0"
931-
else:
932-
result = "0." + "0" * scale
933-
934-
# For non-zero values, format according to scale
935-
elif scale == 0:
936-
# No decimal places needed
937-
result = coef
938-
elif scale >= len(coef):
939-
# Need leading zeros after decimal point
940-
result = "0." + "0" * (scale - len(coef)) + coef
941-
else:
942-
# Insert decimal point at appropriate position
943-
var insert_pos = len(coef) - scale
944-
result = coef[:insert_pos] + "." + coef[insert_pos:]
945-
946-
# Ensure we have exactly 'scale' digits after decimal point
947-
var decimal_point_pos = result.find(".")
948-
var current_decimals = len(result) - decimal_point_pos - 1
949-
950-
if current_decimals < scale:
951-
# Add trailing zeros if needed
952-
result += "0" * (scale - current_decimals)
953-
954-
# Add negative sign if needed
955-
if self.is_negative():
956-
result = "-" + result
957-
958-
return result
907+
return self.to_str()
959908

960909
fn __repr__(self) -> String:
961910
"""Returns a string representation of the Decimal."""
@@ -1071,6 +1020,102 @@ struct Decimal(
10711020

10721021
return res
10731022

1023+
fn to_str(self) -> String:
1024+
"""Returns string representation of the Decimal.
1025+
Preserves trailing zeros after decimal point to match the scale.
1026+
"""
1027+
# Get the coefficient as a string (absolute value)
1028+
var coef = String(self.coefficient())
1029+
var scale = self.scale()
1030+
var result: String
1031+
1032+
# Handle zero as a special case
1033+
if coef == "0":
1034+
if scale == 0:
1035+
result = "0"
1036+
else:
1037+
result = "0." + "0" * scale
1038+
1039+
# For non-zero values, format according to scale
1040+
elif scale == 0:
1041+
# No decimal places needed
1042+
result = coef
1043+
elif scale >= len(coef):
1044+
# Need leading zeros after decimal point
1045+
result = "0." + "0" * (scale - len(coef)) + coef
1046+
else:
1047+
# Insert decimal point at appropriate position
1048+
var insert_pos = len(coef) - scale
1049+
result = coef[:insert_pos] + "." + coef[insert_pos:]
1050+
1051+
# Ensure we have exactly 'scale' digits after decimal point
1052+
var decimal_point_pos = result.find(".")
1053+
var current_decimals = len(result) - decimal_point_pos - 1
1054+
1055+
if current_decimals < scale:
1056+
# Add trailing zeros if needed
1057+
result += "0" * (scale - current_decimals)
1058+
1059+
# Add negative sign if needed
1060+
if self.is_negative():
1061+
result = "-" + result
1062+
1063+
return result
1064+
1065+
fn to_str_scientific(self) raises -> String:
1066+
"""Returns a string representation of this Decimal in scientific notation.
1067+
1068+
Returns:
1069+
A string representation of this Decimal in scientific notation.
1070+
1071+
Raises:
1072+
Error: If significant_digits is not between 1 and 28.
1073+
1074+
Notes:
1075+
1076+
Scientific notation format: M.NNNNe±XX where:
1077+
- M is the first significant digit.
1078+
- NNNN is the remaining significant digits.
1079+
- ±XX is the exponent.
1080+
"""
1081+
var scale: Int = self.scale()
1082+
var coef = self.coefficient()
1083+
1084+
# Special case: zero
1085+
if self.is_zero():
1086+
if scale == 0:
1087+
return String("0")
1088+
else:
1089+
return String("0E-") + String("0") * self.scale()
1090+
1091+
while coef % 10 == 0:
1092+
coef = coef // 10
1093+
scale -= 1
1094+
1095+
# 0.00100: coef=100, scale=5
1096+
# => 0.001: coef=1, scale=3, ndigits_fractional_part=0
1097+
# => 1.0e-3: coef=1, exponent=-3
1098+
var ndigits_coef = decimojo.utility.number_of_digits(coef)
1099+
var ndigits_fractional_part = ndigits_coef - 1
1100+
var exponent = ndigits_fractional_part - scale
1101+
1102+
# Format in scientific notation:
1103+
# sign, first digit, decimal point, remaining digits
1104+
var coef_str = String(coef)
1105+
var result: String = String("-") if self.is_negative() else String("")
1106+
if len(coef_str) == 1:
1107+
result = result + coef_str + String(".0")
1108+
else:
1109+
result = result + coef_str[0] + String(".") + coef_str[1:]
1110+
1111+
# Add exponent (E+XX or E-XX)
1112+
if exponent >= 0:
1113+
result += "E+" + String(exponent)
1114+
else:
1115+
result += "E" + String(exponent)
1116+
1117+
return result
1118+
10741119
# ===------------------------------------------------------------------=== #
10751120
# Basic unary operation dunders
10761121
# neg
@@ -1166,48 +1211,24 @@ struct Decimal(
11661211
# (+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |)
11671212
# ===------------------------------------------------------------------=== #
11681213

1169-
fn __radd__(self, other: Float64) raises -> Self:
1170-
try:
1171-
return decimojo.arithmetics.add(Decimal(other), self)
1172-
except e:
1173-
raise Error("Error in `__radd__()`: ", e)
1174-
11751214
fn __radd__(self, other: Int) raises -> Self:
11761215
try:
11771216
return decimojo.arithmetics.add(Decimal(other), self)
11781217
except e:
11791218
raise Error("Error in `__radd__()`: ", e)
11801219

1181-
fn __rsub__(self, other: Float64) raises -> Self:
1182-
try:
1183-
return decimojo.arithmetics.subtract(Decimal(other), self)
1184-
except e:
1185-
raise Error("Error in `__rsub__()`: ", e)
1186-
11871220
fn __rsub__(self, other: Int) raises -> Self:
11881221
try:
11891222
return decimojo.arithmetics.subtract(Decimal(other), self)
11901223
except e:
11911224
raise Error("Error in `__rsub__()`: ", e)
11921225

1193-
fn __rmul__(self, other: Float64) raises -> Self:
1194-
try:
1195-
return decimojo.arithmetics.multiply(Decimal(other), self)
1196-
except e:
1197-
raise Error("Error in `__rmul__()`: ", e)
1198-
11991226
fn __rmul__(self, other: Int) raises -> Self:
12001227
try:
12011228
return decimojo.arithmetics.multiply(Decimal(other), self)
12021229
except e:
12031230
raise Error("Error in `__rmul__()`: ", e)
12041231

1205-
fn __rtruediv__(self, other: Float64) raises -> Self:
1206-
try:
1207-
return decimojo.arithmetics.true_divide(Decimal(other), self)
1208-
except e:
1209-
raise Error("Error in `__rtruediv__()`: ", e)
1210-
12111232
fn __rtruediv__(self, other: Int) raises -> Self:
12121233
try:
12131234
return decimojo.arithmetics.true_divide(Decimal(other), self)

src/decimojo/rounding_mode.mojo

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
# limitations under the License.
1818
# ===----------------------------------------------------------------------=== #
1919

20+
"""Implements the RoundingMode for different rounding modes.
21+
"""
22+
2023

2124
struct RoundingMode:
2225
"""
@@ -27,6 +30,11 @@ struct RoundingMode:
2730
- HALF_UP: Round away from zero if >= 0.5
2831
- HALF_EVEN: Round to nearest even digit if equidistant (banker's rounding)
2932
- UP: Round away from zero
33+
34+
Notes:
35+
36+
Currently, enum is not available in Mojo. This module provides a workaround
37+
to define a custom enum-like class for rounding modes.
3038
"""
3139

3240
# alias

0 commit comments

Comments
 (0)