diff --git a/README.md b/README.md index 5551ef9..2b60397 100644 --- a/README.md +++ b/README.md @@ -53,24 +53,82 @@ For the latest development version, clone the [GitHub repository](https://github ## Quick start +You can start using DeciMojo by importing the `decimojo` module. An easy way to do this is to import everything from the `prelude` module, which provides the most commonly used types. + +```mojo +from decimojo import * +``` + +This will import the following types or aliases into your namespace: + +- `dm`: An alias for the `decimojo` module. +- `BigInt` (alias `BInt`): An arbitrary-precision signed integer type. +- `BigDecimal` (alias `BDec`): An arbitrary-precision decimal type. +- `Decimal` (alias `Dec`): A 128-bit fixed-precision decimal type. +- `RoundingMode` (alias `RM`): An enumeration for rounding modes. +- `ROUND_DOWN`, `ROUND_HALF_UP`, `ROUND_HALF_EVEN`, `ROUND_UP`: Constants for common rounding modes. + +--- + Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` type. Note that Mojo does not support global variables at the moment, so we need to pass the `precision` parameter explicitly to each function call. In future, we will add a global precision setting with the default value of, *e.g.*, `28`, to avoid passing it around. ```mojo -from decimojo import BDec, RM +from decimojo.prelude import * fn main() raises: - var PRECISION = 100 var a = BDec("123456789.123456789") var b = BDec("1234.56789") - print(a.sqrt(precision=PRECISION)) - # 11111.11106611111096943055498174930232833813065468909453818857935956641682120364106016272519460988485 - print(a.power(b, precision=PRECISION)) - # 3.346361102419080234023813540078946868219632448203078657310495672766009862564151996325555496759911131748170844123475135377098326591508239654961E+9989 - print(a.log(b, precision=PRECISION)) - # 2.617330026656548299907884356415293977170848626010103229392408225981962436022623783231699264341492663671325580092077394824180414301026578169909 + + # === Basic Arithmetic === # + print(a + b) # 123458023.691346789 + print(a - b) # 123455554.555566789 + print(a * b) # 152415787654.32099750190521 + print(a.true_divide(b, precision=80)) # 100000.0001 + + # === Mathematical Functions === # + print(a.sqrt(precision=80)) + # 11111.111066111110969430554981749302328338130654689094538188579359566416821203641 + print(a.cbrt(precision=80)) + # 497.93385938415242742001134219007635925452951248903093962731782327785111102410518 + print(a.root(b, precision=80)) + # 1.0152058862996527138602610522640944903320735973237537866713119992581006582644107 + print(a.power(b, precision=80)) + # 3.3463611024190802340238135400789468682196324482030786573104956727660098625641520E+9989 + print(a.exp(precision=80)) + # 1.8612755889649587035842377856492201091251654136588338983610243887893287518637652E+53616602 + print(a.log(b, precision=80)) + # 2.6173300266565482999078843564152939771708486260101032293924082259819624360226238 + print(a.ln(precision=80)) + # 18.631401767168018032693933348296537542797015174553735308351756611901741276655161 + + # === Internal representation of the number === # + ( + BDec( + "3.141592653589793238462643383279502884197169399375105820974944" + ).power(2, precision=60) + ).print_internal_representation() + # Internal Representation Details of BigDecimal + # ---------------------------------------------- + # number: 9.8696044010893586188344909998 + # 761511353136994072407906264133 + # 5 + # coefficient: 986960440108935861883449099987 + # 615113531369940724079062641335 + # negative: False + # scale: 59 + # word 0: 62641335 + # word 1: 940724079 + # word 2: 113531369 + # word 3: 99987615 + # word 4: 861883449 + # word 5: 440108935 + # word 6: 986960 + # ---------------------------------------------- ``` +--- + Here is a comprehensive quick-start guide showcasing each major function of the `BigInt` type. ```mojo @@ -114,6 +172,8 @@ fn main() raises: print(BInt("123456789" * 400) // BInt("987654321" * 200)) ``` +--- + Here is a comprehensive quick-start guide showcasing each major function of the `Decimal` type. ```mojo diff --git a/docs/examples_on_bdec.mojo b/docs/examples_on_bdec.mojo new file mode 100644 index 0000000..e1b4a06 --- /dev/null +++ b/docs/examples_on_bdec.mojo @@ -0,0 +1,52 @@ +from decimojo.prelude import * + + +fn main() raises: + var a = BDec("123456789.123456789") + var b = BDec("1234.56789") + + # === Basic Arithmetic === # + print(a + b) # 123458023.691346789 + print(a - b) # 123455554.555566789 + print(a * b) # 152415787654.32099750190521 + print(a.true_divide(b, precision=80)) # 100000.0001 + + # === Mathematical Functions === # + print(a.sqrt(precision=80)) + # 11111.111066111110969430554981749302328338130654689094538188579359566416821203641 + print(a.cbrt(precision=80)) + # 497.93385938415242742001134219007635925452951248903093962731782327785111102410518 + print(a.root(b, precision=80)) + # 1.0152058862996527138602610522640944903320735973237537866713119992581006582644107 + print(a.power(b, precision=80)) + # 3.3463611024190802340238135400789468682196324482030786573104956727660098625641520E+9989 + print(a.exp(precision=80)) + # 1.8612755889649587035842377856492201091251654136588338983610243887893287518637652E+53616602 + print(a.log(b, precision=80)) + # 2.6173300266565482999078843564152939771708486260101032293924082259819624360226238 + print(a.ln(precision=80)) + # 18.631401767168018032693933348296537542797015174553735308351756611901741276655161 + + # === Internal representation of the number === # + ( + BDec( + "3.141592653589793238462643383279502884197169399375105820974944" + ).power(2, precision=60) + ).print_internal_representation() + # Internal Representation Details of BigDecimal + # ---------------------------------------------- + # number: 9.8696044010893586188344909998 + # 761511353136994072407906264133 + # 5 + # coefficient: 986960440108935861883449099987 + # 615113531369940724079062641335 + # negative: False + # scale: 59 + # word 0: 62641335 + # word 1: 940724079 + # word 2: 113531369 + # word 3: 99987615 + # word 4: 861883449 + # word 5: 440108935 + # word 6: 986960 + # ---------------------------------------------- diff --git a/src/decimojo/bigdecimal/bigdecimal.mojo b/src/decimojo/bigdecimal/bigdecimal.mojo index 9c7d744..74885e2 100644 --- a/src/decimojo/bigdecimal/bigdecimal.mojo +++ b/src/decimojo/bigdecimal/bigdecimal.mojo @@ -154,17 +154,36 @@ struct BigDecimal( @staticmethod fn from_raw_components( - coefficient: BigUInt, scale: Int = 0, sign: Bool = False + owned words: List[UInt32], scale: Int = 0, sign: Bool = False ) -> Self: - """Creates a BigDecimal from its raw components.""" - return Self(coefficient, scale, sign) + """**UNSAFE** Creates a BigDecimal from its raw components. + The raw components are words, scale, and sign. + + Args: + words: The raw words of the coefficient. + scale: The scale of the BigDecimal. + sign: The sign of the BigDecimal. + + Returns: + A BigDecimal object constructed from the raw components. + + Notes: + + This method is unsafe because it does not check the validity of the + words. It is the caller's responsibility to ensure that the words + represent a valid BigUInt. + """ + var coefficient = BigUInt(words=words^) + return Self(coefficient^, scale, sign) @staticmethod fn from_raw_components( - coefficient: UInt32, scale: Int = 0, sign: Bool = False + word: UInt32, scale: Int = 0, sign: Bool = False ) -> Self: - """Creates a BigDecimal from its raw components.""" - return Self(BigUInt(List[UInt32](coefficient)), scale, sign) + """**UNSAFE** Creates a BigDecimal from its raw components. + The raw components are a single word, scale, and sign. + """ + return Self(BigUInt(words=List[UInt32](word)), scale, sign) @staticmethod fn from_int(value: Int) -> Self: @@ -644,6 +663,8 @@ struct BigDecimal( # Mathematical methods that do not implement a trait (not a dunder) # ===------------------------------------------------------------------=== # + # === Comparisons === # + @always_inline fn compare(self, other: Self) raises -> Int8: """Compares two BigDecimal numbers. @@ -658,6 +679,36 @@ struct BigDecimal( """ return decimojo.bigdecimal.comparison.compare_absolute(self, other) + # === Extrema === # + + @always_inline + fn max(self, other: Self) raises -> Self: + """Returns the maximum of two BigDecimal numbers.""" + return decimojo.bigdecimal.comparison.max(self, other) + + @always_inline + fn min(self, other: Self) raises -> Self: + """Returns the minimum of two BigDecimal numbers.""" + return decimojo.bigdecimal.comparison.min(self, other) + + # === Constants === # + + @always_inline + @staticmethod + fn pi(precision: Int) raises -> Self: + """Returns the mathematical constant pi to the specified precision.""" + return decimojo.bigdecimal.constants.pi(precision=precision) + + @always_inline + @staticmethod + fn e(precision: Int) raises -> Self: + """Returns the mathematical constant e to the specified precision.""" + return decimojo.bigdecimal.exponential.exp( + x=BigDecimal(BigUInt.ONE), precision=precision + ) + + # === Exponentional operations === # + @always_inline fn exp(self, precision: Int = 28) raises -> Self: """Returns the exponential of the BigDecimal number.""" @@ -679,16 +730,6 @@ struct BigDecimal( """Returns the base-10 logarithm of the BigDecimal number.""" return decimojo.bigdecimal.exponential.log10(self, precision) - @always_inline - fn max(self, other: Self) raises -> Self: - """Returns the maximum of two BigDecimal numbers.""" - return decimojo.bigdecimal.comparison.max(self, other) - - @always_inline - fn min(self, other: Self) raises -> Self: - """Returns the minimum of two BigDecimal numbers.""" - return decimojo.bigdecimal.comparison.min(self, other) - @always_inline fn root(self, root: Self, precision: Int = 28) raises -> Self: """Returns the root of the BigDecimal number.""" @@ -704,6 +745,21 @@ struct BigDecimal( """Returns the cube root of the BigDecimal number.""" return decimojo.bigdecimal.exponential.cbrt(self, precision) + @always_inline + fn power(self, exponent: Self, precision: Int) raises -> Self: + """Returns the result of exponentiation with the given precision. + See `exponential.power()` for more information. + """ + return decimojo.bigdecimal.exponential.power(self, exponent, precision) + + # === Trigonometric operations === # + @always_inline + fn arctan(self, precision: Int = 28) raises -> Self: + """Returns the arctangent of the BigDecimal number.""" + return decimojo.bigdecimal.trigonometric.arctan(self, precision) + + # === Arithmetic operations === # + @always_inline fn true_divide(self, other: Self, precision: Int) raises -> Self: """Returns the result of true division of two BigDecimal numbers. @@ -731,12 +787,7 @@ struct BigDecimal( """ return decimojo.bigdecimal.arithmetics.truncate_divide(self, other) - @always_inline - fn power(self, exponent: Self, precision: Int) raises -> Self: - """Returns the result of exponentiation with the given precision. - See `exponential.power()` for more information. - """ - return decimojo.bigdecimal.exponential.power(self, exponent, precision) + # === Rounding operations === # @always_inline fn round( @@ -867,7 +918,7 @@ struct BigDecimal( ) @always_inline - fn print_internal_representation(self) raises: + fn print_internal_representation(self): """Prints the internal representation of the BigDecimal.""" var line_width = 30 var string_of_number = self.to_string(line_width=line_width).split("\n") @@ -897,14 +948,41 @@ struct BigDecimal( else: ndigits = 3 print( - String("word {}:{}{}") - .format( - i, " " * (10 - ndigits), String(self.coefficient.words[i]) - ) - .rjust(9, fillchar="0") + String( + "word ", + i, + ":", + " " * (10 - ndigits), + self.coefficient.words[i], + ).rjust(9, fillchar="0") ) print("----------------------------------------------") + @always_inline + fn print_representation_as_components(self): + """Prints the representation of the BigDecimal as components.""" + print( + ( + "BigDecimal(\n coefficient=BigUInt(\n " + " words=List[UInt32](\n " + ), + end="", + ) + ref words = self.coefficient.words + for i in range(len(words)): + if i != len(words) - 1: + print(words[i], end=",\n ") + else: + print(words[i], end=",\n ),\n ),\n") + print( + " scale=", + self.scale, + ",\n sign=", + self.sign, + ")", + sep="", + ) + @always_inline fn is_integer(self) -> Bool: """Returns True if this number represents an integer value.""" diff --git a/src/decimojo/bigdecimal/constants.mojo b/src/decimojo/bigdecimal/constants.mojo index 72210f9..581e1d0 100644 --- a/src/decimojo/bigdecimal/constants.mojo +++ b/src/decimojo/bigdecimal/constants.mojo @@ -21,3 +21,180 @@ import math as builtin_math from decimojo.bigdecimal.bigdecimal import BigDecimal from decimojo.rounding_mode import RoundingMode + +alias PI_1024 = BigDecimal( + coefficient=BigUInt( + words=List[UInt32]( + 858632789, + 572010654, + 989380952, + 92164201, + 766111959, + 130019278, + 712268066, + 577805321, + 519577818, + 537875937, + 628638823, + 687311595, + 904287554, + 35982534, + 776691473, + 814206171, + 875332083, + 387528865, + 100031378, + 311881710, + 850352619, + 82533446, + 26425223, + 553469083, + 950244594, + 160963185, + 597317328, + 780499510, + 999983729, + 72113499, + 99605187, + 297747713, + 181598136, + 608640344, + 121290219, + 420199561, + 892589235, + 507922796, + 495853710, + 534301465, + 409012249, + 787214684, + 91736371, + 427577896, + 277857713, + 452635608, + 5681271, + 694051320, + 748184676, + 767523846, + 171762931, + 27705392, + 798609437, + 371907021, + 463952247, + 860213949, + 406566430, + 336733624, + 119491298, + 279381830, + 527248912, + 673518857, + 799627495, + 480744623, + 931051185, + 819326117, + 921861173, + 595919530, + 572703657, + 116094330, + 469519415, + 665213841, + 305488204, + 600113305, + 678925903, + 917153643, + 628292540, + 815209209, + 155881748, + 870066063, + 412737245, + 72602491, + 482133936, + 104543266, + 234603486, + 456485669, + 712019091, + 867831652, + 756482337, + 334461284, + 109756659, + 819644288, + 489549303, + 596446229, + 385211055, + 841027019, + 811174502, + 594081284, + 822317253, + 446095505, + 66470938, + 865132823, + 679821480, + 253421170, + 986280348, + 62862089, + 923078164, + 209749445, + 993751058, + 841971693, + 832795028, + 384626433, + 535897932, + 31415926, + ), + ), + scale=1024, + sign=False, +) +"""Pi to 1024 digits of precision.""" + + +# TODO: When Mojo support global variables, +# we save the value of π to a certain precision in the global scope. +# This will allow us to use it everywhere without recalculating it +# if the required precision is the same or lower. +fn pi(precision: Int) raises -> BigDecimal: + """Calculates π using Machin's formula. + π/4 = 4*arctan(1/5) - arctan(1/239). + + Notes: + Time complexity is O(n^3) ~ O(n^4) for precision n. + Every time you double the precision, the time taken increases by a + factor of 8 ~ 16. + """ + + if precision < 0: + raise Error("Precision must be non-negative") + # For precision up to 1024, we can use the precomputed value of π. + # Since π is also input to other functions. + # TODO: Everytime when user calls pi(precision), + # we check whether the precision is higher than the current precision. + # If yes, then we save it into the global scope. + if precision <= 1024: + return PI_1024.round(precision, RoundingMode.ROUND_HALF_EVEN) + + alias BUFFER_DIGITS = 9 # word-length, easy to append and trim + var working_precision = precision + BUFFER_DIGITS + + var bdec_1 = BigDecimal.from_raw_components(UInt32(1)) + var bdec_4 = BigDecimal.from_raw_components(UInt32(4)) + var bdec_5 = BigDecimal.from_raw_components(UInt32(5)) + var bdec_239 = BigDecimal.from_raw_components(UInt32(239)) + + # Calculate 4 * arctan(1/5) + var one_fifth = bdec_1.true_divide(bdec_5, working_precision) + var term1 = bdec_4 * decimojo.bigdecimal.trigonometric.arctan_taylor_series( + one_fifth, working_precision + ) + + # Calculate arctan(1/239) + var one_239 = bdec_1.true_divide(bdec_239, working_precision) + var term2 = decimojo.bigdecimal.trigonometric.arctan_taylor_series( + one_239, working_precision + ) + + # π/4 = 4*arctan(1/5) - arctan(1/239) + var pi_over_4 = term1 - term2 + + # π = 4 * (π/4) + var result = bdec_4 * pi_over_4 + + return result.round(precision, RoundingMode.ROUND_HALF_EVEN) diff --git a/src/decimojo/bigdecimal/exponential.mojo b/src/decimojo/bigdecimal/exponential.mojo index 3f9a120..613910c 100644 --- a/src/decimojo/bigdecimal/exponential.mojo +++ b/src/decimojo/bigdecimal/exponential.mojo @@ -734,7 +734,8 @@ fn exp_taylor_series( minimum_precision: Minimum precision in significant digits. Returns: - The natural exponential of x (e^x) to the specified precision + 9. + The natural exponential of x (e^x) to the specified precision with some + extra digits to ensure accuracy. """ # Theoretical number of terms needed based on precision # For |x| ≤ 1, error after n terms is approximately |x|^(n+1)/(n+1)! diff --git a/src/decimojo/bigdecimal/trigonometric.mojo b/src/decimojo/bigdecimal/trigonometric.mojo new file mode 100644 index 0000000..56be886 --- /dev/null +++ b/src/decimojo/bigdecimal/trigonometric.mojo @@ -0,0 +1,163 @@ +# ===----------------------------------------------------------------------=== # +# Copyright 2025 Yuhao Zhu +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ===----------------------------------------------------------------------=== # + +# ===----------------------------------------------------------------------=== # +# Trigonometric functions for BigDecimal +# ===----------------------------------------------------------------------=== # + +import time + +from decimojo.bigdecimal.bigdecimal import BigDecimal +from decimojo.rounding_mode import RoundingMode +import decimojo.utility + + +# ===----------------------------------------------------------------------=== # +# Trigonometric functions +# ===----------------------------------------------------------------------=== # + + +fn sin(x: BigDecimal, precision: Int) raises: + ... + + +# ===----------------------------------------------------------------------=== # +# Inverse trigonometric functions +# ===----------------------------------------------------------------------=== # + + +fn arctan(x: BigDecimal, precision: Int) raises -> BigDecimal: + """Calculates arctangent (arctan) of the number. + + Notes: + + y = arctan(x), + where x can be all real numbers, + and y is in the range (-π/2, π/2). + """ + + alias BUFFER_DIGITS = 9 # word-length, easy to append and trim + var working_precision = precision + BUFFER_DIGITS + + bdec_1 = BigDecimal.from_raw_components(UInt32(1), scale=0, sign=False) + bdec_2 = BigDecimal.from_raw_components(UInt32(2), scale=0, sign=False) + bdec_0d5 = BigDecimal.from_raw_components(UInt32(5), scale=1, sign=False) + + if x.compare_absolute(bdec_0d5) <= 0: + # |x| <= 0.5, use Taylor series: + # print("Using Taylor series for arctan with |x| <= 0.5") + return arctan_taylor_series(x, minimum_precision=precision).round( + ndigits=precision, rounding_mode=RoundingMode.ROUND_HALF_EVEN + ) + elif x.compare_absolute(bdec_2) <= 0: + # |x| <= 2, use the identity: + # arctan(x) = 2 * arctan(x / (1 + sqrt(1 + x²))) + # This is to ensure convergence of the Taylor series. + # print("Using identity for arctan with |x| <= 2") + var sqrt_term = (bdec_1 + x * x).sqrt(precision=working_precision) + var x_divided = x.true_divide( + bdec_1 + sqrt_term, precision=working_precision + ) + var result = bdec_2 * arctan_taylor_series( + x_divided, minimum_precision=precision + ) + return result.round( + ndigits=precision, rounding_mode=RoundingMode.ROUND_HALF_EVEN + ) + else: # x.compare_absolute(bdec_1) > 0 + # |x| > 2, use the identity: + # For x > 2: arctan(x) = π/2 - arctan(1/x) + # For x < -2: arctan(x) = -π/2 - arctan(1/x) + # This is to ensure convergence of the Taylor series. + # print("Using identity for arctan with |x| > 2") + var half_pi = decimojo.bigdecimal.constants.pi( + precision=working_precision + ).true_divide(bdec_2, precision=working_precision) + var reciprocal_x = bdec_1.true_divide(x, precision=working_precision) + var arctan_reciprocal = arctan_taylor_series( + reciprocal_x^, minimum_precision=precision + ) + + var result: BigDecimal + if x.sign: + result = -half_pi - arctan_reciprocal + else: + result = half_pi - arctan_reciprocal + + return result.round( + ndigits=precision, rounding_mode=RoundingMode.ROUND_HALF_EVEN + ) + + +fn arctan_taylor_series( + x: BigDecimal, minimum_precision: Int +) raises -> BigDecimal: + """Calculates arctangent (arctan) of a number with Taylor series. + + Args: + x: The input number, must be in the range (-0.5, 0.5) for convergence. + minimum_precision: The mininum precision of the result. + + Returns: + The arctangent of the input number with the specified precision plus + some extra digits to ensure accuracy. + + Notes: + + Using Taylor series. + arctan(x) = x - x³/3 + x⁵/5 - x⁷/7 + ... + The input x must be in the range (-0.5, 0.5) for convergence. + Time complexity is O(n^4) for precision n. + Every time you double the precision, the time taken increases by a + factor of 16. + """ + + alias BUFFER_DIGITS = 9 # word-length, easy to append and trim + var working_precision = minimum_precision + BUFFER_DIGITS + + if x.is_zero(): + return BigDecimal(0) + + var term = x # x^n + var term_divided = x # x^n / n + var result = x + var x_squared = x * x + var n = 1 + var sign = -1 + + # Continue until term is smaller than desired precision + var epsilon = BigDecimal(BigUInt.ONE, scale=working_precision, sign=False) + + while term_divided.compare_absolute(epsilon) > 0: + n += 2 + term = term * x_squared # x^n = x^(n-2) * x^2 + term_divided = term.true_divide( + BigDecimal(n), precision=working_precision + ) # x^n / n + if sign == 1: + result += term_divided + else: + result -= term_divided + sign *= -1 + # Ensure that the result will not explode in size + result.round_to_precision( + working_precision, + rounding_mode=RoundingMode.ROUND_DOWN, + remove_extra_digit_due_to_rounding=False, + fill_zeros_to_precision=False, + ) + + return result^ diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index 5c44645..c0f9cb5 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -78,6 +78,12 @@ struct BigUInt(Absable, IntableRaising, Stringable, Writable): """Returns a BigUInt with value 1.""" return Self(words=List[UInt32](UInt32(1))) + @staticmethod + @always_inline + fn power_of_10(exponent: Int) raises -> Self: + """Calculates 10^exponent efficiently.""" + return decimojo.biguint.arithmetics.power_of_10(exponent) + # ===------------------------------------------------------------------=== # # Constructors and life time dunder methods # diff --git a/tests/bigdecimal/test_bigdecimal_trigonometric.mojo b/tests/bigdecimal/test_bigdecimal_trigonometric.mojo new file mode 100644 index 0000000..d94f0a8 --- /dev/null +++ b/tests/bigdecimal/test_bigdecimal_trigonometric.mojo @@ -0,0 +1,39 @@ +""" +Test BigDecimal trigonometric functions +""" + +from python import Python +import testing + +from decimojo import BDec +from decimojo.tests import TestCase, parse_file, load_test_cases + +alias file_path = "tests/bigdecimal/test_data/bigdecimal_trigonometric.toml" + + +fn test_bigdecimal_trignometric() raises: + # Load test cases from TOML file + var toml = parse_file(file_path) + var test_cases: List[TestCase] + + print("------------------------------------------------------") + print("Testing BigDecimal arctan...") + print("------------------------------------------------------") + + test_cases = load_test_cases(toml, "arctan_tests") + for test_case in test_cases: + var result = BDec(test_case.a).arctan(precision=50) + testing.assert_equal( + lhs=String(result), + rhs=test_case.expected, + msg=test_case.description, + ) + + +fn main() raises: + print("Running BigDecimal trigonometric tests") + + # Run all tests + test_bigdecimal_trignometric() + + print("All BigDecimal trigonometric tests passed!") diff --git a/tests/bigdecimal/test_data/bigdecimal_trigonometric.toml b/tests/bigdecimal/test_data/bigdecimal_trigonometric.toml new file mode 100644 index 0000000..b77da5f --- /dev/null +++ b/tests/bigdecimal/test_data/bigdecimal_trigonometric.toml @@ -0,0 +1,129 @@ +# ===----------------------------------------------------------------------=== # +# Test cases for arctan +# ===----------------------------------------------------------------------=== # + +[[arctan_tests]] +a = "0" +b = "" +expected = "0.000000000000000000000000000000000000000000000E-6" +description = "Arctan of 0" + +[[arctan_tests]] +a = "1" +b = "" +expected = "0.78539816339744830961566084581987572104929234984378" +description = "Arctan of 1 = π/4" + +[[arctan_tests]] +a = "-1" +b = "" +expected = "-0.78539816339744830961566084581987572104929234984378" +description = "Arctan of -1 = -π/4" + +[[arctan_tests]] +a = "0.5" +b = "" +expected = "0.46364760900080611621425623146121440202853705428612" +description = "Arctan of 0.5" + +[[arctan_tests]] +a = "-0.5" +b = "" +expected = "-0.46364760900080611621425623146121440202853705428612" +description = "Arctan of -0.5" + +[[arctan_tests]] +a = "0.2" +b = "" +expected = "0.19739555984988075837004976519479029344758510378785" +description = "Arctan of 0.2 (small positive value)" + +[[arctan_tests]] +a = "0.1" +b = "" +expected = "0.09966865249116202737844611987802059024327832250431" +description = "Arctan of 0.1 (very small value for good convergence)" + +[[arctan_tests]] +a = "0.6" +b = "" +expected = "0.54041950027058415544357836460859991013514825146259" +description = "Arctan of 0.6" + +[[arctan_tests]] +a = "0.7" +b = "" +expected = "0.61072596438920861654375887649023609381850306612883" +description = "Arctan of 0.7" + +[[arctan_tests]] +a = "0.8" +b = "" +expected = "0.67474094222355266305652097360981361507400625484071" +description = "Arctan of 0.8" + +[[arctan_tests]] +a = "0.9" +b = "" +expected = "0.73281510178650659164079207273428025198575567935826" +description = "Arctan of 0.9" + +[[arctan_tests]] +a = "2" +b = "" +expected = "1.10714871779409050301706546017853704007004764540143" +description = "Arctan of 2 (>1, tests convergence for larger values)" + +[[arctan_tests]] +a = "3" +b = "" +expected = "1.24904577239825442582991707728109012307782940412990" +description = "Arctan of 3" + +[[arctan_tests]] +a = "5" +b = "" +expected = "1.37340076694501586086127192644496114865099959589970" +description = "Arctan of 5 (large value)" + +[[arctan_tests]] +a = "-10" +b = "" +expected = "-1.47112767430373459185287557176173085185530637718324" +description = "Arctan of -10 (negative large value)" + +[[arctan_tests]] +a = "-20" +b = "" +expected = "-1.52083793107295385782131540460490656060730761926405" +description = "Arctan of -20 (negative large value)" + +[[arctan_tests]] +a = "0.2679491924311227064725536584405014305617238566135711746170850036962728620449831" +b = "" +expected = "0.26179938779914943653855361522325797893391221422499" +description = "Arctan of tan(π/12) should equal π/12" + +[[arctan_tests]] +a = "0.5773502691896257645091487805019574556476017512701268760186023264839776723054951" +b = "" +expected = "0.52359877559829887307710723054658381403286156656252" +description = "Arctan of tan(π/6) = √3/3 should equal π/6" + +[[arctan_tests]] +a = "-1.7320508075688772935274463415058723669428052538103806280558069794519330169088000" +b = "" +expected = "-1.04719755119659774615421446109316762806572313312504" +description = "Arctan of -√3 should equal -π/3" + +[[arctan_tests]] +a = "0.00001" +b = "" +expected = "9.99999999966666666668666666666523809523820635E-6" +description = "Arctan of very small value (near linear approximation)" + +[[arctan_tests]] +a = "100" +b = "" +expected = "1.56079666010823138102498157543047189353721534714318" +description = "Arctan of 100 (very large value, should approach π/2)"