Skip to content

Commit 21ee99b

Browse files
authored
[decimal] Implement tan(), cot(), csc(), sec() for BigDecimal (#99)
This pull request enhances the `BigDecimal` module by adding new trigonometric functions, improving precision handling, and updating test cases to ensure correctness. The most important changes include the addition of tangent, cotangent, cosecant, and secant functions, updates to the `sin` and `cos` functions for zero handling, and the inclusion of comprehensive test cases for the new functionality. ### Enhancements to `BigDecimal` functionality: * Added new trigonometric functions: `tan`, `cot`, `csc`, and `sec` to the `BigDecimal` struct, enabling calculations for tangent, cotangent, cosecant, and secant with arbitrary precision. [[1]](diffhunk://#diff-be2f9b2702fd15952546e17fe94a64c22902a0e4678bfd9cb925269647923db4R781-R800) [[2]](diffhunk://#diff-c958e120b7d4b4345780398bc974ce25453a40ef0edc4a7b1499844d3164640cR298-R461) * Improved handling of zero values in `sin` and `cos` functions to use `BigDecimal(BigUInt.ZERO)` and `BigDecimal(BigUInt.ONE)` for better precision and consistency. [[1]](diffhunk://#diff-c958e120b7d4b4345780398bc974ce25453a40ef0edc4a7b1499844d3164640cL59-R60) [[2]](diffhunk://#diff-c958e120b7d4b4345780398bc974ce25453a40ef0edc4a7b1499844d3164640cL176-R175) [[3]](diffhunk://#diff-c958e120b7d4b4345780398bc974ce25453a40ef0edc4a7b1499844d3164640cL231-R228) ### Updates to test cases: * Added test cases for new trigonometric functions (`tan`, `cot`, `csc`, and `sec`) to the TOML test data file, covering edge cases and typical values. * Updated the `run_test` function to directly compare `BigDecimal` objects instead of converting them to strings for improved accuracy in assertions. * Expanded the `test_bigdecimal_trigonometric` function to include tests for `cos`, `tan`, and `cot` functions alongside existing tests for `sin` and `arctan`. ### Codebase improvements: * Refactored import statements in `trigonometric.mojo` to include `decimojo.bigdecimal.constants`, ensuring access to constants like π for trigonometric calculations.
1 parent 9359e6a commit 21ee99b

File tree

4 files changed

+309
-19
lines changed

4 files changed

+309
-19
lines changed

src/decimojo/bigdecimal/bigdecimal.mojo

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ alias BDec = BigDecimal
3535

3636
@value
3737
struct BigDecimal(
38-
Absable, Comparable, IntableRaising, Roundable, Stringable, Writable
38+
Absable,
39+
Comparable,
40+
FloatableRaising,
41+
IntableRaising,
42+
Roundable,
43+
Stringable,
44+
Writable,
3945
):
4046
"""Represents a arbitrary-precision decimal.
4147
@@ -772,6 +778,26 @@ struct BigDecimal(
772778
"""Returns the cosine of the BigDecimal number."""
773779
return decimojo.bigdecimal.trigonometric.cos(self, precision)
774780

781+
@always_inline
782+
fn tan(self, precision: Int = 28) raises -> Self:
783+
"""Returns the tangent of the BigDecimal number."""
784+
return decimojo.bigdecimal.trigonometric.tan(self, precision)
785+
786+
@always_inline
787+
fn cot(self, precision: Int = 28) raises -> Self:
788+
"""Returns the cotangent of the BigDecimal number."""
789+
return decimojo.bigdecimal.trigonometric.cot(self, precision)
790+
791+
@always_inline
792+
fn csc(self, precision: Int = 28) raises -> Self:
793+
"""Returns the cosecant of the BigDecimal number."""
794+
return decimojo.bigdecimal.trigonometric.csc(self, precision)
795+
796+
@always_inline
797+
fn sec(self, precision: Int = 28) raises -> Self:
798+
"""Returns the secant of the BigDecimal number."""
799+
return decimojo.bigdecimal.trigonometric.sec(self, precision)
800+
775801
@always_inline
776802
fn arctan(self, precision: Int = 28) raises -> Self:
777803
"""Returns the arctangent of the BigDecimal number."""

src/decimojo/bigdecimal/trigonometric.mojo

Lines changed: 168 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import time
2323
from decimojo.bigdecimal.bigdecimal import BigDecimal
2424
from decimojo.rounding_mode import RoundingMode
2525
import decimojo.utility
26+
import decimojo.bigdecimal.constants
2627

2728

2829
# ===----------------------------------------------------------------------=== #
@@ -56,9 +57,7 @@ fn sin(x: BigDecimal, precision: Int) raises -> BigDecimal:
5657
var result: BigDecimal
5758

5859
if x.is_zero():
59-
return BigDecimal.from_raw_components(
60-
UInt32(0), scale=precision, sign=x.sign
61-
)
60+
return BigDecimal(BigUInt.ZERO)
6261

6362
var bdec_2 = BigDecimal.from_raw_components(UInt32(2), scale=0, sign=False)
6463
var bdec_4 = BigDecimal.from_raw_components(UInt32(4), scale=0, sign=False)
@@ -173,9 +172,7 @@ fn sin_taylor_series(
173172
var working_precision = minimum_precision + BUFFER_DIGITS
174173

175174
if x.is_zero():
176-
return BigDecimal.from_raw_components(
177-
UInt32(0), scale=minimum_precision, sign=x.sign
178-
)
175+
return BigDecimal(BigUInt.ZERO)
179176

180177
var term = x # x^n / n!
181178
var result = x
@@ -228,9 +225,7 @@ fn cos(x: BigDecimal, precision: Int) raises -> BigDecimal:
228225
var working_precision = precision + BUFFER_DIGITS
229226

230227
if x.is_zero():
231-
return BigDecimal.from_raw_components(
232-
UInt32(1), scale=precision, sign=x.sign
233-
)
228+
return BigDecimal(BigUInt.ONE)
234229

235230
# cos(x) = sin(π/2 - x)
236231
var pi = decimojo.bigdecimal.constants.pi(precision=working_precision)
@@ -300,6 +295,170 @@ fn cos_taylor_series(
300295
return result^
301296

302297

298+
fn tan(x: BigDecimal, precision: Int) raises -> BigDecimal:
299+
"""Calculates tangent (tan) of the number.
300+
301+
Args:
302+
x: The input number in radians.
303+
precision: The desired precision of the result.
304+
305+
Returns:
306+
The tangent of x with the specified precision.
307+
308+
Notes:
309+
310+
This function calculates tan(x) = sin(x) / cos(x).
311+
"""
312+
return tan_cot(x, precision, is_tan=True)
313+
314+
315+
fn cot(x: BigDecimal, precision: Int) raises -> BigDecimal:
316+
"""Calculates cotangent (cot) of the number.
317+
318+
Args:
319+
x: The input number in radians.
320+
precision: The desired precision of the result.
321+
322+
Returns:
323+
The cotangent of x with the specified precision.
324+
325+
Notes:
326+
327+
This function calculates cot(x) = cos(x) / sin(x).
328+
"""
329+
return tan_cot(x, precision, is_tan=False)
330+
331+
332+
fn tan_cot(x: BigDecimal, precision: Int, is_tan: Bool) raises -> BigDecimal:
333+
"""Calculates tangent (tan) or cotangent (cot) of the number.
334+
335+
Args:
336+
x: The input number in radians.
337+
precision: The desired precision of the result.
338+
is_tan: If True, calculates tangent; if False, calculates cotangent.
339+
340+
Returns:
341+
The cotangent of x with the specified precision.
342+
343+
Notes:
344+
345+
This function calculates tan(x) = cos(x) / sin(x) or
346+
cot(x) = sin(x) / cos(x) depending on the is_tan flag.
347+
"""
348+
349+
alias BUFFER_DIGITS = 99
350+
var working_precision_pi = precision + 2 * BUFFER_DIGITS
351+
var working_precision = precision + BUFFER_DIGITS
352+
353+
if x.is_zero():
354+
if is_tan:
355+
return BigDecimal(BigUInt.ZERO)
356+
else:
357+
# cot(0) is undefined, but we return 0 for consistency
358+
# since tan(0) is defined as 0.
359+
# This is a design choice, not a mathematical one.
360+
# In practice, cot(0) should raise an error.
361+
raise Error(
362+
"bigdecimal.trigonometric.tan_cot: cot(nπ) is undefined."
363+
)
364+
365+
var pi = decimojo.bigdecimal.constants.pi(precision=working_precision_pi)
366+
var bdec_2 = BigDecimal.from_raw_components(UInt32(2), scale=0, sign=False)
367+
var two_pi = bdec_2 * pi
368+
var pi_div_2 = pi.true_divide(bdec_2, precision=working_precision_pi)
369+
370+
var x_reduced = x
371+
# First reduce to (-π, π) range
372+
if x_reduced.compare_absolute(pi) > 0:
373+
x_reduced = x_reduced % two_pi
374+
# Adjust to (-π, π) range
375+
if x_reduced.compare_absolute(pi) > 0:
376+
if x_reduced.sign:
377+
x_reduced += two_pi
378+
else:
379+
x_reduced -= two_pi
380+
381+
# Now reduce to (-π/2, π/2) using tan(x + π) = tan(x)
382+
if x_reduced.compare_absolute(pi_div_2) > 0:
383+
if x_reduced.sign:
384+
x_reduced += pi
385+
else:
386+
x_reduced -= pi
387+
388+
# Calculate
389+
# tan(x) = sin(x) / cos(x)
390+
# cot(x) = cos(x) / sin(x)
391+
var sin_x: BigDecimal = sin(x_reduced, precision=working_precision)
392+
var cos_x: BigDecimal = cos(x_reduced, precision=working_precision)
393+
if is_tan:
394+
result: BigDecimal = sin_x.true_divide(
395+
cos_x, precision=working_precision
396+
)
397+
else:
398+
result: BigDecimal = cos_x.true_divide(
399+
sin_x, precision=working_precision
400+
)
401+
402+
result.round_to_precision(
403+
precision,
404+
RoundingMode.ROUND_HALF_EVEN,
405+
remove_extra_digit_due_to_rounding=True,
406+
fill_zeros_to_precision=False,
407+
)
408+
409+
return result^
410+
411+
412+
fn csc(x: BigDecimal, precision: Int) raises -> BigDecimal:
413+
"""Calculates cosecant (csc) of the number.
414+
415+
Args:
416+
x: The input number in radians.
417+
precision: The desired precision of the result.
418+
419+
Returns:
420+
The cosecant of x with the specified precision.
421+
422+
Notes:
423+
424+
This function calculates csc(x) = 1 / sin(x).
425+
"""
426+
if x.is_zero():
427+
raise Error("bigdecimal.trigonometric.csc: csc(nπ) is undefined.")
428+
429+
alias BUFFER_DIGITS = 9
430+
var working_precision = precision + BUFFER_DIGITS
431+
432+
var sin_x = sin(x, precision=working_precision)
433+
434+
return BigDecimal(BigUInt.ONE).true_divide(sin_x, precision=precision)
435+
436+
437+
fn sec(x: BigDecimal, precision: Int) raises -> BigDecimal:
438+
"""Calculates secant (sec) of the number.
439+
440+
Args:
441+
x: The input number in radians.
442+
precision: The desired precision of the result.
443+
444+
Returns:
445+
The secant of x with the specified precision.
446+
447+
Notes:
448+
449+
This function calculates sec(x) = 1 / cos(x).
450+
"""
451+
if x.is_zero():
452+
return BigDecimal(BigUInt.ONE)
453+
454+
alias BUFFER_DIGITS = 9
455+
var working_precision = precision + BUFFER_DIGITS
456+
457+
var cos_x = cos(x, precision=working_precision)
458+
459+
return BigDecimal(BigUInt.ONE).true_divide(cos_x, precision=precision)
460+
461+
303462
# ===----------------------------------------------------------------------=== #
304463
# Inverse trigonometric functions
305464
# ===----------------------------------------------------------------------=== #

tests/bigdecimal/test_bigdecimal_trigonometric.mojo

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ fn run_test[
2121
for test_case in test_cases:
2222
var result = func(BDec(test_case.a), 50)
2323
testing.assert_equal(
24-
lhs=String(result),
25-
rhs=test_case.expected,
24+
lhs=result,
25+
rhs=BDec(test_case.expected),
2626
msg=test_case.description,
2727
)
2828

@@ -31,16 +31,31 @@ fn test_bigdecimal_trignometric() raises:
3131
# Load test cases from TOML file
3232
var toml = parse_file(file_path)
3333

34-
run_test[func = decimojo.bigdecimal.trigonometric.arctan](
35-
toml,
36-
"arctan_tests",
37-
"arctan",
38-
)
3934
run_test[func = decimojo.bigdecimal.trigonometric.sin](
4035
toml,
4136
"sin_tests",
4237
"sin",
4338
)
39+
run_test[func = decimojo.bigdecimal.trigonometric.cos](
40+
toml,
41+
"cos_tests",
42+
"cos",
43+
)
44+
run_test[func = decimojo.bigdecimal.trigonometric.tan](
45+
toml,
46+
"tan_tests",
47+
"tan",
48+
)
49+
run_test[func = decimojo.bigdecimal.trigonometric.cot](
50+
toml,
51+
"cot_tests",
52+
"cot",
53+
)
54+
run_test[func = decimojo.bigdecimal.trigonometric.arctan](
55+
toml,
56+
"arctan_tests",
57+
"arctan",
58+
)
4459

4560

4661
fn main() raises:

0 commit comments

Comments
 (0)