Skip to content

Commit d91e41a

Browse files
authored
[decimal] Implement some IO methods for BigDecimal (#65)
This pull request introduces significant changes to the `decimojo` library, primarily focusing on the addition of the `BigDecimal` type and various improvements to the `BigInt` and `BigUInt` types. The changes include new functionality, refactoring, and code cleanup. ### New Features: * **BigDecimal Implementation**: * Added a new `BigDecimal` type with comprehensive methods for construction, arithmetic operations, and type conversion. (`src/decimojo/bigdecimal/bigdecimal.mojo`) ### Refactoring and Code Cleanup: * **BigInt Enhancements**: * Refactored `BigInt` to handle the case of `Int.MIN` more robustly by introducing an `is_min` flag. (`src/decimojo/bigint/bigint.mojo`) [[1]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bR218-R225) [[2]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bR237-R239) * Updated method names from `to_str` to `to_string` for consistency. (`src/decimojo/bigint/bigint.mojo`) [[1]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL316-R297) [[2]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL367-R346) [[3]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL377-R360) [[4]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL391-R370) [[5]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL614-R593) * Removed commented-out initializers to clean up the code. (`src/decimojo/bigint/bigint.mojo`) * **BigUInt Enhancements**: * Added a type alias `BUInt` for `BigUInt` to improve code readability. (`src/decimojo/biguint/biguint.mojo`) * Updated method names from `to_str` to `to_string` for consistency. (`src/decimojo/biguint/biguint.mojo`) [[1]](diffhunk://#diff-f9432b9b2671643af91201f9e3f011551a3d3b0e6d7b256d0d4569f5ae59848aL386-R388) [[2]](diffhunk://#diff-f9432b9b2671643af91201f9e3f011551a3d3b0e6d7b256d0d4569f5ae59848aL463-R463) [[3]](diffhunk://#diff-f9432b9b2671643af91201f9e3f011551a3d3b0e6d7b256d0d4569f5ae59848aL482-R482) ### Import Adjustments: * **Import Statements**: * Updated import statements to include `BigDecimal` and the new alias `BUInt`. (`src/decimojo/__init__.mojo`)
1 parent 7410089 commit d91e41a

5 files changed

Lines changed: 382 additions & 49 deletions

File tree

src/decimojo/__init__.mojo

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ from decimojo import Decimal, BigInt, RoundingMode
2727
# Core types
2828
from .decimal.decimal import Decimal
2929
from .bigint.bigint import BigInt, BInt
30-
from .biguint.biguint import BigUInt
30+
from .biguint.biguint import BigUInt, BUInt
31+
from .bigdecimal.bigdecimal import BigDecimal
3132
from .rounding_mode import RoundingMode
3233

3334
# Core functions

src/decimojo/bigdecimal/__init__.mojo

Whitespace-only changes.
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
# ===----------------------------------------------------------------------=== #
2+
# Copyright 2025 Yuhao Zhu
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ===----------------------------------------------------------------------=== #
16+
17+
"""Implements basic object methods for the BigDecimal type.
18+
19+
This module contains the basic object methods for the BigDecimal type.
20+
These methods include constructors, life time methods, output dunders,
21+
type-transfer dunders, basic arithmetic operation dunders, comparison
22+
operation dunders, and other dunders that implement traits, as well as
23+
mathematical methods that do not implement a trait.
24+
"""
25+
26+
from memory import UnsafePointer
27+
import testing
28+
29+
from decimojo.rounding_mode import RoundingMode
30+
31+
32+
@value
33+
struct BigDecimal:
34+
"""Represents a arbitrary-precision decimal.
35+
36+
Notes:
37+
38+
Internal Representation:
39+
40+
- A base-10 unsigned integer (BigUInt) for coefficient.
41+
- A Int value for the scale
42+
- A Bool value for the sign.
43+
44+
Final value:
45+
(-1)**sign * coefficient * 10^(-scale)
46+
"""
47+
48+
# ===------------------------------------------------------------------=== #
49+
# Organization of fields and methods:
50+
# - Internal representation fields
51+
# - Constants (aliases)
52+
# - Special values (methods)
53+
# - Constructors and life time methods
54+
# - Constructing methods that are not dunders
55+
# - Output dunders, type-transfer dunders, and other type-transfer methods
56+
# - Basic unary arithmetic operation dunders
57+
# - Basic binary arithmetic operation dunders
58+
# - Basic binary arithmetic operation dunders with reflected operands
59+
# - Basic binary augmented arithmetic operation dunders
60+
# - Basic comparison operation dunders
61+
# - Other dunders that implements traits
62+
# - Mathematical methods that do not implement a trait (not a dunder)
63+
# - Other methods
64+
# - Internal methods
65+
# ===------------------------------------------------------------------=== #
66+
67+
# Internal representation fields
68+
var coefficient: BigUInt
69+
"""The coefficient of the BigDecimal."""
70+
var scale: Int
71+
"""The scale of the BigDecimal."""
72+
var sign: Bool
73+
"""Sign information."""
74+
75+
# ===------------------------------------------------------------------=== #
76+
# Constructors and life time dunder methods
77+
# ===------------------------------------------------------------------=== #
78+
79+
fn __init__(out self, coefficient: BigUInt, scale: Int, sign: Bool) raises:
80+
"""Constructs a BigDecimal from its components."""
81+
self.coefficient = coefficient
82+
self.scale = scale
83+
self.sign = sign
84+
85+
fn __init__(out self, value: String) raises:
86+
"""Constructs a BigDecimal from a string representation."""
87+
# The string is normalized with `deciomojo.str.parse_numeric_string()`.
88+
self = Self.from_string(value)
89+
90+
fn __init__(out self, value: Int) raises:
91+
"""Constructs a BigDecimal from an integer."""
92+
self = Self.from_int(value)
93+
94+
fn __init__(out self, value: Scalar) raises:
95+
"""Constructs a BigDecimal from a Mojo Scalar."""
96+
self = Self.from_scalar(value)
97+
98+
# ===------------------------------------------------------------------=== #
99+
# Constructing methods that are not dunders
100+
# from_int(value: Int) -> Self
101+
# from_scalar(value: Scalar) -> Self
102+
# from_string(value: String) -> Self
103+
# ===------------------------------------------------------------------=== #
104+
105+
@staticmethod
106+
fn from_int(value: Int) raises -> Self:
107+
"""Creates a BigDecimal from an integer."""
108+
if value == 0:
109+
return Self(coefficient=BigUInt(UInt32(0)), scale=0, sign=False)
110+
111+
var words = List[UInt32](capacity=2)
112+
var sign: Bool
113+
var remainder: Int
114+
var quotient: Int
115+
var is_min: Bool = False
116+
if value < 0:
117+
sign = True
118+
# Handle the case of Int.MIN due to asymmetry of Int.MIN and Int.MAX
119+
if value == Int.MIN:
120+
is_min = True
121+
remainder = Int.MAX
122+
else:
123+
remainder = -value
124+
else:
125+
sign = False
126+
remainder = value
127+
128+
while remainder != 0:
129+
quotient = remainder // 1_000_000_000
130+
remainder = remainder % 1_000_000_000
131+
words.append(UInt32(remainder))
132+
remainder = quotient
133+
134+
if is_min:
135+
words[0] += 1
136+
137+
return Self(coefficient=BigUInt(words^), scale=0, sign=sign)
138+
139+
@staticmethod
140+
fn from_scalar[dtype: DType, //](value: Scalar[dtype]) raises -> Self:
141+
"""Initializes a BigDecimal from a Mojo Scalar.
142+
143+
Args:
144+
value: The Scalar value to be converted to BigDecimal.
145+
146+
Returns:
147+
The BigDecimal representation of the Scalar value.
148+
149+
Notes:
150+
If the value is a floating-point number, it is converted to a string
151+
with full precision before converting to BigDecimal.
152+
"""
153+
var sign = True if value < 0 else False
154+
155+
@parameter
156+
if dtype.is_integral():
157+
var list_of_words = List[UInt32]()
158+
var remainder: Scalar[dtype] = value
159+
var quotient: Scalar[dtype]
160+
var is_min = False
161+
162+
if sign:
163+
var min_value: Scalar[dtype]
164+
var max_value: Scalar[dtype]
165+
166+
# TODO: Currently Int256 is not supported due to the limitation
167+
# of Mojo's standard library. The following part can be removed
168+
# if `mojo/stdlib/src/utils/numerics.mojo` is updated.
169+
@parameter
170+
if dtype == DType.int128:
171+
min_value = Scalar[dtype](
172+
-170141183460469231731687303715884105728
173+
)
174+
max_value = Scalar[dtype](
175+
170141183460469231731687303715884105727
176+
)
177+
elif dtype == DType.int64:
178+
min_value = Scalar[dtype].MIN
179+
max_value = Scalar[dtype].MAX
180+
elif dtype == DType.int32:
181+
min_value = Scalar[dtype].MIN
182+
max_value = Scalar[dtype].MAX
183+
elif dtype == DType.int16:
184+
min_value = Scalar[dtype].MIN
185+
max_value = Scalar[dtype].MAX
186+
elif dtype == DType.int8:
187+
min_value = Scalar[dtype].MIN
188+
max_value = Scalar[dtype].MAX
189+
else:
190+
raise Error(
191+
"Error in `from_scalar()`: Unsupported integral type"
192+
)
193+
194+
if value == min_value:
195+
remainder = max_value
196+
is_min = True
197+
else:
198+
remainder = -value
199+
200+
while remainder != 0:
201+
quotient = remainder // 1_000_000_000
202+
remainder = remainder % 1_000_000_000
203+
list_of_words.append(UInt32(remainder))
204+
remainder = quotient
205+
206+
if is_min:
207+
list_of_words[0] += 1
208+
209+
return Self(coefficient=BigUInt(list_of_words^), scale=0, sign=sign)
210+
211+
else: # floating-point
212+
if value != value: # Check for NaN
213+
raise Error(
214+
"Error in `from_scalar()`: Cannot convert NaN to BigUInt"
215+
)
216+
# Convert to string with full precision
217+
try:
218+
return Self.from_string(String(value))
219+
except e:
220+
raise Error("Error in `from_scalar()`: ", e)
221+
222+
return Self(
223+
coefficient=BigUInt(UInt32(0)), scale=0, sign=sign
224+
) # Default case
225+
226+
@staticmethod
227+
fn from_string(value: String) raises -> Self:
228+
"""Initializes a BigDecimal from a string representation.
229+
The string is normalized with `deciomojo.str.parse_numeric_string()`.
230+
231+
Args:
232+
value: The string representation of the BigDecimal.
233+
234+
Returns:
235+
The BigDecimal representation of the string.
236+
"""
237+
var coef: List[UInt8]
238+
var scale: Int
239+
var sign: Bool
240+
coef, scale, sign = decimojo.str.parse_numeric_string(value)
241+
242+
var number_of_digits = len(coef)
243+
var number_of_words = number_of_digits // 9
244+
if number_of_digits % 9 != 0:
245+
number_of_words += 1
246+
247+
coefficient_words = List[UInt32](capacity=number_of_words)
248+
249+
var end: Int = number_of_digits
250+
var start: Int
251+
while end >= 9:
252+
start = end - 9
253+
var word: UInt32 = 0
254+
for digit in coef[start:end]:
255+
word = word * 10 + UInt32(digit[])
256+
coefficient_words.append(word)
257+
end = start
258+
if end > 0:
259+
var word: UInt32 = 0
260+
for digit in coef[0:end]:
261+
word = word * 10 + UInt32(digit[])
262+
coefficient_words.append(word)
263+
264+
coefficient = BigUInt(coefficient_words^)
265+
266+
return Self(coefficient^, scale, sign)
267+
268+
# ===------------------------------------------------------------------=== #
269+
# Output dunders, type-transfer dunders
270+
# __str__()
271+
# __repr__()
272+
# __int__()
273+
# __float__()
274+
# ===------------------------------------------------------------------=== #
275+
276+
fn __str__(self) -> String:
277+
"""Returns string representation of the BigDecimal.
278+
See `to_string()` for more information.
279+
"""
280+
return self.to_string()
281+
282+
fn __repr__(self) -> String:
283+
"""Returns a string representation of the BigDecimal."""
284+
return 'BigDecimal("' + self.__str__() + '")'
285+
286+
fn __int__(self) raises -> Int:
287+
"""Converts the BigDecimal to an integer."""
288+
return Int(String(self))
289+
290+
fn __float__(self) raises -> Float64:
291+
"""Converts the BigDecimal to a floating-point number."""
292+
return Float64(String(self))
293+
294+
# ===------------------------------------------------------------------=== #
295+
# Type-transfer or output methods that are not dunders
296+
# ===------------------------------------------------------------------=== #
297+
298+
fn to_string(self) -> String:
299+
"""Returns string representation of the number."""
300+
301+
if self.coefficient.is_unitialized():
302+
return String("Unitilialized maginitude of BigDecimal")
303+
304+
var result = String("-") if self.sign else String("")
305+
306+
var coefficient_string = self.coefficient.to_string()
307+
308+
if self.scale == 0:
309+
result += coefficient_string
310+
311+
elif self.scale > 0:
312+
if self.scale < len(coefficient_string):
313+
# Example: 123_456 with scale 3 -> 123.456
314+
result += coefficient_string[
315+
: len(coefficient_string) - self.scale
316+
]
317+
result += "."
318+
result += coefficient_string[
319+
len(coefficient_string) - self.scale :
320+
]
321+
else:
322+
# Example: 123_456 with scale 6 -> 0.123_456
323+
# Example: 123_456 with scale 7 -> 0.012_345_6
324+
result += "0."
325+
result += "0" * (self.scale - len(coefficient_string))
326+
result += coefficient_string
327+
328+
else:
329+
# scale < 0
330+
# Example: 12_345 with scale -3 -> 12_345_000
331+
result += coefficient_string
332+
result += "0" * (-self.scale)
333+
334+
return result^
335+
336+
# ===------------------------------------------------------------------=== #
337+
# Type-transfer or output methods that are not dunders
338+
# ===------------------------------------------------------------------=== #
339+
340+
fn write_to[W: Writer](self, mut writer: W):
341+
"""Writes the BigDecimal to a writer.
342+
This implement the `write` method of the `Writer` trait.
343+
"""
344+
writer.write(String(self))
345+
346+
# ===------------------------------------------------------------------=== #
347+
# Other methods
348+
# ===------------------------------------------------------------------=== #
349+
350+
@always_inline
351+
fn is_zero(self) -> Bool:
352+
"""Returns True if this number represents zero."""
353+
return self.coefficient.is_zero()

0 commit comments

Comments
 (0)