Skip to content

Commit 81b523f

Browse files
authored
[integer][decimal] Allows implicit type conversion and type merging (#89)
This pull request introduces significant updates to `BigDecimal`, `BigInt`, and `BigUInt` structures in the `decimojo` library. The changes add implicit constructors for better usability, refine initialization methods, and improve type handling for integral and floating-point scalars. ### Enhancements to `BigDecimal`: * [`src/decimojo/bigdecimal/bigdecimal.mojo`](diffhunk://#diff-be2f9b2702fd15952546e17fe94a64c22902a0e4678bfd9cb925269647923db4R85-R125): Added implicit constructors for `BigUInt`, `BigInt`, `Int`, and integral scalars, enabling seamless initialization of `BigDecimal` objects from these types. * [`src/decimojo/bigdecimal/bigdecimal.mojo`](diffhunk://#diff-be2f9b2702fd15952546e17fe94a64c22902a0e4678bfd9cb925269647923db4L160-R207): Replaced the `from_scalar` method with `from_integral_scalar` and introduced `from_float` for better handling of integral and floating-point scalars. Improved constraints and error handling for scalar type validation. [[1]](diffhunk://#diff-be2f9b2702fd15952546e17fe94a64c22902a0e4678bfd9cb925269647923db4L160-R207) [[2]](diffhunk://#diff-be2f9b2702fd15952546e17fe94a64c22902a0e4678bfd9cb925269647923db4L174-R237) * [`src/decimojo/bigdecimal/bigdecimal.mojo`](diffhunk://#diff-be2f9b2702fd15952546e17fe94a64c22902a0e4678bfd9cb925269647923db4R488-R519): Added right-side arithmetic dunders (`__radd__`, `__rsub__`, etc.) for `BigDecimal`, enabling more flexible cross-type operations. ### Enhancements to `BigInt`: * [`src/decimojo/bigint/bigint.mojo`](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bR73-R78): Added implicit constructors for `BigUInt`, `Int`, and integral scalars, simplifying the initialization of `BigInt` objects. [[1]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bR73-R78) [[2]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bR145-R161) * [`src/decimojo/bigint/bigint.mojo`](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL236-R277): Introduced `from_integral_scalar` for handling integral scalars, replacing `from_uint128` with improved constraints and validation. ### Minor Refinements: * [`src/decimojo/bigdecimal/exponential.mojo`](diffhunk://#diff-8edc842002d08323642db57f775ff7f626be068d55cb55bbe637e215f68ed8bcL515-R515): Updated the `sqrt` function to use `BigUInt.from_unsigned_integral_scalar`, ensuring consistent handling of unsigned integral scalars. * [`src/decimojo/bigint/bigint.mojo`](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL84-R91): Marked certain initialization methods as unsafe and added warnings in their documentation to encourage users to use safer alternatives. [[1]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL84-R91) [[2]](diffhunk://#diff-d65a756a58e3387a2b30bbe88308c3b318d71d2116b2870ac0a44109729e554bL103-R111) These changes collectively improve the usability, readability, and robustness of the `decimojo` library, making it more intuitive for developers working with arbitrary-precision arithmetic.
1 parent 41366c1 commit 81b523f

File tree

5 files changed

+342
-205
lines changed

5 files changed

+342
-205
lines changed

README.md

Lines changed: 61 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,67 @@ For the latest development version, clone the [GitHub repository](https://github
5353

5454
## Quick start
5555

56+
Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` type.
57+
58+
```mojo
59+
from decimojo import BDec, RM
60+
61+
62+
fn main() raises:
63+
var PRECISION = 100
64+
var a = BDec("123456789.123456789")
65+
var b = BDec("1234.56789")
66+
print(a.sqrt(precision=PRECISION))
67+
# 11111.11106611111096943055498174930232833813065468909453818857935956641682120364106016272519460988485
68+
print(a.power(b, precision=PRECISION))
69+
# 3.346361102419080234023813540078946868219632448203078657310495672766009862564151996325555496759911131748170844123475135377098326591508239654961E+9989
70+
print(a.log(b, precision=PRECISION))
71+
# 2.617330026656548299907884356415293977170848626010103229392408225981962436022623783231699264341492663671325580092077394824180414301026578169909
72+
```
73+
74+
Here is a comprehensive quick-start guide showcasing each major function of the `BigInt` type.
75+
76+
```mojo
77+
from decimojo import BigInt, BInt
78+
# BInt is an alias for BigInt
79+
80+
fn main() raises:
81+
# === Construction ===
82+
var a = BigInt("12345678901234567890") # From string
83+
var b = BInt(12345) # From integer
84+
85+
# === Basic Arithmetic ===
86+
print(a + b) # Addition: 12345678901234580235
87+
print(a - b) # Subtraction: 12345678901234555545
88+
print(a * b) # Multiplication: 152415787814108380241050
89+
90+
# === Division Operations ===
91+
print(a // b) # Floor division: 999650944609516
92+
print(a.truncate_divide(b)) # Truncate division: 999650944609516
93+
print(a % b) # Modulo: 9615
94+
95+
# === Power Operation ===
96+
print(BInt(2).power(10)) # Power: 1024
97+
print(BInt(2) ** 10) # Power (using ** operator): 1024
98+
99+
# === Comparison ===
100+
print(a > b) # Greater than: True
101+
print(a == BInt("12345678901234567890")) # Equality: True
102+
print(a.is_zero()) # Check for zero: False
103+
104+
# === Type Conversions ===
105+
print(a.to_str()) # To string: "12345678901234567890"
106+
107+
# === Sign Handling ===
108+
print(-a) # Negation: -12345678901234567890
109+
print(abs(BInt("-12345678901234567890"))) # Absolute value: 12345678901234567890
110+
print(a.is_negative()) # Check if negative: False
111+
112+
# === Extremely large numbers ===
113+
# 3600 digits // 1800 digits
114+
print(BInt("123456789" * 400) // BInt("987654321" * 200))
115+
```
116+
56117
Here is a comprehensive quick-start guide showcasing each major function of the `Decimal` type.
57118

58119
```mojo
@@ -118,67 +179,6 @@ fn main() raises:
118179

119180
[Click here for 8 key examples](https://zhuyuhao.com/decimojo/docs/examples) highlighting the most important features of the `Decimal` type.
120181

121-
Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` type.
122-
123-
```mojo
124-
from decimojo import BDec, RM
125-
126-
127-
fn main() raises:
128-
var PRECISION = 100
129-
var a = BDec("123456789.123456789")
130-
var b = BDec("1234.56789")
131-
print(a.sqrt(precision=PRECISION))
132-
# 11111.11106611111096943055498174930232833813065468909453818857935956641682120364106016272519460988485
133-
print(a.power(b, precision=PRECISION))
134-
# 3.346361102419080234023813540078946868219632448203078657310495672766009862564151996325555496759911131748170844123475135377098326591508239654961E+9989
135-
print(a.log(b, precision=PRECISION))
136-
# 2.617330026656548299907884356415293977170848626010103229392408225981962436022623783231699264341492663671325580092077394824180414301026578169909
137-
```
138-
139-
Here is a comprehensive quick-start guide showcasing each major function of the `BigInt` type.
140-
141-
```mojo
142-
from decimojo import BigInt, BInt
143-
# BInt is an alias for BigInt
144-
145-
fn main() raises:
146-
# === Construction ===
147-
var a = BigInt("12345678901234567890") # From string
148-
var b = BInt(12345) # From integer
149-
150-
# === Basic Arithmetic ===
151-
print(a + b) # Addition: 12345678901234580235
152-
print(a - b) # Subtraction: 12345678901234555545
153-
print(a * b) # Multiplication: 152415787814108380241050
154-
155-
# === Division Operations ===
156-
print(a // b) # Floor division: 999650944609516
157-
print(a.truncate_divide(b)) # Truncate division: 999650944609516
158-
print(a % b) # Modulo: 9615
159-
160-
# === Power Operation ===
161-
print(BInt(2).power(10)) # Power: 1024
162-
print(BInt(2) ** 10) # Power (using ** operator): 1024
163-
164-
# === Comparison ===
165-
print(a > b) # Greater than: True
166-
print(a == BInt("12345678901234567890")) # Equality: True
167-
print(a.is_zero()) # Check for zero: False
168-
169-
# === Type Conversions ===
170-
print(a.to_str()) # To string: "12345678901234567890"
171-
172-
# === Sign Handling ===
173-
print(-a) # Negation: -12345678901234567890
174-
print(abs(BInt("-12345678901234567890"))) # Absolute value: 12345678901234567890
175-
print(a.is_negative()) # Check if negative: False
176-
177-
# === Extremely large numbers ===
178-
# 3600 digits // 1800 digits
179-
print(BInt("123456789" * 400) // BInt("987654321" * 200))
180-
```
181-
182182
## Objective
183183

184184
Financial calculations and data analysis require precise decimal arithmetic that floating-point numbers cannot reliably provide. As someone working in finance and credit risk model validation, I needed a dependable correctly-rounded, fixed-precision numeric type when migrating my personal projects from Python to Mojo.

src/decimojo/bigdecimal/bigdecimal.mojo

Lines changed: 106 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -82,24 +82,47 @@ struct BigDecimal(
8282
# Constructors and life time dunder methods
8383
# ===------------------------------------------------------------------=== #
8484

85+
@implicit
86+
fn __init__(out self, coefficient: BigUInt):
87+
"""Constructs a BigDecimal from a BigUInt object."""
88+
self.coefficient = coefficient
89+
self.scale = 0
90+
self.sign = False
91+
8592
fn __init__(out self, coefficient: BigUInt, scale: Int, sign: Bool):
8693
"""Constructs a BigDecimal from its components."""
8794
self.coefficient = coefficient
8895
self.scale = scale
8996
self.sign = sign
9097

98+
@implicit
99+
fn __init__(out self, value: BigInt):
100+
"""Constructs a BigDecimal from a big interger."""
101+
self.coefficient = value.magnitude
102+
self.scale = 0
103+
self.sign = value.sign
104+
91105
fn __init__(out self, value: String) raises:
92106
"""Constructs a BigDecimal from a string representation."""
93107
# The string is normalized with `deciomojo.str.parse_numeric_string()`.
94108
self = Self.from_string(value)
95109

96-
fn __init__(out self, value: Int) raises:
97-
"""Constructs a BigDecimal from an integer."""
110+
@implicit
111+
fn __init__(out self, value: Int):
112+
"""Constructs a BigDecimal from an `Int` object.
113+
See `from_int()` for more information.
114+
"""
98115
self = Self.from_int(value)
99116

100-
fn __init__(out self, value: Scalar) raises:
101-
"""Constructs a BigDecimal from a Mojo Scalar."""
102-
self = Self.from_scalar(value)
117+
@implicit
118+
fn __init__(out self, value: Scalar):
119+
"""Constructs a BigDecimal from an integral scalar.
120+
This includes all SIMD integral types, such as Int8, Int16, UInt32, etc.
121+
122+
Constraints:
123+
The dtype of the scalar must be integral.
124+
"""
125+
self = Self.from_integral_scalar(value)
103126

104127
# ===------------------------------------------------------------------=== #
105128
# Constructing methods that are not dunders
@@ -123,10 +146,10 @@ struct BigDecimal(
123146
return Self(BigUInt(List[UInt32](coefficient)), scale, sign)
124147

125148
@staticmethod
126-
fn from_int(value: Int) raises -> Self:
149+
fn from_int(value: Int) -> Self:
127150
"""Creates a BigDecimal from an integer."""
128151
if value == 0:
129-
return Self(coefficient=BigUInt(UInt32(0)), scale=0, sign=False)
152+
return Self(coefficient=BigUInt.ZERO, scale=0, sign=False)
130153

131154
var words = List[UInt32](capacity=2)
132155
var sign: Bool
@@ -157,8 +180,31 @@ struct BigDecimal(
157180
return Self(coefficient=BigUInt(words^), scale=0, sign=sign)
158181

159182
@staticmethod
160-
fn from_scalar[dtype: DType, //](value: Scalar[dtype]) raises -> Self:
161-
"""Initializes a BigDecimal from a Mojo Scalar.
183+
fn from_integral_scalar[dtype: DType, //](value: SIMD[dtype, 1]) -> Self:
184+
"""Initializes a BigDecimal from an integral scalar.
185+
This includes all SIMD integral types, such as Int8, Int16, UInt32, etc.
186+
187+
Args:
188+
value: The Scalar value to be converted to BigDecimal.
189+
190+
Returns:
191+
The BigDecimal representation of the Scalar value.
192+
"""
193+
194+
constrained[dtype.is_integral(), "dtype must be integral."]()
195+
196+
if value == 0:
197+
return Self(coefficient=BigUInt.ZERO, scale=0, sign=False)
198+
199+
return Self(
200+
coefficient=BigUInt.from_absolute_integral_scalar(value),
201+
scale=0,
202+
sign=True if value < 0 else False,
203+
)
204+
205+
@staticmethod
206+
fn from_float[dtype: DType, //](value: Scalar[dtype]) raises -> Self:
207+
"""Initializes a BigDecimal from a floating-point scalar.
162208
163209
Args:
164210
value: The Scalar value to be converted to BigDecimal.
@@ -171,78 +217,24 @@ struct BigDecimal(
171217
If the value is a floating-point number, it is converted to a string
172218
with full precision before converting to BigDecimal.
173219
"""
174-
var sign = True if value < 0 else False
175-
176-
@parameter
177-
if dtype.is_integral():
178-
var list_of_words = List[UInt32]()
179-
var remainder: Scalar[dtype] = value
180-
var quotient: Scalar[dtype]
181-
var is_min = False
182-
183-
if sign:
184-
var min_value: Scalar[dtype]
185-
var max_value: Scalar[dtype]
186-
187-
# TODO: Currently Int256 is not supported due to the limitation
188-
# of Mojo's standard library. The following part can be removed
189-
# if `mojo/stdlib/src/utils/numerics.mojo` is updated.
190-
@parameter
191-
if dtype == DType.int128:
192-
min_value = Scalar[dtype](
193-
-170141183460469231731687303715884105728
194-
)
195-
max_value = Scalar[dtype](
196-
170141183460469231731687303715884105727
197-
)
198-
elif dtype == DType.int64:
199-
min_value = Scalar[dtype].MIN
200-
max_value = Scalar[dtype].MAX
201-
elif dtype == DType.int32:
202-
min_value = Scalar[dtype].MIN
203-
max_value = Scalar[dtype].MAX
204-
elif dtype == DType.int16:
205-
min_value = Scalar[dtype].MIN
206-
max_value = Scalar[dtype].MAX
207-
elif dtype == DType.int8:
208-
min_value = Scalar[dtype].MIN
209-
max_value = Scalar[dtype].MAX
210-
else:
211-
raise Error(
212-
"Error in `from_scalar()`: Unsupported integral type"
213-
)
214-
215-
if value == min_value:
216-
remainder = max_value
217-
is_min = True
218-
else:
219-
remainder = -value
220-
221-
while remainder != 0:
222-
quotient = remainder // 1_000_000_000
223-
remainder = remainder % 1_000_000_000
224-
list_of_words.append(UInt32(remainder))
225-
remainder = quotient
226220

227-
if is_min:
228-
list_of_words[0] += 1
221+
constrained[
222+
dtype.is_floating_point(), "dtype must be floating-point."
223+
]()
229224

230-
return Self(coefficient=BigUInt(list_of_words^), scale=0, sign=sign)
231-
232-
else: # floating-point
233-
if value != value: # Check for NaN
234-
raise Error(
235-
"Error in `from_scalar()`: Cannot convert NaN to BigUInt"
236-
)
237-
# Convert to string with full precision
238-
try:
239-
return Self.from_string(String(value))
240-
except e:
241-
raise Error("Error in `from_scalar()`: ", e)
225+
if value == 0:
226+
return Self(coefficient=BigUInt.ZERO, scale=0, sign=False)
242227

243-
return Self(
244-
coefficient=BigUInt(UInt32(0)), scale=0, sign=sign
245-
) # Default case
228+
if value != value: # Check for NaN
229+
raise Error("`from_scalar()`: Cannot convert NaN to BigUInt")
230+
# Convert to string with full precision
231+
try:
232+
return Self.from_string(String(value))
233+
except e:
234+
raise Error(
235+
"`from_scalar()`: Cannot get decimal from string\nTrace back: "
236+
+ String(e),
237+
)
246238

247239
@staticmethod
248240
fn from_string(value: String) raises -> Self:
@@ -493,6 +485,38 @@ struct BigDecimal(
493485
self, exponent, precision=28
494486
)
495487

488+
# ===------------------------------------------------------------------=== #
489+
# Basic binary right-side arithmetic operation dunders
490+
# These methods are called to implement the binary arithmetic operations
491+
# (+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |)
492+
# ===------------------------------------------------------------------=== #
493+
494+
@always_inline
495+
fn __radd__(self, other: Self) raises -> Self:
496+
return decimojo.bigdecimal.arithmetics.add(self, other)
497+
498+
@always_inline
499+
fn __rsub__(self, other: Self) raises -> Self:
500+
return decimojo.bigdecimal.arithmetics.subtract(other, self)
501+
502+
@always_inline
503+
fn __rmul__(self, other: Self) raises -> Self:
504+
return decimojo.bigdecimal.arithmetics.multiply(self, other)
505+
506+
@always_inline
507+
fn __rfloordiv__(self, other: Self) raises -> Self:
508+
return decimojo.bigdecimal.arithmetics.truncate_divide(other, self)
509+
510+
@always_inline
511+
fn __rmod__(self, other: Self) raises -> Self:
512+
return decimojo.bigdecimal.arithmetics.truncate_modulo(
513+
other, self, precision=28
514+
)
515+
516+
@always_inline
517+
fn __rpow__(self, base: Self) raises -> Self:
518+
return decimojo.bigdecimal.exponential.power(base, self, precision=28)
519+
496520
# ===------------------------------------------------------------------=== #
497521
# Basic binary augmented arithmetic assignments dunders
498522
# These methods are called to implement the binary augmented arithmetic
@@ -586,6 +610,10 @@ struct BigDecimal(
586610
except e:
587611
return self
588612

613+
# ===------------------------------------------------------------------=== #
614+
# Other dunders
615+
# ===------------------------------------------------------------------=== #
616+
589617
# ===------------------------------------------------------------------=== #
590618
# Mathematical methods that do not implement a trait (not a dunder)
591619
# ===------------------------------------------------------------------=== #

src/decimojo/bigdecimal/exponential.mojo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ fn sqrt(x: BigDecimal, precision: Int) raises -> BigDecimal:
512512
if odd_ndigits_frac_part:
513513
value = value * UInt128(10)
514514
var sqrt_value = decimojo.utility.sqrt(value)
515-
var sqrt_value_biguint = BigUInt.from_scalar(sqrt_value)
515+
var sqrt_value_biguint = BigUInt.from_unsigned_integral_scalar(sqrt_value)
516516
guess = BigDecimal(
517517
sqrt_value_biguint,
518518
sqrt_value_biguint.number_of_digits() - ndigits_int_part_sqrt,

0 commit comments

Comments
 (0)