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
122 changes: 61 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,67 @@ For the latest development version, clone the [GitHub repository](https://github

## Quick start

Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` type.

```mojo
from decimojo import BDec, RM


fn main() raises:
var PRECISION = 100
var a = BDec("123456789.123456789")
var b = BDec("1234.56789")
print(a.sqrt(precision=PRECISION))
# 11111.11106611111096943055498174930232833813065468909453818857935956641682120364106016272519460988485
print(a.power(b, precision=PRECISION))
# 3.346361102419080234023813540078946868219632448203078657310495672766009862564151996325555496759911131748170844123475135377098326591508239654961E+9989
print(a.log(b, precision=PRECISION))
# 2.617330026656548299907884356415293977170848626010103229392408225981962436022623783231699264341492663671325580092077394824180414301026578169909
```

Here is a comprehensive quick-start guide showcasing each major function of the `BigInt` type.

```mojo
from decimojo import BigInt, BInt
# BInt is an alias for BigInt

fn main() raises:
# === Construction ===
var a = BigInt("12345678901234567890") # From string
var b = BInt(12345) # From integer

# === Basic Arithmetic ===
print(a + b) # Addition: 12345678901234580235
print(a - b) # Subtraction: 12345678901234555545
print(a * b) # Multiplication: 152415787814108380241050

# === Division Operations ===
print(a // b) # Floor division: 999650944609516
print(a.truncate_divide(b)) # Truncate division: 999650944609516
print(a % b) # Modulo: 9615

# === Power Operation ===
print(BInt(2).power(10)) # Power: 1024
print(BInt(2) ** 10) # Power (using ** operator): 1024

# === Comparison ===
print(a > b) # Greater than: True
print(a == BInt("12345678901234567890")) # Equality: True
print(a.is_zero()) # Check for zero: False

# === Type Conversions ===
print(a.to_str()) # To string: "12345678901234567890"

# === Sign Handling ===
print(-a) # Negation: -12345678901234567890
print(abs(BInt("-12345678901234567890"))) # Absolute value: 12345678901234567890
print(a.is_negative()) # Check if negative: False

# === Extremely large numbers ===
# 3600 digits // 1800 digits
print(BInt("123456789" * 400) // BInt("987654321" * 200))
```

Here is a comprehensive quick-start guide showcasing each major function of the `Decimal` type.

```mojo
Expand Down Expand Up @@ -118,67 +179,6 @@ fn main() raises:

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

Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` type.

```mojo
from decimojo import BDec, RM


fn main() raises:
var PRECISION = 100
var a = BDec("123456789.123456789")
var b = BDec("1234.56789")
print(a.sqrt(precision=PRECISION))
# 11111.11106611111096943055498174930232833813065468909453818857935956641682120364106016272519460988485
print(a.power(b, precision=PRECISION))
# 3.346361102419080234023813540078946868219632448203078657310495672766009862564151996325555496759911131748170844123475135377098326591508239654961E+9989
print(a.log(b, precision=PRECISION))
# 2.617330026656548299907884356415293977170848626010103229392408225981962436022623783231699264341492663671325580092077394824180414301026578169909
```

Here is a comprehensive quick-start guide showcasing each major function of the `BigInt` type.

```mojo
from decimojo import BigInt, BInt
# BInt is an alias for BigInt

fn main() raises:
# === Construction ===
var a = BigInt("12345678901234567890") # From string
var b = BInt(12345) # From integer

# === Basic Arithmetic ===
print(a + b) # Addition: 12345678901234580235
print(a - b) # Subtraction: 12345678901234555545
print(a * b) # Multiplication: 152415787814108380241050

# === Division Operations ===
print(a // b) # Floor division: 999650944609516
print(a.truncate_divide(b)) # Truncate division: 999650944609516
print(a % b) # Modulo: 9615

# === Power Operation ===
print(BInt(2).power(10)) # Power: 1024
print(BInt(2) ** 10) # Power (using ** operator): 1024

# === Comparison ===
print(a > b) # Greater than: True
print(a == BInt("12345678901234567890")) # Equality: True
print(a.is_zero()) # Check for zero: False

# === Type Conversions ===
print(a.to_str()) # To string: "12345678901234567890"

# === Sign Handling ===
print(-a) # Negation: -12345678901234567890
print(abs(BInt("-12345678901234567890"))) # Absolute value: 12345678901234567890
print(a.is_negative()) # Check if negative: False

# === Extremely large numbers ===
# 3600 digits // 1800 digits
print(BInt("123456789" * 400) // BInt("987654321" * 200))
```

## Objective

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.
Expand Down
184 changes: 106 additions & 78 deletions src/decimojo/bigdecimal/bigdecimal.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,47 @@ struct BigDecimal(
# Constructors and life time dunder methods
# ===------------------------------------------------------------------=== #

@implicit
fn __init__(out self, coefficient: BigUInt):
"""Constructs a BigDecimal from a BigUInt object."""
self.coefficient = coefficient
self.scale = 0
self.sign = False

fn __init__(out self, coefficient: BigUInt, scale: Int, sign: Bool):
"""Constructs a BigDecimal from its components."""
self.coefficient = coefficient
self.scale = scale
self.sign = sign

@implicit
fn __init__(out self, value: BigInt):
"""Constructs a BigDecimal from a big interger."""
self.coefficient = value.magnitude
self.scale = 0
self.sign = value.sign

fn __init__(out self, value: String) raises:
"""Constructs a BigDecimal from a string representation."""
# The string is normalized with `deciomojo.str.parse_numeric_string()`.
self = Self.from_string(value)

fn __init__(out self, value: Int) raises:
"""Constructs a BigDecimal from an integer."""
@implicit
fn __init__(out self, value: Int):
"""Constructs a BigDecimal from an `Int` object.
See `from_int()` for more information.
"""
self = Self.from_int(value)

fn __init__(out self, value: Scalar) raises:
"""Constructs a BigDecimal from a Mojo Scalar."""
self = Self.from_scalar(value)
@implicit
fn __init__(out self, value: Scalar):
"""Constructs a BigDecimal from an integral scalar.
This includes all SIMD integral types, such as Int8, Int16, UInt32, etc.

Constraints:
The dtype of the scalar must be integral.
"""
self = Self.from_integral_scalar(value)

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

@staticmethod
fn from_int(value: Int) raises -> Self:
fn from_int(value: Int) -> Self:
"""Creates a BigDecimal from an integer."""
if value == 0:
return Self(coefficient=BigUInt(UInt32(0)), scale=0, sign=False)
return Self(coefficient=BigUInt.ZERO, scale=0, sign=False)

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

@staticmethod
fn from_scalar[dtype: DType, //](value: Scalar[dtype]) raises -> Self:
"""Initializes a BigDecimal from a Mojo Scalar.
fn from_integral_scalar[dtype: DType, //](value: SIMD[dtype, 1]) -> Self:
"""Initializes a BigDecimal from an integral scalar.
This includes all SIMD integral types, such as Int8, Int16, UInt32, etc.

Args:
value: The Scalar value to be converted to BigDecimal.

Returns:
The BigDecimal representation of the Scalar value.
"""

constrained[dtype.is_integral(), "dtype must be integral."]()

if value == 0:
return Self(coefficient=BigUInt.ZERO, scale=0, sign=False)

return Self(
coefficient=BigUInt.from_absolute_integral_scalar(value),
scale=0,
sign=True if value < 0 else False,
)

@staticmethod
fn from_float[dtype: DType, //](value: Scalar[dtype]) raises -> Self:
"""Initializes a BigDecimal from a floating-point scalar.

Args:
value: The Scalar value to be converted to BigDecimal.
Expand All @@ -171,78 +217,24 @@ struct BigDecimal(
If the value is a floating-point number, it is converted to a string
with full precision before converting to BigDecimal.
"""
var sign = True if value < 0 else False

@parameter
if dtype.is_integral():
var list_of_words = List[UInt32]()
var remainder: Scalar[dtype] = value
var quotient: Scalar[dtype]
var is_min = False

if sign:
var min_value: Scalar[dtype]
var max_value: Scalar[dtype]

# TODO: Currently Int256 is not supported due to the limitation
# of Mojo's standard library. The following part can be removed
# if `mojo/stdlib/src/utils/numerics.mojo` is updated.
@parameter
if dtype == DType.int128:
min_value = Scalar[dtype](
-170141183460469231731687303715884105728
)
max_value = Scalar[dtype](
170141183460469231731687303715884105727
)
elif dtype == DType.int64:
min_value = Scalar[dtype].MIN
max_value = Scalar[dtype].MAX
elif dtype == DType.int32:
min_value = Scalar[dtype].MIN
max_value = Scalar[dtype].MAX
elif dtype == DType.int16:
min_value = Scalar[dtype].MIN
max_value = Scalar[dtype].MAX
elif dtype == DType.int8:
min_value = Scalar[dtype].MIN
max_value = Scalar[dtype].MAX
else:
raise Error(
"Error in `from_scalar()`: Unsupported integral type"
)

if value == min_value:
remainder = max_value
is_min = True
else:
remainder = -value

while remainder != 0:
quotient = remainder // 1_000_000_000
remainder = remainder % 1_000_000_000
list_of_words.append(UInt32(remainder))
remainder = quotient

if is_min:
list_of_words[0] += 1
constrained[
dtype.is_floating_point(), "dtype must be floating-point."
]()

return Self(coefficient=BigUInt(list_of_words^), scale=0, sign=sign)

else: # floating-point
if value != value: # Check for NaN
raise Error(
"Error in `from_scalar()`: Cannot convert NaN to BigUInt"
)
# Convert to string with full precision
try:
return Self.from_string(String(value))
except e:
raise Error("Error in `from_scalar()`: ", e)
if value == 0:
return Self(coefficient=BigUInt.ZERO, scale=0, sign=False)

return Self(
coefficient=BigUInt(UInt32(0)), scale=0, sign=sign
) # Default case
if value != value: # Check for NaN
raise Error("`from_scalar()`: Cannot convert NaN to BigUInt")
# Convert to string with full precision
try:
return Self.from_string(String(value))
except e:
raise Error(
"`from_scalar()`: Cannot get decimal from string\nTrace back: "
+ String(e),
)

@staticmethod
fn from_string(value: String) raises -> Self:
Expand Down Expand Up @@ -493,6 +485,38 @@ struct BigDecimal(
self, exponent, precision=28
)

# ===------------------------------------------------------------------=== #
# Basic binary right-side arithmetic operation dunders
# These methods are called to implement the binary arithmetic operations
# (+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |)
# ===------------------------------------------------------------------=== #

@always_inline
fn __radd__(self, other: Self) raises -> Self:
return decimojo.bigdecimal.arithmetics.add(self, other)

@always_inline
fn __rsub__(self, other: Self) raises -> Self:
return decimojo.bigdecimal.arithmetics.subtract(other, self)

@always_inline
fn __rmul__(self, other: Self) raises -> Self:
return decimojo.bigdecimal.arithmetics.multiply(self, other)

@always_inline
fn __rfloordiv__(self, other: Self) raises -> Self:
return decimojo.bigdecimal.arithmetics.truncate_divide(other, self)

@always_inline
fn __rmod__(self, other: Self) raises -> Self:
return decimojo.bigdecimal.arithmetics.truncate_modulo(
other, self, precision=28
)

@always_inline
fn __rpow__(self, base: Self) raises -> Self:
return decimojo.bigdecimal.exponential.power(base, self, precision=28)

# ===------------------------------------------------------------------=== #
# Basic binary augmented arithmetic assignments dunders
# These methods are called to implement the binary augmented arithmetic
Expand Down Expand Up @@ -586,6 +610,10 @@ struct BigDecimal(
except e:
return self

# ===------------------------------------------------------------------=== #
# Other dunders
# ===------------------------------------------------------------------=== #

# ===------------------------------------------------------------------=== #
# Mathematical methods that do not implement a trait (not a dunder)
# ===------------------------------------------------------------------=== #
Expand Down
2 changes: 1 addition & 1 deletion src/decimojo/bigdecimal/exponential.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ fn sqrt(x: BigDecimal, precision: Int) raises -> BigDecimal:
if odd_ndigits_frac_part:
value = value * UInt128(10)
var sqrt_value = decimojo.utility.sqrt(value)
var sqrt_value_biguint = BigUInt.from_scalar(sqrt_value)
var sqrt_value_biguint = BigUInt.from_unsigned_integral_scalar(sqrt_value)
guess = BigDecimal(
sqrt_value_biguint,
sqrt_value_biguint.number_of_digits() - ndigits_int_part_sqrt,
Expand Down
Loading