A few years ago when I wrote this subroutine I found that depending of the compiler in use a call to this routine was either marginally slower that two sequential calls to sin and cos, and potentially up to 2 times faster.
…
pure elemental subroutine sincosD(x, s, c)
!--------------------------------------------------------------
! SUBROUTINE: sincos
!
! DESCRIPTION:
! Computes both the sine and cosine of an angle simultaneously.
!
! INTERFACE:
! x (Input) : The angle in radians, 64-bit real.
! s (Output) : The calculated sine value, 64-bit real.
! c (Output) : The calculated cosine value, 64-bit real.
!
! METHODOLOGY:
! 1. Range Reduction: Uses high-precision Cody-Waite
! constants (P1, P2, P3) to reduce the input angle x
! modulo pi/2, preventing catastrophic cancellation error
! for large values of x.
! 2. Approximation: Computes polynomial expansions for sine
! and cosine using Maclaurin Series.
! 3. Reconstruction: Uses a lookup table (LUT) based on the
! quadrant to correct the signs and swap sine/cosine
! functions as required.
!--------------------------------------------------------------
use iso_fortran_env, only: real64, int32, int64
implicit none
integer, parameter :: wp = real64
real(wp), intent(in) :: x
real(wp), intent(out) :: s, c
real(wp) :: x_reduced, x2, s_poly, c_poly
integer(int64) :: quadrant
integer(int32) :: q_idx
! Constants (sufficient for real128)
real(wp), parameter :: PI_4 = 0.78539816339744830961566084581987572_wp
real(wp), parameter :: INV_PI_2 = 0.63661977236758134307553505349005745_wp
! High-Precision Cody-Waite Split Constants (for real64)
real(wp), parameter :: P1 = 1.570796326734125614166259765625_wp
real(wp), parameter :: P2 = 6.0771005063039659766010730103343e-11_wp
real(wp), parameter :: P3 = 2.0222662487959506315414243419361e-21_wp
! Cosine Factorial Reciprocals
real(wp), parameter :: C12 = 1.0_wp / 479001600.0_wp
real(wp), parameter :: C10 = -1.0_wp / 3628800.0_wp
real(wp), parameter :: C8 = 1.0_wp / 40320.0_wp
real(wp), parameter :: C6 = -1.0_wp / 720.0_wp
real(wp), parameter :: C4 = 1.0_wp / 24.0_wp
real(wp), parameter :: C2 = -0.5_wp
! Sine Factorial Reciprocals
real(wp), parameter :: S13 = 1.0_wp / 6227020800.0_wp
real(wp), parameter :: S11 = -1.0_wp / 39916800.0_wp
real(wp), parameter :: S9 = 1.0_wp / 362880.0_wp
real(wp), parameter :: S7 = -1.0_wp / 5040.0_wp
real(wp), parameter :: S5 = 1.0_wp / 120.0_wp
real(wp), parameter :: S3 = -1.0_wp / 6.0_wp
! Lookup Tables
real(wp), parameter :: cos_sign_lut(0:3) = [1.0_wp,-1.0_wp,-1.0_wp, 1.0_wp]
real(wp), parameter :: sin_sign_lut(0:3) = [1.0_wp, 1.0_wp,-1.0_wp,-1.0_wp]
real(wp), parameter :: cos_weight(0:3) = [1.0_wp, 0.0_wp, 1.0_wp, 0.0_wp]
real(wp), parameter :: sin_weight(0:3) = [0.0_wp, 1.0_wp, 0.0_wp, 1.0_wp]
if (x == 0.0_wp) then
s = x
c = 1.0_wp
else
! Range reduction
if (abs(x) <= PI_4) then
x_reduced = x
q_idx = 0
else
quadrant = int(x * INV_PI_2 + sign(0.5_wp, x), kind=int64)
x_reduced = ((x - real(quadrant, kind=wp) * P1) &
- real(quadrant, kind=wp) * P2) &
- real(quadrant, kind=wp) * P3
! Zero-based masking
q_idx = int(iand(quadrant, 3_int64), kind=int32)
end if
x2 = x_reduced * x_reduced
! Cosine
c_poly = C10 + x2 * C12
c_poly = C8 + x2 * c_poly
c_poly = C6 + x2 * c_poly
c_poly = C4 + x2 * c_poly
c_poly = C2 + x2 * c_poly
c_poly = 1.0_wp + x2 * c_poly
! Sine
s_poly = S11 + x2 * S13
s_poly = S9 + x2 * s_poly
s_poly = S7 + x2 * s_poly
s_poly = S5 + x2 * s_poly
s_poly = S3 + x2 * s_poly
s_poly = x_reduced * (1.0_wp + x2 * s_poly)
! Reconstruction
c = (c_poly * cos_weight(q_idx) + s_poly * sin_weight(q_idx)) * cos_sign_lut(q_idx)
s = (s_poly * cos_weight(q_idx) + c_poly * sin_weight(q_idx)) * sin_sign_lut(q_idx)
end if
end subroutine sincosD
…