diff --git a/src/decimojo/bigdecimal/bigdecimal.mojo b/src/decimojo/bigdecimal/bigdecimal.mojo index 7e0388b..f690075 100644 --- a/src/decimojo/bigdecimal/bigdecimal.mojo +++ b/src/decimojo/bigdecimal/bigdecimal.mojo @@ -35,7 +35,13 @@ alias BDec = BigDecimal @value struct BigDecimal( - Absable, Comparable, IntableRaising, Roundable, Stringable, Writable + Absable, + Comparable, + FloatableRaising, + IntableRaising, + Roundable, + Stringable, + Writable, ): """Represents a arbitrary-precision decimal. @@ -772,6 +778,26 @@ struct BigDecimal( """Returns the cosine of the BigDecimal number.""" return decimojo.bigdecimal.trigonometric.cos(self, precision) + @always_inline + fn tan(self, precision: Int = 28) raises -> Self: + """Returns the tangent of the BigDecimal number.""" + return decimojo.bigdecimal.trigonometric.tan(self, precision) + + @always_inline + fn cot(self, precision: Int = 28) raises -> Self: + """Returns the cotangent of the BigDecimal number.""" + return decimojo.bigdecimal.trigonometric.cot(self, precision) + + @always_inline + fn csc(self, precision: Int = 28) raises -> Self: + """Returns the cosecant of the BigDecimal number.""" + return decimojo.bigdecimal.trigonometric.csc(self, precision) + + @always_inline + fn sec(self, precision: Int = 28) raises -> Self: + """Returns the secant of the BigDecimal number.""" + return decimojo.bigdecimal.trigonometric.sec(self, precision) + @always_inline fn arctan(self, precision: Int = 28) raises -> Self: """Returns the arctangent of the BigDecimal number.""" diff --git a/src/decimojo/bigdecimal/trigonometric.mojo b/src/decimojo/bigdecimal/trigonometric.mojo index 19af310..966ad21 100644 --- a/src/decimojo/bigdecimal/trigonometric.mojo +++ b/src/decimojo/bigdecimal/trigonometric.mojo @@ -23,6 +23,7 @@ import time from decimojo.bigdecimal.bigdecimal import BigDecimal from decimojo.rounding_mode import RoundingMode import decimojo.utility +import decimojo.bigdecimal.constants # ===----------------------------------------------------------------------=== # @@ -56,9 +57,7 @@ fn sin(x: BigDecimal, precision: Int) raises -> BigDecimal: var result: BigDecimal if x.is_zero(): - return BigDecimal.from_raw_components( - UInt32(0), scale=precision, sign=x.sign - ) + return BigDecimal(BigUInt.ZERO) var bdec_2 = BigDecimal.from_raw_components(UInt32(2), scale=0, sign=False) var bdec_4 = BigDecimal.from_raw_components(UInt32(4), scale=0, sign=False) @@ -173,9 +172,7 @@ fn sin_taylor_series( var working_precision = minimum_precision + BUFFER_DIGITS if x.is_zero(): - return BigDecimal.from_raw_components( - UInt32(0), scale=minimum_precision, sign=x.sign - ) + return BigDecimal(BigUInt.ZERO) var term = x # x^n / n! var result = x @@ -228,9 +225,7 @@ fn cos(x: BigDecimal, precision: Int) raises -> BigDecimal: var working_precision = precision + BUFFER_DIGITS if x.is_zero(): - return BigDecimal.from_raw_components( - UInt32(1), scale=precision, sign=x.sign - ) + return BigDecimal(BigUInt.ONE) # cos(x) = sin(π/2 - x) var pi = decimojo.bigdecimal.constants.pi(precision=working_precision) @@ -300,6 +295,170 @@ fn cos_taylor_series( return result^ +fn tan(x: BigDecimal, precision: Int) raises -> BigDecimal: + """Calculates tangent (tan) of the number. + + Args: + x: The input number in radians. + precision: The desired precision of the result. + + Returns: + The tangent of x with the specified precision. + + Notes: + + This function calculates tan(x) = sin(x) / cos(x). + """ + return tan_cot(x, precision, is_tan=True) + + +fn cot(x: BigDecimal, precision: Int) raises -> BigDecimal: + """Calculates cotangent (cot) of the number. + + Args: + x: The input number in radians. + precision: The desired precision of the result. + + Returns: + The cotangent of x with the specified precision. + + Notes: + + This function calculates cot(x) = cos(x) / sin(x). + """ + return tan_cot(x, precision, is_tan=False) + + +fn tan_cot(x: BigDecimal, precision: Int, is_tan: Bool) raises -> BigDecimal: + """Calculates tangent (tan) or cotangent (cot) of the number. + + Args: + x: The input number in radians. + precision: The desired precision of the result. + is_tan: If True, calculates tangent; if False, calculates cotangent. + + Returns: + The cotangent of x with the specified precision. + + Notes: + + This function calculates tan(x) = cos(x) / sin(x) or + cot(x) = sin(x) / cos(x) depending on the is_tan flag. + """ + + alias BUFFER_DIGITS = 99 + var working_precision_pi = precision + 2 * BUFFER_DIGITS + var working_precision = precision + BUFFER_DIGITS + + if x.is_zero(): + if is_tan: + return BigDecimal(BigUInt.ZERO) + else: + # cot(0) is undefined, but we return 0 for consistency + # since tan(0) is defined as 0. + # 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." + ) + + var pi = decimojo.bigdecimal.constants.pi(precision=working_precision_pi) + var bdec_2 = BigDecimal.from_raw_components(UInt32(2), scale=0, sign=False) + var two_pi = bdec_2 * pi + var pi_div_2 = pi.true_divide(bdec_2, precision=working_precision_pi) + + var x_reduced = x + # First reduce to (-π, π) range + if x_reduced.compare_absolute(pi) > 0: + x_reduced = x_reduced % two_pi + # Adjust to (-π, π) range + if x_reduced.compare_absolute(pi) > 0: + if x_reduced.sign: + x_reduced += two_pi + else: + x_reduced -= two_pi + + # Now reduce to (-π/2, π/2) using tan(x + π) = tan(x) + if x_reduced.compare_absolute(pi_div_2) > 0: + if x_reduced.sign: + x_reduced += pi + else: + x_reduced -= pi + + # Calculate + # tan(x) = sin(x) / cos(x) + # cot(x) = cos(x) / sin(x) + var sin_x: BigDecimal = sin(x_reduced, precision=working_precision) + var cos_x: BigDecimal = cos(x_reduced, precision=working_precision) + if is_tan: + result: BigDecimal = sin_x.true_divide( + cos_x, precision=working_precision + ) + else: + result: BigDecimal = cos_x.true_divide( + sin_x, precision=working_precision + ) + + result.round_to_precision( + precision, + RoundingMode.ROUND_HALF_EVEN, + remove_extra_digit_due_to_rounding=True, + fill_zeros_to_precision=False, + ) + + return result^ + + +fn csc(x: BigDecimal, precision: Int) raises -> BigDecimal: + """Calculates cosecant (csc) of the number. + + Args: + x: The input number in radians. + precision: The desired precision of the result. + + Returns: + The cosecant of x with the specified precision. + + Notes: + + This function calculates csc(x) = 1 / sin(x). + """ + if x.is_zero(): + raise Error("bigdecimal.trigonometric.csc: csc(nπ) is undefined.") + + alias BUFFER_DIGITS = 9 + var working_precision = precision + BUFFER_DIGITS + + var sin_x = sin(x, precision=working_precision) + + return BigDecimal(BigUInt.ONE).true_divide(sin_x, precision=precision) + + +fn sec(x: BigDecimal, precision: Int) raises -> BigDecimal: + """Calculates secant (sec) of the number. + + Args: + x: The input number in radians. + precision: The desired precision of the result. + + Returns: + The secant of x with the specified precision. + + Notes: + + This function calculates sec(x) = 1 / cos(x). + """ + if x.is_zero(): + return BigDecimal(BigUInt.ONE) + + alias BUFFER_DIGITS = 9 + var working_precision = precision + BUFFER_DIGITS + + var cos_x = cos(x, precision=working_precision) + + return BigDecimal(BigUInt.ONE).true_divide(cos_x, precision=precision) + + # ===----------------------------------------------------------------------=== # # Inverse trigonometric functions # ===----------------------------------------------------------------------=== # diff --git a/tests/bigdecimal/test_bigdecimal_trigonometric.mojo b/tests/bigdecimal/test_bigdecimal_trigonometric.mojo index 0704cdc..9e7d183 100644 --- a/tests/bigdecimal/test_bigdecimal_trigonometric.mojo +++ b/tests/bigdecimal/test_bigdecimal_trigonometric.mojo @@ -21,8 +21,8 @@ fn run_test[ for test_case in test_cases: var result = func(BDec(test_case.a), 50) testing.assert_equal( - lhs=String(result), - rhs=test_case.expected, + lhs=result, + rhs=BDec(test_case.expected), msg=test_case.description, ) @@ -31,16 +31,31 @@ fn test_bigdecimal_trignometric() raises: # Load test cases from TOML file var toml = parse_file(file_path) - run_test[func = decimojo.bigdecimal.trigonometric.arctan]( - toml, - "arctan_tests", - "arctan", - ) run_test[func = decimojo.bigdecimal.trigonometric.sin]( toml, "sin_tests", "sin", ) + run_test[func = decimojo.bigdecimal.trigonometric.cos]( + toml, + "cos_tests", + "cos", + ) + run_test[func = decimojo.bigdecimal.trigonometric.tan]( + toml, + "tan_tests", + "tan", + ) + run_test[func = decimojo.bigdecimal.trigonometric.cot]( + toml, + "cot_tests", + "cot", + ) + run_test[func = decimojo.bigdecimal.trigonometric.arctan]( + toml, + "arctan_tests", + "arctan", + ) fn main() raises: diff --git a/tests/bigdecimal/test_data/bigdecimal_trigonometric.toml b/tests/bigdecimal/test_data/bigdecimal_trigonometric.toml index a600fa8..0925442 100644 --- a/tests/bigdecimal/test_data/bigdecimal_trigonometric.toml +++ b/tests/bigdecimal/test_data/bigdecimal_trigonometric.toml @@ -5,7 +5,7 @@ [[sin_tests]] a = "0" b = "" -expected = "0E-50" +expected = "0" description = "sin(0) = 0" [[sin_tests]] @@ -122,6 +122,96 @@ b = "" expected = "0.000099999999833333333416666666646825396828152557318973" description = "sin(0.0001) - very small value (linear approximation region)" +# ===----------------------------------------------------------------------=== # +# Test cases for cos +# ===----------------------------------------------------------------------=== # + +[[cos_tests]] +a = "0" +b = "" +expected = "1" +description = "cos(0) = 1" + +[[cos_tests]] +a = "0.5235987755982988730771072305" +b = "" +expected = "0.86602540378443864676372317077622809048783341018645" +description = "cos(π/6) = √3/2" + +[[cos_tests]] +a = "-1.2345678901234567890123456789012345678901234567890" +b = "" +expected = "0.32992906977373266497821982544739832858011036857534" +description = "negative value" + +# ===----------------------------------------------------------------------=== # +# Test cases for tan +# ===----------------------------------------------------------------------=== # + +[[tan_tests]] +a = "1.5707963267948966192313216916397514420985846996876" +b = "" +expected = "-2.1236151030692384854558538473739128298113204417314E+49" +description = "tan(π/2) - very large or small value" + +[[tan_tests]] +a = "0" +b = "" +expected = "0" +description = "tan(0) = 0" + +[[tan_tests]] +a = "0.78539816339744830961566084581987572104929234984378" +b = "" +expected = "1.0000000000000000000000000000000000000000000000000" +description = "tan(π/4) = 1" + +[[tan_tests]] +a = "1.0471975511965977461542144610931676280657231331250" +b = "" +expected = "1.7320508075688772935274463415058723669428052538102" +description = "tan(π/3) = √3" + +[[tan_tests]] +a = "0.1" +b = "" +expected = "0.10033467208545054505808004578111153681900480457644" +description = "tan(0.1) - small positive value" + +[[tan_tests]] +a = "-0.3" +b = "" +expected = "-0.30933624960962323303530367969829466725781590680046" +description = "tan(-0.3) - small negative value" + +[[tan_tests]] +a = "100" +b = "" +expected = "-0.58721391515692907667780963564458789425876598687292" +description = "tan(100) - very large value" + +# ===----------------------------------------------------------------------=== # +# Test cases for cot +# ===----------------------------------------------------------------------=== # + +[[cot_tests]] +a = "1" +b = "" +expected = "0.64209261593433070300641998659426562023027811391817" +description = "cot(1) - small positive value" + +[[cot_tests]] +a = "-3" +b = "" +expected = "7.0152525514345334694285513795264765782931033520964" +description = "cot(-3) - small negative value" + +[[cot_tests]] +a = "10" +b = "" +expected = "1.5423510453569200482774693556824293113206672064020" +description = "cot(10) - large positive value" + # ===----------------------------------------------------------------------=== # # Test cases for arctan # ===----------------------------------------------------------------------=== # @@ -129,7 +219,7 @@ description = "sin(0.0001) - very small value (linear approximation region)" [[arctan_tests]] a = "0" b = "" -expected = "0E-50" +expected = "0" description = "Arctan of 0" [[arctan_tests]]