diff --git a/src/decimo/bigdecimal/arithmetics.mojo b/src/decimo/bigdecimal/arithmetics.mojo index 1cb61b8..e3463fb 100644 --- a/src/decimo/bigdecimal/arithmetics.mojo +++ b/src/decimo/bigdecimal/arithmetics.mojo @@ -20,6 +20,7 @@ Implements functions for mathematical operations on BigDecimal objects. from std import math +from decimo.errors import ZeroDivisionError from decimo.rounding_mode import RoundingMode # ===----------------------------------------------------------------------=== # @@ -319,7 +320,12 @@ def true_divide( """ # Check for division by zero if y.coefficient.is_zero(): - raise Error("bigdecimal.arithmetics.true_divide(): Division by zero") + raise Error( + ZeroDivisionError( + message="Division by zero.", + function="true_divide()", + ) + ) # Handle dividend of zero if x.coefficient.is_zero(): @@ -692,7 +698,12 @@ def true_divide_inexact( # Check for division by zero if x2.coefficient.is_zero(): - raise Error("Division by zero") + raise Error( + ZeroDivisionError( + message="Division by zero.", + function="true_divide_inexact()", + ) + ) # Handle dividend of zero if x1.coefficient.is_zero(): @@ -905,7 +916,12 @@ def truncate_divide(x1: BigDecimal, x2: BigDecimal) raises -> BigDecimal: """ # Check for division by zero if x2.coefficient.is_zero(): - raise Error("Division by zero") + raise Error( + ZeroDivisionError( + message="Division by zero.", + function="truncate_divide()", + ) + ) # Handle dividend of zero if x1.coefficient.is_zero(): @@ -945,7 +961,12 @@ def truncate_modulo( """ # Check for division by zero if x2.coefficient.is_zero(): - raise Error("Division by zero") + raise Error( + ZeroDivisionError( + message="Division by zero.", + function="truncate_modulo()", + ) + ) return subtract( x1, diff --git a/src/decimo/bigdecimal/bigdecimal.mojo b/src/decimo/bigdecimal/bigdecimal.mojo index 346ff00..2da23b7 100644 --- a/src/decimo/bigdecimal/bigdecimal.mojo +++ b/src/decimo/bigdecimal/bigdecimal.mojo @@ -27,7 +27,7 @@ from std.memory import UnsafePointer from std.python import PythonObject from std import testing -from decimo.errors import DecimoError +from decimo.errors import DecimoError, ConversionError, ValueError from decimo.rounding_mode import RoundingMode from decimo.bigdecimal.rounding import round_to_precision from decimo.bigint10.bigint10 import BigInt10 @@ -362,14 +362,22 @@ struct BigDecimal( return Self(coefficient=BigUInt.zero(), scale=0, sign=False) if value != value: # Check for NaN - raise Error("`from_scalar()`: Cannot convert NaN to BigUInt") + raise Error( + ValueError( + message="Cannot convert NaN to BigDecimal.", + function="BigDecimal.from_scalar()", + ) + ) # 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), + ConversionError( + message="Cannot convert scalar to BigDecimal.", + function="BigDecimal.from_scalar()", + previous_error=e^, + ) ) @staticmethod @@ -537,11 +545,9 @@ struct BigDecimal( except e: raise Error( - DecimoError( - file="src/decimo/bigdecimal/bigdecimal.mojo", - function="from_python_decimal()", - message="Failed to convert Python Decimal to BigDecimal: " - + "as_tuple() returned invalid data or conversion failed.", + ConversionError( + message="Failed to convert Python Decimal to BigDecimal.", + function="BigDecimal.from_python_decimal()", previous_error=e^, ), ) diff --git a/src/decimo/bigdecimal/constants.mojo b/src/decimo/bigdecimal/constants.mojo index 078aeac..f2b5536 100644 --- a/src/decimo/bigdecimal/constants.mojo +++ b/src/decimo/bigdecimal/constants.mojo @@ -18,6 +18,7 @@ """ from decimo.bigdecimal.bigdecimal import BigDecimal +from decimo.errors import ValueError from decimo.bigint10.bigint10 import BigInt10 from decimo.rounding_mode import RoundingMode @@ -164,7 +165,11 @@ def pi(precision: Int) raises -> BigDecimal: """ if precision < 0: - raise Error("Precision must be non-negative") + raise Error( + ValueError( + message="Precision must be non-negative", function="pi()" + ) + ) # TODO: When global variables are supported, # we can check if we have a cached value for the requested precision. diff --git a/src/decimo/bigdecimal/exponential.mojo b/src/decimo/bigdecimal/exponential.mojo index 36a3951..b63de43 100644 --- a/src/decimo/bigdecimal/exponential.mojo +++ b/src/decimo/bigdecimal/exponential.mojo @@ -19,6 +19,7 @@ from std import math from decimo.bigdecimal.bigdecimal import BigDecimal +from decimo.errors import ValueError, OverflowError, ZeroDivisionError from decimo.rounding_mode import RoundingMode # ===----------------------------------------------------------------------=== # @@ -235,11 +236,20 @@ def power( # Special cases if base.coefficient.is_zero(): if exponent.coefficient.is_zero(): - raise Error("Error in power: 0^0 is undefined") + raise Error( + ValueError( + message="0^0 is undefined.", + function="power()", + ) + ) elif exponent.sign: raise Error( - "Error in power: Division by zero (negative exponent with zero" - " base)" + ZeroDivisionError( + message=( + "Division by zero (negative exponent with zero base)." + ), + function="power()", + ) ) else: return BigDecimal(BigUInt.zero(), 0, False) @@ -264,8 +274,13 @@ def power( # Check for negative base with non-integer exponent if base.sign and not exponent.is_integer(): raise Error( - "Error in power: Negative base with non-integer exponent would" - " produce a complex result" + ValueError( + message=( + "Negative base with non-integer exponent would produce" + " a complex result." + ), + function="power()", + ) ) # Optimization for integer exponents @@ -434,7 +449,12 @@ def root(x: BigDecimal, n: BigDecimal, precision: Int) raises -> BigDecimal: # Check for n = 0 if n.coefficient.is_zero(): - raise Error("Error in `root`: Cannot compute zeroth root") + raise Error( + ValueError( + message="Cannot compute zeroth root.", + function="root()", + ) + ) # Special case for integer roots - use more efficient implementation if not n.sign: @@ -493,8 +513,13 @@ def root(x: BigDecimal, n: BigDecimal, precision: Int) raises -> BigDecimal: var n_is_odd_reciprocal = is_odd_reciprocal(n) if not n_is_integer and not n_is_odd_reciprocal: raise Error( - "Error in `root`: Cannot compute non-odd-integer root of a" - " negative number" + ValueError( + message=( + "Cannot compute non-odd-integer root of a negative" + " number." + ), + function="root()", + ) ) elif n_is_integer: var result = integer_root(x, n, precision) @@ -549,13 +574,28 @@ def integer_root( # Handle special case: n must be a positive integer if n.sign: - raise Error("Error in `root`: Root value must be positive") + raise Error( + ValueError( + message="Root value must be positive.", + function="integer_root()", + ) + ) if not n.is_integer(): - raise Error("Error in `root`: Root value must be an integer") + raise Error( + ValueError( + message="Root value must be an integer.", + function="integer_root()", + ) + ) if n.coefficient.is_zero(): - raise Error("Error in `root`: Cannot compute zeroth root") + raise Error( + ValueError( + message="Cannot compute zeroth root.", + function="integer_root()", + ) + ) # Special case: n = 1 (1st root is just the number itself) if n.is_one(): @@ -594,7 +634,10 @@ def integer_root( result_sign = True else: # n_uint.words[0] % 2 == 0: # Even root raise Error( - "Error in `root`: Cannot compute even root of a negative number" + ValueError( + message="Cannot compute even root of a negative number.", + function="integer_root()", + ) ) # Extract n as Int for Newton's method @@ -1183,7 +1226,10 @@ def sqrt_exact(x: BigDecimal, precision: Int) raises -> BigDecimal: # Handle special cases if x.sign: raise Error( - "Error in `sqrt`: Cannot compute square root of negative number" + ValueError( + message="Cannot compute square root of a negative number.", + function="sqrt_exact()", + ) ) if x.coefficient.is_zero(): @@ -1316,7 +1362,10 @@ def sqrt_reciprocal(x: BigDecimal, precision: Int) raises -> BigDecimal: # Handle special cases if x.sign: raise Error( - "Error in `sqrt`: Cannot compute square root of negative number" + ValueError( + message="Cannot compute square root of a negative number.", + function="sqrt_reciprocal()", + ) ) if x.coefficient.is_zero(): @@ -1498,7 +1547,10 @@ def sqrt_newton(x: BigDecimal, precision: Int) raises -> BigDecimal: # Handle special cases if x.sign: raise Error( - "Error in `sqrt`: Cannot compute square root of negative number" + ValueError( + message="Cannot compute square root of a negative number.", + function="sqrt_newton()", + ) ) if x.coefficient.is_zero(): @@ -1583,7 +1635,10 @@ def sqrt_decimal_approach(x: BigDecimal, precision: Int) raises -> BigDecimal: # Handle special cases if x.sign: raise Error( - "Error in `sqrt`: Cannot compute square root of negative number" + ValueError( + message="Cannot compute square root of a negative number.", + function="sqrt_decimal_approach()", + ) ) if x.coefficient.is_zero(): @@ -1765,7 +1820,11 @@ def exp(x: BigDecimal, precision: Int) raises -> BigDecimal: # For very large positive values, result will overflow BigDecimal capacity # TODO: Use BigInt10 as scale can avoid overflow in this case if not x.sign and x.adjusted() >= 20: # x > 10^20 - raise Error("Error in `exp`: Result too large to represent") + raise Error( + OverflowError( + message="Result too large to represent", function="exp()" + ) + ) # For very large negative values, result will be effectively zero if x.sign and x.adjusted() >= 20: # x < -10^20 @@ -1973,10 +2032,17 @@ def ln( # Handle special cases if x.sign: raise Error( - "Error in `ln`: Cannot compute logarithm of negative number" + ValueError( + message="Cannot compute logarithm of negative number", + function="ln()", + ) ) if x.coefficient.is_zero(): - raise Error("Error in `ln`: Cannot compute logarithm of zero") + raise Error( + ValueError( + message="Cannot compute logarithm of zero", function="ln()" + ) + ) if x == BigDecimal(BigUInt.one(), 0, False): return BigDecimal(BigUInt.zero(), 0, False) # ln(1) = 0 @@ -2066,21 +2132,36 @@ def log(x: BigDecimal, base: BigDecimal, precision: Int) raises -> BigDecimal: # Special cases if x.sign: raise Error( - "Error in log(): Cannot compute logarithm of a negative number" + ValueError( + message="Cannot compute logarithm of a negative number", + function="log()", + ) ) if x.coefficient.is_zero(): - raise Error("Error in log(): Cannot compute logarithm of zero") + raise Error( + ValueError( + message="Cannot compute logarithm of zero", function="log()" + ) + ) # Base validation if base.sign: - raise Error("Error in log(): Cannot use a negative base") + raise Error( + ValueError(message="Cannot use a negative base", function="log()") + ) if base.coefficient.is_zero(): - raise Error("Error in log(): Cannot use zero as a base") + raise Error( + ValueError(message="Cannot use zero as a base", function="log()") + ) if ( base.coefficient.number_of_digits() == base.scale + 1 and base.coefficient.words[-1] == 1 ): - raise Error("Error in log(): Cannot use base 1 for logarithm") + raise Error( + ValueError( + message="Cannot use base 1 for logarithm", function="log()" + ) + ) # Special cases if ( @@ -2129,10 +2210,17 @@ def log10(x: BigDecimal, precision: Int) raises -> BigDecimal: # Special cases if x.sign: raise Error( - "Error in log10(): Cannot compute logarithm of a negative number" + ValueError( + message="Cannot compute logarithm of a negative number", + function="log10()", + ) ) if x.coefficient.is_zero(): - raise Error("Error in log10(): Cannot compute logarithm of zero") + raise Error( + ValueError( + message="Cannot compute logarithm of zero", function="log10()" + ) + ) # Fast path: Powers of 10 are handled directly if x.coefficient.is_power_of_10(): diff --git a/src/decimo/bigdecimal/trigonometric.mojo b/src/decimo/bigdecimal/trigonometric.mojo index b424593..10f1f79 100644 --- a/src/decimo/bigdecimal/trigonometric.mojo +++ b/src/decimo/bigdecimal/trigonometric.mojo @@ -21,6 +21,7 @@ from std import time from decimo.bigdecimal.bigdecimal import BigDecimal +from decimo.errors import ValueError from decimo.rounding_mode import RoundingMode import decimo.bigdecimal.constants import decimo.bigdecimal.exponential @@ -364,7 +365,7 @@ def tan_cot(x: BigDecimal, precision: Int, is_tan: Bool) raises -> BigDecimal: # This is a design choice, not a mathematical one. # In practice, cot(0) should raise an error. raise Error( - "bigdecimal.trigonometric.tan_cot: cot(nπ) is undefined." + ValueError(message="cot(nπ) is undefined", function="tan_cot()") ) var pi = decimo.bigdecimal.constants.pi(precision=working_precision_pi) @@ -429,7 +430,9 @@ def csc(x: BigDecimal, precision: Int) raises -> BigDecimal: This function calculates csc(x) = 1 / sin(x). """ if x.is_zero(): - raise Error("bigdecimal.trigonometric.csc: csc(nπ) is undefined.") + raise Error( + ValueError(message="csc(nπ) is undefined", function="csc()") + ) comptime BUFFER_DIGITS = 9 var working_precision = precision + BUFFER_DIGITS diff --git a/src/decimo/bigfloat/bigfloat.mojo b/src/decimo/bigfloat/bigfloat.mojo index ecf4a0d..9b03360 100644 --- a/src/decimo/bigfloat/bigfloat.mojo +++ b/src/decimo/bigfloat/bigfloat.mojo @@ -38,6 +38,7 @@ from std.memory import UnsafePointer from decimo.bigdecimal.bigdecimal import BigDecimal from decimo.biguint.biguint import BigUInt +from decimo.errors import DecimoError, ConversionError from decimo.bigfloat.mpfr_wrapper import ( mpfrw_available, mpfrw_init, @@ -146,13 +147,22 @@ struct BigFloat(Comparable, Movable, Writable): """ if not mpfrw_available(): raise Error( - "BigFloat requires MPFR" - " (brew install mpfr / apt install libmpfr-dev)" + DecimoError( + message=( + "BigFloat requires MPFR (brew install mpfr / apt" + " install libmpfr-dev)" + ) + ) ) var bits = _dps_to_bits(precision) self.handle = mpfrw_init(bits) if self.handle < 0: - raise Error("BigFloat: MPFR handle pool exhausted") + raise Error( + DecimoError( + message="MPFR handle pool exhausted", + function="BigFloat.__init__()", + ) + ) self.precision = precision var s_bytes = value.as_bytes() var result_code = mpfrw_set_str( @@ -162,7 +172,12 @@ struct BigFloat(Comparable, Movable, Writable): ) if result_code != 0: mpfrw_clear(self.handle) - raise Error("BigFloat: invalid number string: " + value) + raise Error( + ConversionError( + message="Invalid number string: " + value, + function="BigFloat.__init__()", + ) + ) def __init__(out self, value: Int, precision: Int = PRECISION) raises: """Creates a BigFloat from an integer. @@ -229,7 +244,12 @@ struct BigFloat(Comparable, Movable, Writable): var d = digits if digits > 0 else self.precision var address = mpfrw_get_str(self.handle, Int32(d)) if address == 0: - raise Error("BigFloat: failed to export string") + raise Error( + ConversionError( + message="Failed to export string", + function="BigFloat.to_string()", + ) + ) var result = _read_c_string(address) mpfrw_free_str(address) return result @@ -309,7 +329,12 @@ struct BigFloat(Comparable, Movable, Writable): self.handle, Int32(d), UnsafePointer(to=exp) ) if address == 0: - raise Error("BigFloat.to_bigdecimal: mpfr_get_str failed") + raise Error( + ConversionError( + message="mpfr_get_str failed", + function="BigFloat.to_bigdecimal()", + ) + ) # 2. Single memcpy into a Mojo-owned buffer comptime ASCII_MINUS: UInt8 = 45 # ord("-") @@ -443,7 +468,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_neg(h, self.handle) return Self(_handle=h, _precision=self.precision) @@ -455,7 +480,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_abs(h, self.handle) return Self(_handle=h, _precision=self.precision) @@ -475,7 +500,7 @@ struct BigFloat(Comparable, Movable, Writable): var prec = max(self.precision, other.precision) var h = mpfrw_init(_dps_to_bits(prec)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_add(h, self.handle, other.handle) return Self(_handle=h, _precision=prec) @@ -491,7 +516,7 @@ struct BigFloat(Comparable, Movable, Writable): var prec = max(self.precision, other.precision) var h = mpfrw_init(_dps_to_bits(prec)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_sub(h, self.handle, other.handle) return Self(_handle=h, _precision=prec) @@ -507,7 +532,7 @@ struct BigFloat(Comparable, Movable, Writable): var prec = max(self.precision, other.precision) var h = mpfrw_init(_dps_to_bits(prec)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_mul(h, self.handle, other.handle) return Self(_handle=h, _precision=prec) @@ -523,7 +548,7 @@ struct BigFloat(Comparable, Movable, Writable): var prec = max(self.precision, other.precision) var h = mpfrw_init(_dps_to_bits(prec)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_div(h, self.handle, other.handle) return Self(_handle=h, _precision=prec) @@ -550,7 +575,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_sqrt(h, self.handle) return Self(_handle=h, _precision=self.precision) @@ -562,7 +587,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_exp(h, self.handle) return Self(_handle=h, _precision=self.precision) @@ -574,7 +599,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_log(h, self.handle) return Self(_handle=h, _precision=self.precision) @@ -586,7 +611,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_sin(h, self.handle) return Self(_handle=h, _precision=self.precision) @@ -598,7 +623,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_cos(h, self.handle) return Self(_handle=h, _precision=self.precision) @@ -610,7 +635,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_tan(h, self.handle) return Self(_handle=h, _precision=self.precision) @@ -626,7 +651,7 @@ struct BigFloat(Comparable, Movable, Writable): var prec = max(self.precision, exponent.precision) var h = mpfrw_init(_dps_to_bits(prec)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_pow(h, self.handle, exponent.handle) return Self(_handle=h, _precision=prec) @@ -641,7 +666,7 @@ struct BigFloat(Comparable, Movable, Writable): """ var h = mpfrw_init(_dps_to_bits(self.precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_rootn_ui(h, self.handle, n) return Self(_handle=h, _precision=self.precision) @@ -657,11 +682,15 @@ struct BigFloat(Comparable, Movable, Writable): """ if not mpfrw_available(): raise Error( - "BigFloat requires MPFR" - " (brew install mpfr / apt install libmpfr-dev)" + DecimoError( + message=( + "BigFloat requires MPFR (brew install mpfr / apt" + " install libmpfr-dev)" + ) + ) ) var h = mpfrw_init(_dps_to_bits(precision)) if h < 0: - raise Error("BigFloat: handle allocation failed") + raise Error(DecimoError(message="Handle allocation failed")) mpfrw_const_pi(h) return BigFloat(_handle=h, _precision=precision) diff --git a/src/decimo/bigint/arithmetics.mojo b/src/decimo/bigint/arithmetics.mojo index 9708fad..06515c7 100644 --- a/src/decimo/bigint/arithmetics.mojo +++ b/src/decimo/bigint/arithmetics.mojo @@ -37,7 +37,7 @@ from std.memory import memcpy, memset_zero from decimo.bigint.bigint import BigInt from decimo.bigint.comparison import compare_magnitudes -from decimo.errors import DecimoError +from decimo.errors import DecimoError, ZeroDivisionError # Karatsuba cutoff: operands with this many words or fewer use schoolbook. @@ -716,11 +716,9 @@ def _divmod_magnitudes( break if divisor_is_zero: raise Error( - DecimoError( - file="src/decimo/bigint/arithmetics", + ZeroDivisionError( function="_divmod_magnitudes()", message="Division by zero", - previous_error=None, ) ) @@ -1155,10 +1153,8 @@ def _divmod_knuth_d_from_slices( if len_b_eff <= 0: raise Error( DecimoError( - file="src/decimo/bigint/arithmetics", function="_divmod_knuth_d_from_slices()", message="Division by zero in B-Z base case", - previous_error=None, ) ) @@ -2343,7 +2339,6 @@ def power(base: BigInt, exponent: Int) raises -> BigInt: if exponent < 0: raise Error( DecimoError( - file="src/decimo/bigint/arithmetics.mojo", function="power()", message=( "The exponent " @@ -2351,7 +2346,6 @@ def power(base: BigInt, exponent: Int) raises -> BigInt: + " is negative.\n" + "Consider using a non-negative exponent." ), - previous_error=None, ) ) @@ -2361,7 +2355,6 @@ def power(base: BigInt, exponent: Int) raises -> BigInt: if exponent >= 1_000_000_000: raise Error( DecimoError( - file="src/decimo/bigint/arithmetics.mojo", function="power()", message=( "The exponent " @@ -2369,7 +2362,6 @@ def power(base: BigInt, exponent: Int) raises -> BigInt: + " is too large.\n" + "Consider using an exponent below 1_000_000_000." ), - previous_error=None, ) ) diff --git a/src/decimo/bigint/bigint.mojo b/src/decimo/bigint/bigint.mojo index 39c7641..9e5b175 100644 --- a/src/decimo/bigint/bigint.mojo +++ b/src/decimo/bigint/bigint.mojo @@ -38,7 +38,12 @@ import decimo.bigint.number_theory import decimo.str from decimo.bigint10.bigint10 import BigInt10 from decimo.biguint.biguint import BigUInt -from decimo.errors import DecimoError, ConversionError +from decimo.errors import ( + DecimoError, + ConversionError, + OverflowError, + ValueError, +) # Type aliases comptime BInt = BigInt @@ -465,7 +470,6 @@ struct BigInt( if scale >= len(coef): raise Error( ConversionError( - file="src/decimo/bigint/bigint.mojo", function="BigInt.from_string(value: String)", message=( 'The input value "' @@ -473,7 +477,6 @@ struct BigInt( + '" is not an integer.\n' + "The scale is larger than the number of digits." ), - previous_error=None, ) ) # Check that the fractional digits are all zero @@ -481,7 +484,6 @@ struct BigInt( if coef[-i] != 0: raise Error( ConversionError( - file="src/decimo/bigint/bigint.mojo", function="BigInt.from_string(value: String)", message=( 'The input value "' @@ -489,7 +491,6 @@ struct BigInt( + '" is not an integer.\n' + "The fractional part is not zero." ), - previous_error=None, ) ) # Remove fractional zeros from coefficient @@ -628,7 +629,12 @@ struct BigInt( # Int is 64-bit, so we need at most 2 words to represent it. # Int.MAX = 9_223_372_036_854_775_807 = 0x7FFF_FFFF_FFFF_FFFF if len(self.words) > 2: - raise Error("BigInt.to_int(): The number exceeds the size of Int") + raise Error( + OverflowError( + message="The number exceeds the size of Int", + function="BigInt.to_int()", + ) + ) var magnitude: UInt64 = UInt64(self.words[0]) if len(self.words) == 2: @@ -638,7 +644,10 @@ struct BigInt( # Negative: check against Int.MIN magnitude (2^63) if magnitude > UInt64(9_223_372_036_854_775_808): raise Error( - "BigInt.to_int(): The number exceeds the size of Int" + OverflowError( + message="The number exceeds the size of Int", + function="BigInt.to_int()", + ) ) if magnitude == UInt64(9_223_372_036_854_775_808): return Int.MIN @@ -647,7 +656,10 @@ struct BigInt( # Positive: check against Int.MAX (2^63 - 1) if magnitude > UInt64(9_223_372_036_854_775_807): raise Error( - "BigInt.to_int(): The number exceeds the size of Int" + OverflowError( + message="The number exceeds the size of Int", + function="BigInt.to_int()", + ) ) return Int(magnitude) @@ -974,9 +986,7 @@ struct BigInt( except e: raise Error( DecimoError( - message=None, function="BigInt.__floordiv__()", - file="src/decimo/bigint/bigint.mojo", previous_error=e^, ) ) @@ -996,9 +1006,7 @@ struct BigInt( except e: raise Error( DecimoError( - message=None, function="BigInt.__mod__()", - file="src/decimo/bigint/bigint.mojo", previous_error=e^, ) ) @@ -1018,9 +1026,7 @@ struct BigInt( except e: raise Error( DecimoError( - message=None, function="BigInt.__divmod__()", - file="src/decimo/bigint/bigint.mojo", previous_error=e^, ) ) @@ -1459,12 +1465,22 @@ struct BigInt( Error: If the exponent is negative or too large. """ if exponent.is_negative(): - raise Error("BigInt.power(): Exponent must be non-negative") + raise Error( + ValueError( + message="Exponent must be non-negative", + function="BigInt.power()", + ) + ) var exp_int: Int try: exp_int = exponent.to_int() except e: - raise Error("BigInt.power(): Exponent too large to fit in Int") + raise Error( + OverflowError( + message="Exponent too large to fit in Int", + function="BigInt.power()", + ) + ) return self.power(exp_int) def sqrt(self) raises -> Self: diff --git a/src/decimo/bigint/exponential.mojo b/src/decimo/bigint/exponential.mojo index 9671b86..ab50c51 100644 --- a/src/decimo/bigint/exponential.mojo +++ b/src/decimo/bigint/exponential.mojo @@ -283,10 +283,8 @@ def sqrt(x: BigInt) raises -> BigInt: if x.is_negative(): raise Error( DecimoError( - file="src/decimo/bigint/exponential.mojo", function="sqrt()", message="Cannot compute square root of a negative number", - previous_error=None, ) ) diff --git a/src/decimo/bigint/number_theory.mojo b/src/decimo/bigint/number_theory.mojo index f6b6f48..a211567 100644 --- a/src/decimo/bigint/number_theory.mojo +++ b/src/decimo/bigint/number_theory.mojo @@ -247,20 +247,16 @@ def mod_pow(base: BigInt, exponent: BigInt, modulus: BigInt) raises -> BigInt: if exponent.is_negative(): raise Error( DecimoError( - file="src/decimo/bigint/number_theory.mojo", function="mod_pow()", message="Exponent must be non-negative", - previous_error=None, ) ) if not modulus.is_positive(): raise Error( DecimoError( - file="src/decimo/bigint/number_theory.mojo", function="mod_pow()", message="Modulus must be positive", - previous_error=None, ) ) @@ -332,10 +328,8 @@ def mod_inverse(a: BigInt, modulus: BigInt) raises -> BigInt: if not modulus.is_positive(): raise Error( DecimoError( - file="src/decimo/bigint/number_theory.mojo", function="mod_inverse()", message="Modulus must be positive", - previous_error=None, ) ) @@ -346,10 +340,8 @@ def mod_inverse(a: BigInt, modulus: BigInt) raises -> BigInt: if not g.is_one(): raise Error( DecimoError( - file="src/decimo/bigint/number_theory.mojo", function="mod_inverse()", message="Modular inverse does not exist (gcd != 1)", - previous_error=None, ) ) diff --git a/src/decimo/bigint10/arithmetics.mojo b/src/decimo/bigint10/arithmetics.mojo index ccd00d1..8774f4e 100644 --- a/src/decimo/bigint10/arithmetics.mojo +++ b/src/decimo/bigint10/arithmetics.mojo @@ -227,9 +227,7 @@ def floor_divide(x1: BigInt10, x2: BigInt10) raises -> BigInt10: except e: raise Error( DecimoError( - file="src/decimo/bigint10/arithmetics", function="floor_divide()", - message=None, previous_error=e^, ), ) @@ -244,9 +242,7 @@ def floor_divide(x1: BigInt10, x2: BigInt10) raises -> BigInt10: except e: raise Error( DecimoError( - file="src/decimo/bigint10/arithmetics", function="floor_divide()", - message=None, previous_error=e^, ), ) @@ -276,9 +272,7 @@ def truncate_divide(x1: BigInt10, x2: BigInt10) raises -> BigInt10: except e: raise Error( DecimoError( - file="src/decimo/bigint10/arithmetics", function="truncate_divide()", - message=None, previous_error=e^, ), ) @@ -313,9 +307,7 @@ def floor_modulo(x1: BigInt10, x2: BigInt10) raises -> BigInt10: except e: raise Error( DecimoError( - file="src/decimo/bigint10/arithmetics", function="floor_modulo()", - message=None, previous_error=e^, ), ) @@ -330,9 +322,7 @@ def floor_modulo(x1: BigInt10, x2: BigInt10) raises -> BigInt10: except e: raise Error( DecimoError( - file="src/decimo/bigint10/arithmetics", function="floor_modulo()", - message=None, previous_error=e^, ), ) @@ -362,9 +352,7 @@ def truncate_modulo(x1: BigInt10, x2: BigInt10) raises -> BigInt10: except e: raise Error( DecimoError( - file="src/decimo/bigint10/arithmetics", function="truncate_modulo()", - message=None, previous_error=e^, ), ) diff --git a/src/decimo/bigint10/bigint10.mojo b/src/decimo/bigint10/bigint10.mojo index 48770b8..00d5c89 100644 --- a/src/decimo/bigint10/bigint10.mojo +++ b/src/decimo/bigint10/bigint10.mojo @@ -30,7 +30,12 @@ import decimo.bigint10.arithmetics import decimo.bigint10.comparison from decimo.bigdecimal.bigdecimal import BigDecimal from decimo.biguint.biguint import BigUInt -from decimo.errors import DecimoError +from decimo.errors import ( + DecimoError, + ValueError, + OverflowError, + ConversionError, +) import decimo.str @@ -114,11 +119,9 @@ struct BigInt10( except e: raise Error( DecimoError( - file="src/decimo/bigint10/bigint10.mojo", function=( "BigInt10.__init__(var words: List[UInt32], sign: Bool)" ), - message=None, previous_error=e^, ) ) @@ -155,7 +158,13 @@ struct BigInt10( try: self = Self.from_string(value) except e: - raise Error("Error in `BigInt10.__init__()` with String: ", e) + raise Error( + ConversionError( + message="Cannot initialize BigInt10 from String.", + function="BigInt10.__init__()", + previous_error=e^, + ) + ) # TODO: If Mojo makes Int type an alias of SIMD[DType.index, 1], # we can remove this method. @@ -222,12 +231,10 @@ struct BigInt10( except e: raise Error( DecimoError( - file="src/decimo/bigint10/bigint10.mojo", function=( "BigInt10.from_list(var words: List[UInt32], sign:" " Bool)" ), - message=None, previous_error=e^, ) ) @@ -256,8 +263,12 @@ struct BigInt10( for word in words: if word > UInt32(999_999_999): raise Error( - "Error in `BigInt10.__init__()`: Word value exceeds maximum" - " value of 999_999_999" + ValueError( + message=( + "Word value exceeds maximum value of 999_999_999" + ), + function="BigInt10.__init__()", + ) ) else: list_of_words.append(word) @@ -396,10 +407,9 @@ struct BigInt10( return Self.from_string(py_str) except e: raise Error( - DecimoError( - file="src/decimo/bigint10/bigint10.mojo", - function="BigInt10.from_python_int(value: PythonObject)", + ConversionError( message="Failed to convert Python int to BigInt10.", + function="BigInt10.from_python_int()", previous_error=e^, ) ) @@ -459,8 +469,10 @@ struct BigInt10( if len(self.magnitude.words) > 3: raise Error( - "Error in `BigInt10.to_int()`: The number exceeds the size" - " of Int" + OverflowError( + message="The number exceeds the size of Int.", + function="BigInt10.to_int()", + ) ) var value: Int128 = 0 @@ -478,8 +490,10 @@ struct BigInt10( var int_max = Int.MAX if value < Int128(int_min) or value > Int128(int_max): raise Error( - "Error in `BigInt10.to_int()`: The number exceeds the size" - " of Int" + OverflowError( + message="The number exceeds the size of Int.", + function="BigInt10.to_int()", + ) ) return Int(value) @@ -622,9 +636,7 @@ struct BigInt10( except e: raise Error( DecimoError( - message=None, function="BigInt10.__floordiv__()", - file="src/decimo/bigint10/bigint10.mojo", previous_error=e^, ) ) @@ -644,9 +656,7 @@ struct BigInt10( except e: raise Error( DecimoError( - message=None, function="BigInt10.__mod__()", - file="src/decimo/bigint10/bigint10.mojo", previous_error=e^, ) ) @@ -1057,7 +1067,12 @@ struct BigInt10( The result of raising to the given power. """ if exponent > Self(BigUInt(raw_words=[0, 1]), sign=False): - raise Error("Error in `BigUInt.power()`: The exponent is too large") + raise Error( + OverflowError( + message="The exponent is too large.", + function="BigInt10.power()", + ) + ) var exponent_as_int = exponent.to_int() return self.power(exponent_as_int) diff --git a/src/decimo/biguint/arithmetics.mojo b/src/decimo/biguint/arithmetics.mojo index 4fba002..b3f9fe3 100644 --- a/src/decimo/biguint/arithmetics.mojo +++ b/src/decimo/biguint/arithmetics.mojo @@ -24,7 +24,12 @@ from std.memory import memcpy, memset_zero from decimo.biguint.biguint import BigUInt import decimo.biguint.comparison -from decimo.errors import DecimoError, OverflowError, ZeroDivisionError +from decimo.errors import ( + DecimoError, + OverflowError, + ValueError, + ZeroDivisionError, +) from decimo.rounding_mode import RoundingMode comptime CUTOFF_KARATSUBA = 64 @@ -105,10 +110,8 @@ def negative(x: BigUInt) raises -> BigUInt: ) raise Error( OverflowError( - file="src/decimo/biguint/arithmetics.mojo", function="negative()", message="Negative of non-zero unsigned integer is undefined", - previous_error=None, ) ) return BigUInt.zero() # Return zero @@ -537,13 +540,11 @@ def subtract_school(x: BigUInt, y: BigUInt) raises -> BigUInt: if comparison_result < 0: raise Error( OverflowError( - file="src/decimo/biguint/arithmetics.mojo", function="subtract_school()", message=( "biguint.arithmetics.subtract(): Result is negative due to" " x < y" ), - previous_error=None, ) ) @@ -633,13 +634,11 @@ def subtract_simd(x: BigUInt, y: BigUInt) raises -> BigUInt: if comparison_result < 0: raise Error( OverflowError( - file="src/decimo/biguint/arithmetics.mojo", function="subtract()", message=( "biguint.arithmetics.subtract(): Result is negative due to" " x < y" ), - previous_error=None, ) ) @@ -706,13 +705,11 @@ def subtract_inplace(mut x: BigUInt, y: BigUInt) raises -> None: elif comparison_result < 0: raise Error( OverflowError( - file="src/decimo/biguint/arithmetics.mojo", function="subtract_inplace()", message=( "biguint.arithmetics.subtract(): Result is negative due to" " x < y" ), - previous_error=None, ) ) @@ -1982,10 +1979,8 @@ def floor_divide(x: BigUInt, y: BigUInt) raises -> BigUInt: if y.is_zero(): raise Error( ZeroDivisionError( - file="src/decimo/biguint/arithmetics.mojo", function="floor_divide()", message="Division by zero", - previous_error=None, ) ) @@ -2083,7 +2078,11 @@ def floor_divide_school(x: BigUInt, y: BigUInt) raises -> BigUInt: # handled properly to improve performance. # CASE: y is zero if y.is_zero(): - raise Error("biguint.arithmetics.floor_divide(): Division by zero") + raise Error( + ZeroDivisionError( + message="Division by zero", function="floor_divide()" + ) + ) # CASE: Dividend is zero if x.is_zero(): @@ -3143,7 +3142,12 @@ def floor_divide_three_by_two_uint32( b = b1 * BASE + b0. """ if b1 < 500_000_000: - raise Error("b1 must be at least 500_000_000") + raise Error( + ValueError( + message="b1 must be at least 500_000_000", + function="floor_divide_three_by_two_uint32()", + ) + ) var a2a1 = UInt64(a2) * 1_000_000_000 + UInt64(a1) @@ -3194,18 +3198,43 @@ def floor_divide_four_by_two_uint32( """ if b1 < 500_000_000: - raise Error("b1 must be at least 500_000_000") + raise Error( + ValueError( + message="b1 must be at least 500_000_000", + function="floor_divide_four_by_two_uint32()", + ) + ) if a3 > b1: - raise Error("a must be less than b * 10^18") + raise Error( + ValueError( + message="a must be less than b * 10^18", + function="floor_divide_four_by_two_uint32()", + ) + ) elif a3 == b1: if a2 > b0: - raise Error("a must be less than b * 10^18") + raise Error( + ValueError( + message="a must be less than b * 10^18", + function="floor_divide_four_by_two_uint32()", + ) + ) elif a2 == b0: if a1 > 0: - raise Error("a must be less than b * 10^18") + raise Error( + ValueError( + message="a must be less than b * 10^18", + function="floor_divide_four_by_two_uint32()", + ) + ) elif a1 == 0: if a0 >= 0: - raise Error("a must be less than b * 10^18") + raise Error( + ValueError( + message="a must be less than b * 10^18", + function="floor_divide_four_by_two_uint32()", + ) + ) var q1, r1, r0 = floor_divide_three_by_two_uint32(a3, a2, a1, b1, b0) var q0, s1, s0 = floor_divide_three_by_two_uint32(r1, r0, a0, b1, b0) @@ -3248,7 +3277,11 @@ def ceil_divide(x1: BigUInt, x2: BigUInt) raises -> BigUInt: len(x2.words) == 1, "ceil_divide(): leading zero words", ) - raise Error("biguint.arithmetics.ceil_divide(): Division by zero") + raise Error( + ZeroDivisionError( + message="Division by zero", function="ceil_divide()" + ) + ) # Apply floor division and check if there is a remainder var quotient = floor_divide(x1, x2) @@ -3285,10 +3318,8 @@ def floor_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: ) raise Error( ZeroDivisionError( - file="src/decimo/biguint/arithmetics.py", function="floor_modulo()", message="Division by zero", - previous_error=None, ) ) @@ -3314,9 +3345,7 @@ def floor_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: except e: raise Error( DecimoError( - file="src/decimo/biguint/arithmetics.py", function="floor_modulo()", - message=None, previous_error=e^, ) ) @@ -3328,9 +3357,7 @@ def floor_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: except e: raise Error( DecimoError( - file="src/decimo/biguint/arithmetics.py", function="floor_modulo()", - message=None, previous_error=e^, ) ) @@ -3359,9 +3386,7 @@ def truncate_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: except e: raise Error( DecimoError( - file="src/decimo/biguint/arithmetics.py", function="truncate_modulo()", - message=None, previous_error=e^, ) ) @@ -3387,7 +3412,11 @@ def ceil_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: debug_assert[assert_mode="none"]( len(x2.words) == 1, "ceil_modulo(): leading zero words" ) - raise Error("Error in `ceil_modulo`: Division by zero") + raise Error( + ZeroDivisionError( + message="Division by zero", function="ceil_modulo()" + ) + ) # CASE: Dividend is zero if x1.is_zero(): @@ -3445,9 +3474,7 @@ def floor_divide_modulo( except e: raise Error( DecimoError( - file="src/decimo/biguint/arithmetics.py", function="floor_divide_modulo()", - message=None, previous_error=e^, ) ) @@ -3632,10 +3659,8 @@ def power_of_10(n: Int) raises -> BigUInt: if n < 0: raise Error( DecimoError( - file="src/decimo/biguint/arithmetics.py", function="power_of_10()", message="Negative exponent not supported", - previous_error=None, ) ) diff --git a/src/decimo/biguint/biguint.mojo b/src/decimo/biguint/biguint.mojo index b4f68a0..6ae8b00 100644 --- a/src/decimo/biguint/biguint.mojo +++ b/src/decimo/biguint/biguint.mojo @@ -205,9 +205,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__init__(var words: List[UInt32])", - message=None, previous_error=e^, ) ) @@ -252,9 +250,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__init__(value: Int)", - message=None, previous_error=e^, ) ) @@ -289,9 +285,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__init__(value: String)", - message=None, previous_error=e^, ) ) @@ -339,8 +333,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + " exceeds maximum value of 999_999_999" ), function="BigUInt.from_list()", - file="src/decimo/biguint/biguint.mojo", - previous_error=None, ) ) @@ -407,8 +399,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + " exceeds maximum value of 999_999_999" ), function="BigUInt.from_words()", - file="src/decimo/biguint/biguint.mojo", - previous_error=None, ) ) else: @@ -477,14 +467,12 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if value < 0: raise Error( OverflowError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.from_int(value: Int)", message=( "The input value " + String(value) + " is negative and is not compatible with BigUInt." ), - previous_error=None, ) ) @@ -679,7 +667,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if (not ignore_sign) and sign: raise Error( OverflowError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.from_string(value: String)", message=( 'The input value "' @@ -688,7 +675,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + "Consider using `ignore_sign=True` to ignore the" " sign." ), - previous_error=None, ) ) @@ -703,7 +689,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if scale >= len(coef): raise Error( ConversionError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.from_string(value: String)", message=( 'The input value "' @@ -711,14 +696,12 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + '" is not an integer.\n' + "The scale is larger than the number of digits." ), - previous_error=None, ) ) for i in range(1, scale + 1): if coef[-i] != 0: raise Error( ConversionError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.from_string(value: String)", message=( 'The input value "' @@ -726,7 +709,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + '" is not an integer.\n' + "The fractional part is not zero." ), - previous_error=None, ) ) coef.resize(len(coef) - scale, UInt8(0)) @@ -806,9 +788,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__int__()", - message=None, previous_error=e^, ) ) @@ -855,12 +835,10 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): var overflow_error: Error = Error( OverflowError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.to_int()", message="The number exceeds the size of Int (" + String(Int.MAX) + ")", - previous_error=None, ) ) if len(self.words) > 3: @@ -887,14 +865,12 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if self.is_uint64_overflow(): raise Error( OverflowError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.to_uint64()", message=( "The number exceeds the size of UInt64 (" + String(UInt64.MAX) + ")" ), - previous_error=None, ) ) @@ -1174,9 +1150,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__sub__(other: Self)", - message=None, previous_error=e^, ) ) @@ -1208,9 +1182,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__floordiv__(other: Self)", - message=None, previous_error=e^, ) ) @@ -1230,9 +1202,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__ceildiv__(other: Self)", - message=None, previous_error=e^, ) ) @@ -1252,9 +1222,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__mod__(other: Self)", - message=None, previous_error=e^, ) ) @@ -1274,9 +1242,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__divmod__(other: Self)", - message=None, previous_error=e^, ) ) @@ -1296,9 +1262,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__pow__(exponent: Self)", - message=None, previous_error=e^, ) ) @@ -1318,9 +1282,7 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): except e: raise Error( DecimoError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.__pow__(exponent: Int)", - message=None, previous_error=e^, ) ) @@ -1750,7 +1712,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if exponent < 0: raise Error( ValueError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.power(exponent: Int)", message=( "The exponent " @@ -1758,7 +1719,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + " is negative.\n" + "Consider using a non-negative exponent." ), - previous_error=None, ) ) @@ -1768,7 +1728,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if exponent >= 1_000_000_000: raise Error( ValueError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.power(exponent: Int)", message=( "The exponent " @@ -1776,7 +1735,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + " is too large.\n" + "Consider using an exponent below 1_000_000_000." ), - previous_error=None, ) ) @@ -1806,7 +1764,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if len(exponent.words) > 1: raise Error( ValueError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.power(exponent: BigUInt)", message=( "The exponent " @@ -1814,7 +1771,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + " is too large.\n" + "Consider using an exponent below 1_000_000_000." ), - previous_error=None, ) ) var exponent_as_int = exponent.to_int() @@ -2097,7 +2053,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if i < 0: raise Error( IndexError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.ith_digit(i: Int)", message=( "The index " @@ -2105,7 +2060,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + " is negative.\n" + "Consider using a non-negative index." ), - previous_error=None, ) ) if i >= len(self.words) * 9: @@ -2239,13 +2193,11 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if ndigits < 0: raise Error( ValueError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.remove_trailing_digits_with_rounding()", message=( "The number of digits to remove is negative: " + String(ndigits) ), - previous_error=None, ) ) if ndigits == 0: @@ -2253,7 +2205,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): if ndigits > self.number_of_digits(): raise Error( ValueError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.remove_trailing_digits_with_rounding()", message=( "The number of digits to remove is larger than the " @@ -2262,7 +2213,6 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): + " > " + String(self.number_of_digits()) ), - previous_error=None, ) ) @@ -2317,10 +2267,8 @@ struct BigUInt(Absable, Copyable, IntableRaising, Movable, Writable): else: raise Error( ValueError( - file="src/decimo/biguint/biguint.mojo", function="BigUInt.remove_trailing_digits_with_rounding()", message=("Unknown rounding mode: " + String(rounding_mode)), - previous_error=None, ) ) diff --git a/src/decimo/decimal128/arithmetics.mojo b/src/decimo/decimal128/arithmetics.mojo index ff93d9f..9c0caba 100644 --- a/src/decimo/decimal128/arithmetics.mojo +++ b/src/decimo/decimal128/arithmetics.mojo @@ -36,6 +36,11 @@ from std import testing from decimo.decimal128.decimal128 import Decimal128 from decimo.rounding_mode import RoundingMode +from decimo.errors import ( + DecimoError, + OverflowError, + ZeroDivisionError, +) import decimo.decimal128.utility @@ -124,7 +129,12 @@ def add(x1: Decimal128, x2: Decimal128) raises -> Decimal128: # Check for overflow (UInt128 can store values beyond our 96-bit limit) # We need to make sure the sum fits in 96 bits (our Decimal128 capacity) if summation > Decimal128.MAX_AS_UINT128: # 2^96-1 - raise Error("Error in `addition()`: Decimal128 overflow") + raise Error( + OverflowError( + message="Decimal128 overflow in addition.", + function="add()", + ) + ) return Decimal128.from_uint128(summation, 0, x1.is_negative()) @@ -154,7 +164,12 @@ def add(x1: Decimal128, x2: Decimal128) raises -> Decimal128: # Check for overflow (UInt128 can store values beyond our 96-bit limit) # We need to make sure the sum fits in 96 bits (our Decimal128 capacity) if summation > Decimal128.MAX_AS_UINT128: # 2^96-1 - raise Error("Error in `addition()`: Decimal128 overflow") + raise Error( + OverflowError( + message="Decimal128 overflow in addition.", + function="add()", + ) + ) # Determine the scale for the result var scale = UInt32( @@ -374,7 +389,13 @@ def subtract(x1: Decimal128, x2: Decimal128) raises -> Decimal128: try: return x1 + (-x2) except e: - raise Error("Error in `subtract()`; ", e) + raise Error( + DecimoError( + message="Subtraction failed.", + function="subtract()", + previous_error=e^, + ) + ) def negative(x: Decimal128) -> Decimal128: @@ -545,10 +566,12 @@ def multiply(x1: Decimal128, x2: Decimal128) raises -> Decimal128: var prod: UInt128 = UInt128(x1_coef) * UInt128(x2_coef) if prod > Decimal128.MAX_AS_UINT128: raise Error( - String( - "Error in `multiply()`: The product is {}, which" - " exceeds the capacity of Decimal128 (2^96-1)" - ).format(prod) + OverflowError( + message=String( + "The product {} exceeds Decimal128 capacity." + ).format(prod), + function="multiply()", + ) ) else: return Decimal128.from_uint128(prod, 0, is_negative) @@ -557,10 +580,12 @@ def multiply(x1: Decimal128, x2: Decimal128) raises -> Decimal128: else: var prod: UInt256 = UInt256(x1_coef) * UInt256(x2_coef) raise Error( - String( - "Error in `multiply()`: The product is {}, which exceeds" - " the capacity of Decimal128 (2^96-1)" - ).format(prod) + OverflowError( + message=String( + "The product {} exceeds Decimal128 capacity." + ).format(prod), + function="multiply()", + ) ) # SPECIAL CASE: Both operands are integers but with scales @@ -576,7 +601,12 @@ def multiply(x1: Decimal128, x2: Decimal128) raises -> Decimal128: x2_integral_part ) if prod > Decimal128.MAX_AS_UINT256: - raise Error("Error in `multiply()`: Decimal128 overflow") + raise Error( + OverflowError( + message="Decimal128 overflow in multiplication.", + function="multiply()", + ) + ) else: var num_digits = decimo.decimal128.utility.number_of_digits(prod) var final_scale = min( @@ -668,7 +698,12 @@ def multiply(x1: Decimal128, x2: Decimal128) raises -> Decimal128: if (num_digits_of_integral_part >= Decimal128.MAX_NUM_DIGITS) & ( truncated_prod_at_max_length > Decimal128.MAX_AS_UINT128 ): - raise Error("Error in `multiply()`: Decimal128 overflow") + raise Error( + OverflowError( + message="Decimal128 overflow in multiplication.", + function="multiply()", + ) + ) # Otherwise, the value will not overflow even after rounding # Determine the final scale after rounding @@ -729,7 +764,12 @@ def multiply(x1: Decimal128, x2: Decimal128) raises -> Decimal128: if (num_digits_of_integral_part >= Decimal128.MAX_NUM_DIGITS) & ( truncated_prod_at_max_length > Decimal128.MAX_AS_UINT256 ): - raise Error("Error in `multiply()`: Decimal128 overflow") + raise Error( + OverflowError( + message="Decimal128 overflow in multiplication.", + function="multiply()", + ) + ) # Otherwise, the value will not overflow even after rounding # Determine the final scale after rounding @@ -791,7 +831,12 @@ def divide(x1: Decimal128, x2: Decimal128) raises -> Decimal128: # 特例: 除數爲零 # Check for division by zero if x2.is_zero(): - raise Error("Error in `__truediv__`: Division by zero") + raise Error( + ZeroDivisionError( + message="Division by zero.", + function="divide()", + ) + ) # SPECIAL CASE: zero dividend # If dividend is zero, return zero with appropriate scale @@ -848,7 +893,12 @@ def divide(x1: Decimal128, x2: Decimal128) raises -> Decimal128: else: var quot = UInt256(x1_coef) * UInt256(10) ** (-diff_scale) if quot > Decimal128.MAX_AS_UINT256: - raise Error("Error in `true_divide()`: Decimal128 overflow") + raise Error( + OverflowError( + message="Decimal128 overflow in division.", + function="divide()", + ) + ) else: var low = UInt32(quot & 0xFFFFFFFF) var mid = UInt32((quot >> 32) & 0xFFFFFFFF) @@ -913,7 +963,12 @@ def divide(x1: Decimal128, x2: Decimal128) raises -> Decimal128: else: var quot = UInt256(quot) * UInt256(10) ** (-diff_scale) if quot > Decimal128.MAX_AS_UINT256: - raise Error("Error in `true_divide()`: Decimal128 overflow") + raise Error( + OverflowError( + message="Decimal128 overflow in division.", + function="divide()", + ) + ) else: var low = UInt32(quot & 0xFFFFFFFF) var mid = UInt32((quot >> 32) & 0xFFFFFFFF) @@ -1190,7 +1245,12 @@ def divide(x1: Decimal128, x2: Decimal128) raises -> Decimal128: (ndigits_quot_int_part == Decimal128.MAX_NUM_DIGITS) and (truncated_quot > Decimal128.MAX_AS_UINT256) ): - raise Error("Error in `true_divide()`: Decimal128 overflow") + raise Error( + OverflowError( + message="Decimal128 overflow in division.", + function="divide()", + ) + ) var scale_of_truncated_quot = ( Decimal128.MAX_NUM_DIGITS - ndigits_quot_int_part @@ -1241,7 +1301,13 @@ def truncate_divide(x1: Decimal128, x2: Decimal128) raises -> Decimal128: try: return divide(x1, x2).round(0, RoundingMode.down()) except e: - raise Error("Error in `divide()`: ", e) + raise Error( + DecimoError( + message="Division failed.", + function="truncate_divide()", + previous_error=e^, + ) + ) def modulo(x1: Decimal128, x2: Decimal128) raises -> Decimal128: @@ -1258,4 +1324,10 @@ def modulo(x1: Decimal128, x2: Decimal128) raises -> Decimal128: try: return x1 - (truncate_divide(x1, x2) * x2) except e: - raise Error("Error in `modulo()`: ", e) + raise Error( + DecimoError( + message="Modulo failed.", + function="modulo()", + previous_error=e^, + ) + ) diff --git a/src/decimo/decimal128/constants.mojo b/src/decimo/decimal128/constants.mojo index 6b09fa5..c634d94 100644 --- a/src/decimo/decimal128/constants.mojo +++ b/src/decimo/decimal128/constants.mojo @@ -17,6 +17,7 @@ """Useful constants for Decimal128 type.""" from decimo.decimal128.decimal128 import Decimal128 +from decimo.errors import ValueError # ===----------------------------------------------------------------------=== # # @@ -461,7 +462,12 @@ def N_DIVIDE_NEXT(n: Int) raises -> Decimal128: # 20/21 = 0.95238095238095238095238095238095... return Decimal128(0x33CF3CF4, 0xCD78948D, 0x1EC5E91C, 0x1C0000) else: - raise Error("N_DIVIDE_NEXT: n must be between 1 and 20, inclusive") + raise Error( + ValueError( + message="n must be between 1 and 20, inclusive.", + function="N_DIVIDE_NEXT()", + ) + ) # ===----------------------------------------------------------------------=== # diff --git a/src/decimo/decimal128/decimal128.mojo b/src/decimo/decimal128/decimal128.mojo index 3d87d10..60fca5f 100644 --- a/src/decimo/decimal128/decimal128.mojo +++ b/src/decimo/decimal128/decimal128.mojo @@ -32,6 +32,12 @@ import decimo.decimal128.constants import decimo.decimal128.exponential import decimo.decimal128.rounding from decimo.rounding_mode import RoundingMode +from decimo.errors import ( + DecimoError, + ValueError, + OverflowError, + ConversionError, +) import decimo.decimal128.utility comptime Dec128 = Decimal128 @@ -292,7 +298,11 @@ struct Decimal128( self = Decimal128.from_components(low, mid, high, scale, sign) except e: raise Error( - "Error in `Decimal128.__init__()` with five components: ", e + DecimoError( + message="Cannot initialize with five components.", + function="Decimal128.__init__()", + previous_error=e^, + ) ) def __init__(out self, value: Int): @@ -315,7 +325,13 @@ struct Decimal128( try: self = Decimal128.from_int(value, scale) except e: - raise Error("Error in `Decimal128.__init__()` with Int: ", e) + raise Error( + ConversionError( + message="Cannot initialize Decimal128 from Int.", + function="Decimal128.__init__()", + previous_error=e^, + ) + ) def __init__(out self, value: String) raises: """Initializes a Decimal128 from a string representation. @@ -327,7 +343,13 @@ struct Decimal128( try: self = Decimal128.from_string(value) except e: - raise Error("Error in `Decimal__init__()` with String: ", e) + raise Error( + ConversionError( + message="Cannot initialize Decimal128 from String.", + function="Decimal128.__init__()", + previous_error=e^, + ) + ) def __init__(out self, value: Float64) raises: """Initializes a Decimal128 from a floating-point value. @@ -340,7 +362,13 @@ struct Decimal128( try: self = Decimal128.from_float(value) except e: - raise Error("Error in `Decimal__init__()` with Float64: ", e) + raise Error( + ConversionError( + message="Cannot initialize Decimal128 from Float64.", + function="Decimal128.__init__()", + previous_error=e^, + ) + ) # ===------------------------------------------------------------------=== # # Constructing methods that are not dunders @@ -496,10 +524,12 @@ struct Decimal128( if scale > UInt32(Self.MAX_SCALE): raise Error( - String( - "Error in Decimal128 constructor with Int: Scale must be" - " between 0 and 28, but got {}" - ).format(scale) + ValueError( + message=String( + "Scale must be between 0 and 28, but got {}" + ).format(scale), + function="Decimal128.from_int()", + ) ) if value >= 0: @@ -538,18 +568,22 @@ struct Decimal128( if value >> 96 != 0: raise Error( - String( - "Error in Decimal128 constructor with UInt128: Value must" - " fit in 96 bits, but got {}" - ).format(value) + ValueError( + message=String( + "Value must fit in 96 bits, but got {}" + ).format(value), + function="Decimal128.from_uint128()", + ) ) if scale > UInt32(Self.MAX_SCALE): raise Error( - String( - "Error in Decimal128 constructor with five components:" - " Scale must be between 0 and 28, but got {}" - ).format(scale) + ValueError( + message=String( + "Scale must be between 0 and 28, but got {}" + ).format(scale), + function="Decimal128.from_uint128()", + ) ) var result = UnsafePointer(to=value).bitcast[Decimal128]()[] @@ -597,9 +631,11 @@ struct Decimal128( for byte in value_bytes: if byte > 127: raise Error( - String( - "There are invalid characters in decimal128 string: {}" - ).format(value) + ValueError( + message=String( + "Invalid characters in decimal128 string: {}" + ).format(value), + ) ) # Yuhao's notes: @@ -631,13 +667,24 @@ struct Decimal128( elif code == 45: unexpected_end_char = True if exponent_sign_read: - raise Error("Minus sign cannot appear twice in exponent.") + raise Error( + ValueError( + message=( + "Minus sign cannot appear twice in exponent." + ), + ) + ) elif exponent_notation_read: exponent_sign = True exponent_sign_read = True elif mantissa_sign_read: raise Error( - "Minus sign can only appear once at the begining." + ValueError( + message=( + "Minus sign can only appear once at the" + " beginning." + ), + ) ) else: mantissa_sign = True @@ -646,12 +693,23 @@ struct Decimal128( elif code == 43: unexpected_end_char = True if exponent_sign_read: - raise Error("Plus sign cannot appear twice in exponent.") + raise Error( + ValueError( + message=( + "Plus sign cannot appear twice in exponent." + ), + ) + ) elif exponent_notation_read: exponent_sign_read = True elif mantissa_sign_read: raise Error( - "Plus sign can only appear once at the begining." + ValueError( + message=( + "Plus sign can only appear once at the" + " beginning." + ), + ) ) else: mantissa_sign_read = True @@ -659,7 +717,11 @@ struct Decimal128( elif code == 46: unexpected_end_char = False if decimal_point_read: - raise Error("Decimal point can only appear once.") + raise Error( + ValueError( + message="Decimal point can only appear once.", + ) + ) else: decimal_point_read = True mantissa_sign_read = True @@ -667,9 +729,21 @@ struct Decimal128( elif code == 101 or code == 69: unexpected_end_char = True if exponent_notation_read: - raise Error("Exponential notation can only appear once.") + raise Error( + ValueError( + message=( + "Exponential notation can only appear once." + ), + ) + ) if not mantissa_start: - raise Error("Exponential notation must follow a number.") + raise Error( + ValueError( + message=( + "Exponential notation must follow a number." + ), + ) + ) else: exponent_notation_read = True # If the char is a digit 0 @@ -711,8 +785,10 @@ struct Decimal128( raw_exponent > Decimal128.MAX_NUM_DIGITS * 2 ): raise Error( - String("Exponent part is too large: {}").format( - raw_exponent + OverflowError( + message=String( + "Exponent part is too large: {}" + ).format(raw_exponent), ) ) @@ -745,13 +821,19 @@ struct Decimal128( else: raise Error( - String("Invalid character in decimal128 string: {}").format( - chr(Int(code)) + ValueError( + message=String( + "Invalid character in decimal128 string: {}" + ).format(chr(Int(code))), ) ) if unexpected_end_char: - raise Error("Unexpected end character in decimal128 string.") + raise Error( + ValueError( + message="Unexpected end character in decimal128 string.", + ) + ) # print("DEBUG: coef = ", coef) # print("DEBUG: scale = ", scale) @@ -873,10 +955,13 @@ struct Decimal128( # Early exit if the value is too large if UInt128(abs_value) > Decimal128.MAX_AS_UINT128: raise Error( - String( - "Error in `from_float`: The float value {} is too" - " large (>=2^96) to be transformed into Decimal128" - ).format(value) + OverflowError( + message=String( + "The float value {} is too large (>=2^96) to be" + " transformed into Decimal128." + ).format(value), + function="Decimal128.from_float()", + ) ) # Extract binary exponent using IEEE 754 bit manipulation @@ -891,7 +976,12 @@ struct Decimal128( # CASE: Infinity or NaN if biased_exponent == 0x7FF: - raise Error("Cannot convert infinity or NaN to Decimal128") + raise Error( + ValueError( + message="Cannot convert infinity or NaN to Decimal128.", + function="Decimal128.from_float()", + ) + ) # Get unbias exponent var binary_exp: Int = biased_exponent - 1023 @@ -1072,7 +1162,13 @@ struct Decimal128( try: return Int(self.to_int64()) except e: - raise Error("Error in `to_int()`: ", e) + raise Error( + ConversionError( + message="Cannot convert Decimal128 to Int.", + function="Decimal128.to_int()", + previous_error=e^, + ) + ) def to_int64(self) raises -> Int64: """Returns the integral part of the Decimal128 as Int64. @@ -1087,10 +1183,20 @@ struct Decimal128( var result = self.to_int128() if result > Int128(Int64.MAX): - raise Error("Decimal128 is too large to fit in Int64") + raise Error( + OverflowError( + message="Decimal128 is too large to fit in Int64.", + function="Decimal128.to_int64()", + ) + ) if result < Int128(Int64.MIN): - raise Error("Decimal128 is too small to fit in Int64") + raise Error( + OverflowError( + message="Decimal128 is too small to fit in Int64.", + function="Decimal128.to_int64()", + ) + ) return Int64(result & 0xFFFF_FFFF_FFFF_FFFF) @@ -1946,7 +2052,12 @@ struct Decimal128( """ if precision_diff < 0: raise Error( - "Error in `scale_up()`: precision_diff must be greater than 0" + ValueError( + message=( + "precision_diff must be greater than or equal to 0." + ), + function="Decimal128.extend_precision()", + ) ) if precision_diff == 0: diff --git a/src/decimo/decimal128/exponential.mojo b/src/decimo/decimal128/exponential.mojo index 5a81cab..98bdb6e 100644 --- a/src/decimo/decimal128/exponential.mojo +++ b/src/decimo/decimal128/exponential.mojo @@ -20,6 +20,7 @@ import std.math from std import testing from std import time +from decimo.errors import DecimoError, ValueError, OverflowError import decimo.decimal128.constants import decimo.decimal128.special import decimo.decimal128.utility @@ -55,13 +56,24 @@ def power(base: Decimal128, exponent: Decimal128) raises -> Decimal128: try: return power(base, Int(exponent)) except e: - raise Error("Error in `power()` with Decimal128 exponent: ", e) + raise Error( + DecimoError( + message="Failed to compute power with Decimal128 exponent.", + function="power()", + previous_error=e^, + ) + ) # CASE: For negative bases, only integer exponents are supported if base.is_negative(): raise Error( - "Negative base with non-integer exponent results in a complex" - " number" + ValueError( + message=( + "Negative base with non-integer exponent results in a" + " complex number." + ), + function="power()", + ) ) # CASE: If the exponent is simple fractions @@ -70,13 +82,23 @@ def power(base: Decimal128, exponent: Decimal128) raises -> Decimal128: try: return sqrt(base) except e: - raise Error("Error in `power()` with Decimal128 exponent: ", e) + raise Error( + DecimoError( + function="power()", + previous_error=e^, + ) + ) # -0.5 if exponent == Decimal128(5, 0, 0, 0x80010000): try: return Decimal128.ONE() / sqrt(base) except e: - raise Error("Error in `power()` with Decimal128 exponent: ", e) + raise Error( + DecimoError( + function="power()", + previous_error=e^, + ) + ) # GENERAL CASE # Use the identity x^y = e^(y * ln(x)) @@ -85,7 +107,12 @@ def power(base: Decimal128, exponent: Decimal128) raises -> Decimal128: var product = exponent * ln_base return exp(product) except e: - raise Error("Error in `power()` with Decimal128 exponent: ", e) + raise Error( + DecimoError( + function="power()", + previous_error=e^, + ) + ) def power(base: Decimal128, exponent: Int) raises -> Decimal128: @@ -114,7 +141,12 @@ def power(base: Decimal128, exponent: Int) raises -> Decimal128: return Decimal128.ZERO() else: # 0^n is undefined for n < 0 - raise Error("Zero cannot be raised to a negative power") + raise Error( + ValueError( + message="Zero cannot be raised to a negative power.", + function="power()", + ) + ) if base.coefficient() == 1 and base.scale() == 0: # 1^n = 1 for any n @@ -165,7 +197,12 @@ def root(x: Decimal128, n: Int) raises -> Decimal128: # Special cases for n if n <= 0: - raise Error("Error in `root()`: Cannot compute non-positive root") + raise Error( + ValueError( + message="Cannot compute non-positive root.", + function="root()", + ) + ) if n == 1: return x if n == 2: @@ -179,8 +216,10 @@ def root(x: Decimal128, n: Int) raises -> Decimal128: if x.is_negative(): if n % 2 == 0: raise Error( - "Error in `root()`: Cannot compute even root of a negative" - " number" + ValueError( + message="Cannot compute even root of a negative number.", + function="root()", + ) ) # For odd roots of negative numbers, compute |x|^(1/n) and negate return -root(-x, n) @@ -193,7 +232,13 @@ def root(x: Decimal128, n: Int) raises -> Decimal128: # Direct calculation: x^n = e^(ln(x)/n) return exp(ln(x) / Decimal128(n)) except e: - raise Error("Error in `root()`: ", e) + raise Error( + DecimoError( + message="Root computation failed.", + function="root()", + previous_error=e^, + ) + ) # Initial guess # use floating point approach to quickly find a good guess @@ -335,7 +380,10 @@ def sqrt(x: Decimal128) raises -> Decimal128: # Special cases if x.is_negative(): raise Error( - "Error in sqrt: Cannot compute square root of a negative number" + ValueError( + message="Cannot compute square root of a negative number.", + function="sqrt()", + ) ) if x.is_zero(): @@ -464,8 +512,13 @@ def exp(x: Decimal128) raises -> Decimal128: if x > Decimal128.from_int(value=6654, scale=UInt32(2)): raise Error( - "decimal.exponential.exp(): x is too large. It must be no greater" - " than 66.54 to avoid overflow. Consider using `BigDecimal` type." + OverflowError( + message=( + "x is too large (must be <= 66.54). Consider using" + " BigDecimal type." + ), + function="exp()", + ) ) # Handle special cases @@ -673,7 +726,10 @@ def ln(x: Decimal128) raises -> Decimal128: # Handle special cases if x.is_negative() or x.is_zero(): raise Error( - "Error in ln(): Cannot compute logarithm of a non-positive number" + ValueError( + message="Cannot compute logarithm of a non-positive number.", + function="ln()", + ) ) if x.is_one(): @@ -936,18 +992,29 @@ def log(x: Decimal128, base: Decimal128) raises -> Decimal128: # Special cases: x <= 0 if x.is_negative() or x.is_zero(): raise Error( - "Error in log(): Cannot compute logarithm of a non-positive number" + ValueError( + message="Cannot compute logarithm of a non-positive number.", + function="log()", + ) ) # Special cases: base <= 0 if base.is_negative() or base.is_zero(): raise Error( - "Error in log(): Cannot use non-positive base for logarithm" + ValueError( + message="Cannot use non-positive base for logarithm.", + function="log()", + ) ) # Special case: base = 1 if base.is_one(): - raise Error("Error in log(): Cannot use base 1 for logarithm") + raise Error( + ValueError( + message="Cannot use base 1 for logarithm.", + function="log()", + ) + ) # Special case: x = 1 # log_base(1) = 0 for any valid base @@ -988,8 +1055,10 @@ def log10(x: Decimal128) raises -> Decimal128: # Special cases: x <= 0 if x.is_negative() or x.is_zero(): raise Error( - "Error in log10(): Cannot compute logarithm of a non-positive" - " number" + ValueError( + message="Cannot compute logarithm of a non-positive number.", + function="log10()", + ) ) var x_scale = x.scale() diff --git a/src/decimo/decimal128/rounding.mojo b/src/decimo/decimal128/rounding.mojo index 25a8e8b..4ed4cba 100644 --- a/src/decimo/decimal128/rounding.mojo +++ b/src/decimo/decimal128/rounding.mojo @@ -33,6 +33,7 @@ from std import testing from decimo.decimal128.decimal128 import Decimal128 from decimo.rounding_mode import RoundingMode +from decimo.errors import OverflowError import decimo.decimal128.utility # ===------------------------------------------------------------------------===# @@ -95,14 +96,16 @@ def round( # If the digits of result > 29, directly raise an error if ndigits_of_x + scale_diff > Decimal128.MAX_NUM_DIGITS: raise Error( - String( - "Error in `round()`: `ndigits = {}` causes the number of" - " digits in the significant figures of the result (={})" - " exceeds the maximum capacity (={})." - ).format( - ndigits, - ndigits_of_x + scale_diff, - Decimal128.MAX_NUM_DIGITS, + OverflowError( + message=String( + "ndigits={} causes the number of significant figures" + " ({}) to exceed the maximum capacity ({})." + ).format( + ndigits, + ndigits_of_x + scale_diff, + Decimal128.MAX_NUM_DIGITS, + ), + function="round()", ) ) @@ -115,11 +118,13 @@ def round( res_coef > Decimal128.MAX_AS_UINT128 ): raise Error( - String( - "Error in `round()`: `ndigits = {}` causes the" - " significant digits of the result (={}) exceeds the" - " maximum capacity (={})." - ).format(ndigits, res_coef, Decimal128.MAX_AS_UINT128) + OverflowError( + message=String( + "ndigits={} causes the significant digits ({})" + " to exceed the maximum capacity ({})." + ).format(ndigits, res_coef, Decimal128.MAX_AS_UINT128), + function="round()", + ) ) # In other cases, return the result diff --git a/src/decimo/decimal128/special.mojo b/src/decimo/decimal128/special.mojo index 7b14c58..8a7c667 100644 --- a/src/decimo/decimal128/special.mojo +++ b/src/decimo/decimal128/special.mojo @@ -20,6 +20,8 @@ """Implements functions for special operations on Decimal128 objects.""" +from decimo.errors import ValueError, OverflowError + def factorial(n: Int) raises -> Decimal128: """Calculates the factorial of a non-negative integer. @@ -37,11 +39,21 @@ def factorial(n: Int) raises -> Decimal128: """ if n < 0: - raise Error("Factorial is not defined for negative numbers") + raise Error( + ValueError( + message="Factorial is not defined for negative numbers.", + function="factorial()", + ) + ) if n > 27: raise Error( - String("{}! is too large to be represented by Decimal128").format(n) + OverflowError( + message=String( + "{}! is too large to be represented by Decimal128." + ).format(n), + function="factorial()", + ) ) # Directly return the factorial for n = 0 to 27 @@ -166,7 +178,14 @@ def factorial_reciprocal(n: Int) raises -> Decimal128: # 1/27! = 0.0000000000000000000000000001, Decimal128.from_words(0x1, 0x0, 0x0, 0x1c0000) if n < 0: - raise Error("Factorial reciprocal is not defined for negative numbers") + raise Error( + ValueError( + message=( + "Factorial reciprocal is not defined for negative numbers." + ), + function="factorial_reciprocal()", + ) + ) # For n > 27, 1/n! is essentially 0 at Decimal128 precision # Return 0 with max scale diff --git a/src/decimo/errors.mojo b/src/decimo/errors.mojo index 786c7cd..083d2f7 100644 --- a/src/decimo/errors.mojo +++ b/src/decimo/errors.mojo @@ -16,131 +16,147 @@ """ Implements error handling for Decimo. -""" - -from std.pathlib.path import cwd -import decimo.str -comptime OverflowError = DecimoError[error_type="OverflowError"] -"""Type for overflow errors in Decimo. +The error messages follow the Python traceback format as closely as possible: -Fields: +``` +Traceback (most recent call last): + File "/path/to/file.mojo", line 42, in my_function +ValueError: description of what went wrong +``` -file: The file where the error occurred.\\ -function: The function where the error occurred.\\ -message: An optional message describing the error.\\ -previous_error: An optional previous error that caused this error. +File name and line number are automatically captured at the raise site using +`call_location()`. Function name is an optional argument -- Mojo does not have +a built-in way to get the current function name at runtime. """ -comptime IndexError = DecimoError[error_type="IndexError"] -"""Type for index errors in Decimo. +from std.reflection import call_location -Fields: -file: The file where the error occurred.\\ -function: The function where the error occurred.\\ -message: An optional message describing the error.\\ -previous_error: An optional previous error that caused this error. -""" +# ===--- ANSI Color Codes ---=== # +# Mimics Python/Rich traceback coloring style. -comptime KeyError = DecimoError[error_type="KeyError"] -"""Type for key errors in Decimo. +comptime _RESET = "\033[0m" +comptime _BOLD = "\033[1m" +comptime _DIM = "\033[2m" -Fields: +comptime _RED = "\033[31m" +comptime _GREEN = "\033[32m" +comptime _YELLOW = "\033[33m" +comptime _BLUE = "\033[34m" +comptime _MAGENTA = "\033[35m" +comptime _CYAN = "\033[36m" +comptime _WHITE = "\033[37m" -file: The file where the error occurred.\\ -function: The function where the error occurred.\\ -message: An optional message describing the error.\\ -previous_error: An optional previous error that caused this error. -""" +# Semantic aliases for error formatting. +comptime _CLR_ERROR_TYPE = _BOLD + _RED # Error type name (e.g., ValueError) +comptime _CLR_TRACEBACK = _BOLD # "Traceback (most recent call last):" +comptime _CLR_FILE_PATH = _MAGENTA # File path +comptime _CLR_LINE_NUM = _GREEN # Line number +comptime _CLR_FUNC_NAME = _YELLOW # Function name +comptime _CLR_MSG_TEXT = _BOLD # Error message text +comptime _CLR_CHAIN_MSG = _DIM # Chained error separator message -comptime ValueError = DecimoError[error_type="ValueError"] -"""Type for value errors in Decimo. +comptime OverflowError = DecimoError[error_type="OverflowError"] +"""Type for overflow errors in Decimo.""" -Fields: +comptime IndexError = DecimoError[error_type="IndexError"] +"""Type for index errors in Decimo.""" -file: The file where the error occurred.\\ -function: The function where the error occurred.\\ -message: An optional message describing the error.\\ -previous_error: An optional previous error that caused this error. -""" +comptime KeyError = DecimoError[error_type="KeyError"] +"""Type for key errors in Decimo.""" +comptime ValueError = DecimoError[error_type="ValueError"] +"""Type for value errors in Decimo.""" comptime ZeroDivisionError = DecimoError[error_type="ZeroDivisionError"] - -"""Type for divided-by-zero errors in Decimo. - -Fields: - -file: The file where the error occurred.\\ -function: The function where the error occurred.\\ -message: An optional message describing the error.\\ -previous_error: An optional previous error that caused this error. -""" +"""Type for divided-by-zero errors in Decimo.""" comptime ConversionError = DecimoError[error_type="ConversionError"] +"""Type for conversion errors in Decimo.""" -"""Type for conversion errors in Decimo. -Fields: +struct DecimoError[error_type: String = "DecimoError"](Writable): + """Base type for all Decimo errors. -file: The file where the error occurred.\\ -function: The function where the error occurred.\\ -message: An optional message describing the error.\\ -previous_error: An optional previous error that caused this error. -""" + The error message format mimics Python's traceback: + ``` + Traceback (most recent call last): + File "/path/to/file.mojo", line 42, in my_function + ValueError: description of what went wrong + ``` -struct DecimoError[error_type: String = "DecimoError"](Writable): - """Base type for all Decimo errors. + File name and line number are automatically captured at the raise site. + Function name is an optional argument since Mojo does not yet support + runtime introspection of the current function name. Parameters: error_type: The type of the error, e.g., "OverflowError", "IndexError". - - Fields: - - file: The file where the error occurred.\\ - function: The function where the error occurred.\\ - message: An optional message describing the error.\\ - previous_error: An optional previous error that caused this error. """ var file: String - """The source file where the error occurred.""" - var function: String - """The function name where the error occurred.""" + """The source file where the error occurred (auto-captured).""" + var line: Int + """The line number where the error occurred (auto-captured).""" + var function: Optional[String] + """An optional function name where the error occurred.""" var message: Optional[String] """An optional message describing the error.""" var previous_error: Optional[String] """An optional formatted string of a previous error that caused this one.""" + @always_inline def __init__( out self, - file: String, - function: String, - message: Optional[String], - previous_error: Optional[Error], + *, + message: Optional[String] = None, + function: Optional[String] = None, + previous_error: Optional[Error] = None, ): - """Creates a new `DecimoError` with the given context. + """Creates a new `DecimoError` with auto-captured file and line. + + File name and line number are automatically captured from the call site. Args: - file: The source file where the error occurred. - function: The function name where the error occurred. message: An optional message describing the error. + function: An optional function name where the error occurred. previous_error: An optional previous error that caused this one. """ - self.file = file + var loc = call_location() + self.file = String(loc.file_name) + self.line = loc.line self.function = function self.message = message if previous_error is None: self.previous_error = None else: - self.previous_error = "\n".join( - String(previous_error.value()).split("\n")[3:] - ) + self.previous_error = String(previous_error.value()) def write_to[W: Writer](self, mut writer: W): - """Writes a formatted error traceback to a writer. + """Writes a Python-style formatted error traceback to a writer. + + Output format (colored with ANSI codes): + + ``` + Traceback (most recent call last): + File "/path/to/file.mojo", line 42, in my_function + ValueError: description of what went wrong + ``` + + When a previous error is chained: + + ``` + Traceback (most recent call last): + File "/path/to/inner.mojo", line 10 + ValueError: inner error message + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + File "/path/to/outer.mojo", line 20, in outer_function + DecimoError: outer error message + ``` Parameters: W: A type conforming to the `Writer` interface. @@ -148,30 +164,49 @@ struct DecimoError[error_type: String = "DecimoError"](Writable): Args: writer: The writer instance. """ + # Chained previous error (printed FIRST, like Python) + if self.previous_error is not None: + writer.write(self.previous_error.value()) + writer.write("\n") + writer.write(_CLR_CHAIN_MSG) + writer.write( + "The above exception was the direct cause of the following" + " exception:" + ) + writer.write(_RESET) + writer.write("\n") + + # "Traceback (most recent call last):" + writer.write(_CLR_TRACEBACK) + writer.write("Traceback (most recent call last):") + writer.write(_RESET) writer.write("\n") - writer.write(("-" * 80)) - writer.write("\n") - writer.write(decimo.str.ljust(String(Self.error_type), 47, " ")) - writer.write("Traceback (most recent call last)\n") - writer.write('File "') - try: - writer.write(String(cwd())) - except e: - pass - finally: - writer.write("/") + + # ' File "/path/to/file.mojo", line 42, in function_name' + writer.write(" File ") + writer.write('"') + writer.write(_CLR_FILE_PATH) writer.write(self.file) - writer.write('"\n') - writer.write("----> ") - writer.write(self.function) - if self.message is None: - writer.write("\n") - else: - writer.write("\n\n") - writer.write(Self.error_type) + writer.write(_RESET) + writer.write('"') + writer.write(", line ") + writer.write(_CLR_LINE_NUM) + writer.write(String(self.line)) + writer.write(_RESET) + if self.function is not None: + writer.write(", in ") + writer.write(_CLR_FUNC_NAME) + writer.write(self.function.value()) + writer.write(_RESET) + writer.write("\n") + + # "ValueError: description of what went wrong" + writer.write(_CLR_ERROR_TYPE) + writer.write(Self.error_type) + writer.write(_RESET) + if self.message is not None: writer.write(": ") + writer.write(_CLR_MSG_TEXT) writer.write(self.message.value()) - writer.write("\n") - if self.previous_error is not None: - writer.write("\n") - writer.write(self.previous_error.value()) + writer.write(_RESET) + writer.write("\n") diff --git a/src/decimo/str.mojo b/src/decimo/str.mojo index 8da1850..a399794 100644 --- a/src/decimo/str.mojo +++ b/src/decimo/str.mojo @@ -18,6 +18,8 @@ from std.algorithm import vectorize +from decimo.errors import ValueError + def rjust(s: String, width: Int, fillchar: String = " ") -> String: """Right-justifies a string by padding with a fill character on the left. @@ -124,7 +126,12 @@ def parse_numeric_string( var n = len(value_bytes) if n == 0: - raise Error("Error in `parse_numeric_string`: Empty string.") + raise Error( + ValueError( + message="Empty string.", + function="parse_numeric_string()", + ) + ) var ptr = value_bytes.unsafe_ptr() @@ -188,9 +195,19 @@ def parse_numeric_string( elif c == 46: last_was_separator = False if in_exponent: - raise Error("Decimal point cannot appear in the exponent part.") + raise Error( + ValueError( + message=( + "Decimal point cannot appear in the exponent part." + ), + ) + ) if decimal_point_pos != -1: - raise Error("Decimal point can only appear once.") + raise Error( + ValueError( + message="Decimal point can only appear once.", + ) + ) decimal_point_pos = i sign_read = True @@ -198,9 +215,17 @@ def parse_numeric_string( elif c == 101 or c == 69: last_was_separator = True if in_exponent: - raise Error("Exponential notation can only appear once.") + raise Error( + ValueError( + message="Exponential notation can only appear once.", + ) + ) if total_mantissa_digits == 0: - raise Error("Exponential notation must follow a number.") + raise Error( + ValueError( + message="Exponential notation must follow a number.", + ) + ) exponent_pos = i in_exponent = True @@ -210,14 +235,23 @@ def parse_numeric_string( if in_exponent: if exponent_sign_read: raise Error( - "Exponent sign can only appear once," - " before exponent digits." + ValueError( + message=( + "Exponent sign can only appear once," + " before exponent digits." + ), + ) ) exponent_sign_read = True else: if sign_read: raise Error( - "Minus sign can only appear once at the beginning." + ValueError( + message=( + "Minus sign can only appear once at the" + " beginning." + ), + ) ) sign = True sign_read = True @@ -228,29 +262,48 @@ def parse_numeric_string( if in_exponent: if exponent_sign_read: raise Error( - "Exponent sign can only appear once," - " before exponent digits." + ValueError( + message=( + "Exponent sign can only appear once," + " before exponent digits." + ), + ) ) exponent_sign_read = True else: if sign_read: raise Error( - "Plus sign can only appear once at the beginning." + ValueError( + message=( + "Plus sign can only appear once at the" + " beginning." + ), + ) ) sign_read = True else: raise Error( - String( - "Invalid character in the string of the number: {}" - ).format(chr(Int(c))) + ValueError( + message=String( + "Invalid character in the string of the number: {}" + ).format(chr(Int(c))), + ) ) if last_was_separator: - raise Error("Unexpected end character in the string of the number.") + raise Error( + ValueError( + message="Unexpected end character in the string of the number.", + ) + ) if total_mantissa_digits == 0: - raise Error("No digits found in the string of the number.") + raise Error( + ValueError( + message="No digits found in the string of the number.", + ) + ) # ================================================================== # Parse exponent value (separate from pass 1 to keep the main loop diff --git a/src/decimo/tests.mojo b/src/decimo/tests.mojo index 061faa4..3258e5f 100644 --- a/src/decimo/tests.mojo +++ b/src/decimo/tests.mojo @@ -52,6 +52,7 @@ Pattern expansion in string values: from .toml import parse_file as parse_toml_file from .toml.parser import TOMLDocument +from .errors import DecimoError from std.python import Python, PythonObject from std.collections import List from std import os @@ -303,10 +304,11 @@ def parse_file(file_path: String) raises -> TOMLDocument: return parse_toml_file(file_path) except e: raise Error( - "tests.parse_file(): Failed to parse TOML file:", - file_path, - "\nTraceback:", - e, + DecimoError( + message="Failed to parse TOML file: " + file_path, + function="parse_file()", + previous_error=e^, + ) ) diff --git a/src/decimo/toml/parser.mojo b/src/decimo/toml/parser.mojo index f85443f..785b79b 100644 --- a/src/decimo/toml/parser.mojo +++ b/src/decimo/toml/parser.mojo @@ -30,6 +30,7 @@ Supports: """ from std.collections import Dict +from decimo.errors import ValueError from .tokenizer import Token, TokenType, Tokenizer @@ -471,11 +472,21 @@ def _set_value( var existing = root[key].table_values.copy() for entry in value.table_values.items(): if entry.key in existing: - raise Error("Duplicate key: " + key + "." + entry.key) + raise Error( + ValueError( + message="Duplicate key: " + + key + + "." + + entry.key, + function="parse()", + ) + ) existing[entry.key] = entry.value.copy() root[key] = _make_table(existing^) return - raise Error("Duplicate key: " + key) + raise Error( + ValueError(message="Duplicate key: " + key, function="parse()") + ) root[key] = value^ return @@ -496,7 +507,12 @@ def _set_value( arr[len(arr) - 1] = _make_table(last_tbl^) root[first].array_values = arr^ return - raise Error("Key exists but is not a table: " + first) + raise Error( + ValueError( + message="Key exists but is not a table: " + first, + function="parse()", + ) + ) var table = root[first].table_values.copy() var remaining = List[String]() @@ -533,7 +549,12 @@ def _ensure_table_path( root[first].array_values = arr^ return elif root[first].type != TOMLValueType.TABLE: - raise Error("Key exists but is not a table: " + first) + raise Error( + ValueError( + message="Key exists but is not a table: " + first, + function="parse()", + ) + ) if len(path) > 1: var table = root[first].table_values.copy() @@ -553,7 +574,12 @@ def _append_array_of_tables( ) raises: """Append a new empty table to the array-of-tables at `path`.""" if len(path) == 0: - raise Error("Array of tables path cannot be empty") + raise Error( + ValueError( + message="Array of tables path cannot be empty", + function="parse()", + ) + ) if len(path) == 1: var key = path[0] @@ -569,7 +595,12 @@ def _append_array_of_tables( _make_table(Dict[String, TOMLValue]()) ) else: - raise Error("Cannot redefine as array of tables: " + key) + raise Error( + ValueError( + message="Cannot redefine as array of tables: " + key, + function="parse()", + ) + ) return # Multi-part path: navigate to the parent, then handle the last key @@ -596,7 +627,12 @@ def _append_array_of_tables( arr[len(arr) - 1] = _make_table(last_tbl^) root[first].array_values = arr^ else: - raise Error("Key exists but is not a table or array: " + first) + raise Error( + ValueError( + message="Key exists but is not a table or array: " + first, + function="parse()", + ) + ) struct TOMLParser: @@ -669,7 +705,7 @@ struct TOMLParser: var parts = List[String]() if not self._is_key_token(): - raise Error("Expected key") + raise Error(ValueError(message="Expected key", function="parse()")) parts.append(self._tok().value) self._advance() @@ -678,7 +714,11 @@ struct TOMLParser: while self._tok().type == TokenType.DOT: self._advance() # skip dot if not self._is_key_token(): - raise Error("Expected key after dot") + raise Error( + ValueError( + message="Expected key after dot", function="parse()" + ) + ) parts.append(self._tok().value) self._advance() @@ -816,7 +856,12 @@ struct TOMLParser: # Expect equals if self._tok().type != TokenType.EQUAL: - raise Error("Expected '=' in inline table") + raise Error( + ValueError( + message="Expected '=' in inline table", + function="parse()", + ) + ) self._advance() # Parse value @@ -844,7 +889,12 @@ struct TOMLParser: self._advance() break else: - raise Error("Expected ',' or '}' in inline table") + raise Error( + ValueError( + message="Expected ',' or '}' in inline table", + function="parse()", + ) + ) return _make_table(table^) @@ -858,7 +908,12 @@ struct TOMLParser: if self._tok().type == TokenType.ARRAY_END: self._advance() else: - raise Error("Expected ']' after table header") + raise Error( + ValueError( + message="Expected ']' after table header", + function="parse()", + ) + ) return path^ @@ -870,11 +925,21 @@ struct TOMLParser: if self._tok().type == TokenType.ARRAY_END: self._advance() else: - raise Error("Expected ']]' after array of tables header") + raise Error( + ValueError( + message="Expected ']]' after array of tables header", + function="parse()", + ) + ) if self._tok().type == TokenType.ARRAY_END: self._advance() else: - raise Error("Expected ']]' after array of tables header") + raise Error( + ValueError( + message="Expected ']]' after array of tables header", + function="parse()", + ) + ) return path^