Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/decimojo/bigdecimal/bigdecimal.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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."""
Expand Down
177 changes: 168 additions & 9 deletions src/decimojo/bigdecimal/trigonometric.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -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


# ===----------------------------------------------------------------------=== #
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
# ===----------------------------------------------------------------------=== #
Expand Down
29 changes: 22 additions & 7 deletions tests/bigdecimal/test_bigdecimal_trigonometric.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand All @@ -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:
Expand Down
Loading