///| Compute cosine of double-precision floating-point number `x`
///
/// # Examples
///
/// ```moonbit
/// assert_eq!(cos(-1.0), 0.5403023058681398)
/// assert_eq!(cos(0.0), 1)
/// assert_eq!(cos(1.5707963267948966), 0.00000000000000006123233995736766)
/// assert_eq!(cos(3.141592653589793), -1)
/// assert_eq!(cos(10000), -0.9521553682590148)
/// ```
///
/// # Special Cases
///
/// 1. cos(+-inf) = NaN
/// 2. cos(NaN) = NaN
///
/// # Accuracy
///
/// 0 ulp.
pub fn cos(x : Double) -> Double {
  if isinf(x) || isnan(x) {
    return @double.not_a_number
  }
  let y = [0.0, 0.0]
  let z = 0.0
  if fabs(x) <= PI_OVER_4 {
    return __kernel_cos(x, z)
  } else {
    let n = rem_pio2(x, y)
    match n & 3 {
      0 => __kernel_cos(y[0], y[1])
      1 => -__kernel_sin(y[0], y[1], 1)
      2 => -__kernel_cos(y[0], y[1])
      _ => __kernel_sin(y[0], y[1], 1)
    }
  }
}

///|
test "cos" {
  fn assert_cos_ulp!(input, expect) {
    assert_ulp!(expect, cos(input), COS_MAX_ULP)
  }

  assert_cos_ulp!(-0.8, 0.6967067093471654)
  assert_cos_ulp!(-0.7, 0.7648421872844885)
  assert_cos_ulp!(-0.6, 0.8253356149096783)
  assert_cos_ulp!(-0.5, 0.8775825618903728)
  assert_cos_ulp!(-0.4, 0.9210609940028851)
  assert_cos_ulp!(-0.3, 0.955336489125606)
  assert_cos_ulp!(-0.2, 0.9800665778412416)
  assert_cos_ulp!(-0.1, 0.9950041652780257)
  assert_cos_ulp!(-3.141592653589793, -1)
  assert_cos_ulp!(-1.5707963267948966, 6.123233995736766e-17)
  assert_cos_ulp!(-0.7853981633974483, 0.7071067811865476)
  assert_cos_ulp!(0.1, 0.9950041652780257)
  assert_cos_ulp!(0.2, 0.9800665778412416)
  assert_cos_ulp!(0.3, 0.955336489125606)
  assert_cos_ulp!(0.4, 0.9210609940028851)
  assert_cos_ulp!(0.5, 0.8775825618903728)
  assert_cos_ulp!(0.6, 0.8253356149096783)
  assert_cos_ulp!(0.7, 0.7648421872844885)
  assert_cos_ulp!(0.8, 0.6967067093471654)
  assert_cos_ulp!(0.9, 0.6216099682706644)
  assert_cos_ulp!(1, 0.5403023058681398)
  assert_cos_ulp!(3.141592653589793, -1)
  assert_cos_ulp!(1.5707963267948966, 6.123233995736766e-17)
  assert_cos_ulp!(0.7853981633974483, 0.7071067811865476)
  assert_cos_ulp!(-1, 0.5403023058681398)
  assert_cos_ulp!(-2, -0.4161468365471424)
  assert_cos_ulp!(-3, -0.9899924966004454)
  assert_cos_ulp!(-4, -0.6536436208636119)
  assert_cos_ulp!(-5, 0.28366218546322625)
  assert_cos_ulp!(-6, 0.9601702866503661)
  assert_cos_ulp!(-7, 0.7539022543433046)
  assert_cos_ulp!(-8, -0.14550003380861354)
  assert_cos_ulp!(-9, -0.9111302618846769)
  assert_cos_ulp!(1, 0.5403023058681398)
  assert_cos_ulp!(2, -0.4161468365471424)
  assert_cos_ulp!(3, -0.9899924966004454)
  assert_cos_ulp!(4, -0.6536436208636119)
  assert_cos_ulp!(5, 0.28366218546322625)
  assert_cos_ulp!(6, 0.9601702866503661)
  assert_cos_ulp!(7, 0.7539022543433046)
  assert_cos_ulp!(8, -0.14550003380861354)
  assert_cos_ulp!(9, -0.9111302618846769)
  assert_cos_ulp!(10, -0.8390715290764524)
  assert_cos_ulp!(100, 0.8623188722876839)
  assert_cos_ulp!(1000, 0.562379076290703)
  assert_cos_ulp!(10000, -0.9521553682590148)
  assert_cos_ulp!(27185679891.54321, 0.9449751254247084)
  assert_cos_ulp!(31415926535897.9323846, 0.9999992690101899)
  assert_cos_ulp!(516974365183497.332244, 0.11170737916572826)
  assert_cos_ulp!(2.5, -0.8011436155469337)
  assert_cos_ulp!(3.4, -0.9667981925794611)
  assert_cos_ulp!(5.3, 0.5543743361791608)
  assert_cos_ulp!(6.2, 0.9965420970232175)
  assert_cos_ulp!(7.1, 0.6845466664428066)
  assert_cos_ulp!(8.9, -0.8654352092411123)
  assert_cos_ulp!(9.800000000000001, -0.9304262721047533)
  assert_cos_ulp!(10.7, -0.2912892817213455)
  assert_cos_ulp!(101.6, 0.4809704291983219)
  assert_cos_ulp!(1.542, 0.028792347171058365)
  assert_cos_ulp!(2.846, -0.9566296662824658)
  assert_cos_ulp!(7.881, -0.02701507894652548)
  assert_cos_ulp!(3.772, -0.807787455278104)
  assert_cos_ulp!(-1.542, 0.028792347171058365)
  assert_cos_ulp!(-2.846, -0.9566296662824658)
  assert_cos_ulp!(-7.881, -0.02701507894652548)
  assert_cos_ulp!(-3.772, -0.807787455278104)
  assert_cos_ulp!(-15749436813364.748916, -0.9797265387199456)
  assert_cos_ulp!(0, 1)
  assert_cos_ulp!(-0, 1)
  assert_cos_ulp!(@double.not_a_number, @double.not_a_number)
  assert_cos_ulp!(@double.infinity, @double.not_a_number)
  assert_cos_ulp!(@double.neg_infinity, @double.not_a_number)
}