Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Finalize div
  • Loading branch information
forfudan committed Mar 31, 2025
commit 8835b52cf657747cea6be2e5de52ad345a318d82
40 changes: 19 additions & 21 deletions src/decimojo/bigdecimal/arithmetics.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -190,28 +190,28 @@ fn multiply(x1: BigDecimal, x2: BigDecimal) raises -> BigDecimal:

# TODO: Optimize when divided by power of 10
fn true_divide(
x1: BigDecimal, x2: BigDecimal, max_precision: Int = 28
x1: BigDecimal, x2: BigDecimal, precision: Int = 28
) raises -> BigDecimal:
"""Returns the quotient of two numbers.

Args:
x1: The first operand (dividend).
x2: The second operand (divisor).
max_precision: The maximum number of significant digits in the result.
precision: The number of significant digits in the result.

Returns:
The quotient of x1 and x2, with precision up to max_precision
ignificant digits.
The quotient of x1 and x2, with precision up to `precision`
significant digits.

Notes:

- If the coefficients can be divided exactly, the number of digits after
the decimal point is the difference of the scales of the two operands.
- If the coefficients cannot be divided exactly, the number of digits after
the decimal point is max_precision.
the decimal point is precision.
- If the division is not exact, the number of digits after the decimal
point is calcuated to max_precision + BUFFER_DIGITS, and the result is
rounded to max_precision according to the specified rules.
point is calcuated to precision + BUFFER_DIGITS, and the result is
rounded to precision according to the specified rules.
"""
alias BUFFER_DIGITS = 5 # Buffer digits for rounding

Expand All @@ -228,7 +228,7 @@ fn true_divide(
)

# First estimate the number of significant digits needed in the dividend
# to produce a result with max_precision significant digits
# to produce a result with precision significant digits
var x1_digits = x1.coefficient.number_of_digits()
var x2_digits = x2.coefficient.number_of_digits()

Expand All @@ -243,16 +243,16 @@ fn true_divide(
if remainder.is_zero():
# For exact division, calculate significant digits in result
var num_sig_digits = quotient.number_of_digits()
# If the significant digits are within max_precision, return as is
if num_sig_digits <= max_precision:
# If the significant digits are within precision, return as is
if num_sig_digits <= precision:
return BigDecimal(
coefficient=quotient^,
scale=result_scale,
sign=x1.sign != x2.sign,
)
else: # num_sig_digits > max_precision
else: # num_sig_digits > precision
# Otherwise, need to truncate to max precision
var digits_to_remove = num_sig_digits - max_precision
var digits_to_remove = num_sig_digits - precision
var quotient = quotient.remove_trailing_digits_with_rounding(
digits_to_remove, RoundingMode.ROUND_HALF_EVEN
)
Expand All @@ -264,10 +264,8 @@ fn true_divide(
)

# Calculate how many additional digits we need in the dividend
# We want: (x1_digits + additional) - x2_digits ≈ max_precision
var additional_digits = max_precision + BUFFER_DIGITS - (
x1_digits - x2_digits
)
# We want: (x1_digits + additional) - x2_digits ≈ precision
var additional_digits = precision + BUFFER_DIGITS - (x1_digits - x2_digits)
additional_digits = max(0, additional_digits)

# Scale factor needs to account for the scales of the operands
Expand All @@ -293,7 +291,7 @@ fn true_divide(
# If the division is exact
# we may need to remove the extra trailing zeros.
# TODO: Think about the behavior, whether division should always return the
# maximum precision even if the result scale is less than max_precision.
# `precision` even if the result scale is less than precision.
# Example: 10 / 4 = 2.50000000000000000000000000000
# If exact division, remove trailing zeros
if is_exact:
Expand All @@ -305,8 +303,8 @@ fn true_divide(
result_digits = quotient.number_of_digits()

# Otherwise, the division is not exact or have too many digits
# round to max_precision
# If we have too many significant digits, reduce to max_precision
# round to precision
# If we have too many significant digits, reduce to precision
# Extract the digits to be rounded
# Example: 2 digits to remove
# divisor = 100
Expand All @@ -317,8 +315,8 @@ fn true_divide(
# If rounding_digits == half_divisor, round up if the last digit of
# result_coefficient is odd
# If rounding_digits < half_divisor, round down
if result_digits > max_precision:
var digits_to_remove = result_digits - max_precision
if result_digits > precision:
var digits_to_remove = result_digits - precision
quotient = quotient.remove_trailing_digits_with_rounding(
digits_to_remove, RoundingMode.ROUND_HALF_EVEN
)
Expand Down
89 changes: 61 additions & 28 deletions src/decimojo/bigdecimal/bigdecimal.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,11 @@ struct BigDecimal:
# Type-transfer or output methods that are not dunders
# ===------------------------------------------------------------------=== #

fn to_string(
self, threshold_scientific: Int = 28, line_width: Int = 0
) -> String:
fn to_string(self, precision: Int = 28, line_width: Int = 0) -> String:
"""Returns string representation of the number.

Args:
threshold_scientific: The threshold for scientific notation.
precision: The threshold for scientific notation.
If the digits to display is greater than this value,
the number is represented in scientific notation.
line_width: The maximum line width for the string representation.
Expand All @@ -313,41 +311,65 @@ struct BigDecimal:

Returns:
A string representation of the number.

Notes:

Two cases when scientific notation is used:
1. abs(exponent) > `precision`.
2. There are no less than `precision` - 1 leading zeros after decimal.
3. Explicitly set to True.
"""

if self.coefficient.is_unitialized():
return String("Unitilialized maginitude of BigDecimal")

var result = String("-") if self.sign else String("")

var coefficient_string = self.coefficient.to_string()

if self.scale == 0:
result += coefficient_string

elif self.scale > 0:
if self.scale < len(coefficient_string):
# Example: 123_456 with scale 3 -> 123.456
result += coefficient_string[
: len(coefficient_string) - self.scale
]
result += "."
result += coefficient_string[
len(coefficient_string) - self.scale :
]
else:
# Example: 123_456 with scale 6 -> 0.123_456
# Example: 123_456 with scale 7 -> 0.012_345_6
result += "0."
result += "0" * (self.scale - len(coefficient_string))
result += coefficient_string
# Check whether scientific notation is needed
var exponent = self.coefficient.number_of_digits() - 1 - self.scale
# var leading_zeros = -exponent - 1
# criterion: leading_zeros >= precision - 1
if (exponent > precision) or (-exponent >= precision):
# Scientific notation
var exponent_string = String(exponent)
result += coefficient_string[0]
result += "."
result += coefficient_string[1:]
result += "E"
if exponent > 0:
result += "+"
result += exponent_string

else:
# scale < 0
# Example: 12_345 with scale -3 -> 12_345_000
result += coefficient_string
result += "0" * (-self.scale)
# Normal notation
if self.scale == 0:
result += coefficient_string

elif self.scale > 0:
if self.scale < len(coefficient_string):
# Example: 123_456 with scale 3 -> 123.456
result += coefficient_string[
: len(coefficient_string) - self.scale
]
result += "."
result += coefficient_string[
len(coefficient_string) - self.scale :
]
else:
# Example: 123_456 with scale 6 -> 0.123_456
# Example: 123_456 with scale 7 -> 0.012_345_6
result += "0."
result += "0" * (self.scale - len(coefficient_string))
result += coefficient_string

else:
# scale < 0
# Example: 12_345 with scale -3 -> 12_345_000
result += coefficient_string
result += "0" * (-self.scale)

# Split the result in multiple lines if line_width > 0
if line_width > 0:
var start = 0
var end = line_width
Expand Down Expand Up @@ -424,6 +446,17 @@ struct BigDecimal:
# Other methods
# ===------------------------------------------------------------------=== #

fn exponent(self) -> Int:
"""Returns the exponent of the number in scientific notation.

Notes:

123.45 (coefficient = 12345, scale = 2) is represented as 1.2345E+2.
0.00123 (coefficient = 123, scale = 5) is represented as 1.23E-3.
123000 (coefficient = 123, scale = -3) is represented as 1.23E+5.
"""
return self.coefficient.number_of_digits() - 1 - self.scale

fn extend_precision(self, precision_diff: Int) raises -> BigDecimal:
"""Returns a number with additional decimal places (trailing zeros).
This multiplies the coefficient by 10^precision_diff and increases
Expand Down
2 changes: 1 addition & 1 deletion tests/bigdecimal/test_data/bigdecimal_divide.toml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ description = "Small number division"
[[division_tests]]
a = "1000000000000000000000000000000"
b = "0.0000000000000000000000000001"
expected = "10000000000000000000000000000000000000000000000000000000000"
expected = "1.000000000000000000000000000E+58"
description = "Large divided by small"

[[division_tests]]
Expand Down