///| Compute hyperbolic sine function of a double-precision floating point number.
///
/// # Examples
///
/// ```moonbit
/// assert_eq!(sinh(-0.5), -0.5210953054937474)
/// assert_eq!(sinh(0.0), 0.0)
/// assert_eq!(sinh(0.5), 0.5210953054937474)
/// assert_eq!(sinh(1.0), 1.1752011936438014)
/// ```
///
/// # Special Cases
///
/// 1. sinh(NaN) = NaN
/// 2. sinh(+inf) = +inf
/// 3. sinh(-inf) = -inf
///
/// # Accuracy
///
/// 0 ulp.
pub fn sinh(x : Double) -> Double {
  if isnan(x) || isinf(x) {
    return x
  }
  let ix = __hi(x).reinterpret_as_int() & 0x7fffffff
  let abs_x = fabs(x)
  let shuge = 1.0e307
  let h = if x < 0.0 { -0.5 } else { 0.5 }
  if ix < 0x40360000 {
    if ix < 0x3e300000 {
      if shuge + x > 1.0 {
        return x
      }
    }
    let t = expm1(abs_x)
    if ix < 0x3ff00000 {
      return h * (2.0 * t - t * t / (t + 1.0))
    }
    return h * (t + t / (t + 1.0))
  }
  if ix < 0x40862E42 {
    return h * exp(abs_x)
  }
  if abs_x.reinterpret_as_uint64() < 0x408633ce8fb9f87d {
    let w = exp(0.5 * abs_x)
    let t = h * w
    return t * w
  }
  x * shuge
}

///|
test "sinh" {
  fn assert_sinh_ulp!(input, expect) {
    assert_ulp!(expect, sinh(input), SINH_MAX_ULP)
  }

  assert_sinh_ulp!(-0.8, -0.888105982187623)
  assert_sinh_ulp!(-0.7, -0.7585837018395334)
  assert_sinh_ulp!(-0.6, -0.6366535821482412)
  assert_sinh_ulp!(-0.5, -0.5210953054937474)
  assert_sinh_ulp!(-0.4, -0.4107523258028155)
  assert_sinh_ulp!(-0.3, -0.3045202934471426)
  assert_sinh_ulp!(-0.2, -0.20133600254109402)
  assert_sinh_ulp!(-0.1, -0.10016675001984403)
  assert_sinh_ulp!(-0, -0)
  assert_sinh_ulp!(-3.141592653589793, -11.548739357257748)
  assert_sinh_ulp!(-1.5707963267948966, -2.3012989023072947)
  assert_sinh_ulp!(-0.7853981633974483, -0.8686709614860095)
  assert_sinh_ulp!(0, 0)
  assert_sinh_ulp!(0.1, 0.10016675001984403)
  assert_sinh_ulp!(0.2, 0.20133600254109402)
  assert_sinh_ulp!(0.3, 0.3045202934471426)
  assert_sinh_ulp!(0.4, 0.4107523258028155)
  assert_sinh_ulp!(0.5, 0.5210953054937474)
  assert_sinh_ulp!(0.6, 0.6366535821482412)
  assert_sinh_ulp!(0.7, 0.7585837018395334)
  assert_sinh_ulp!(0.8, 0.888105982187623)
  assert_sinh_ulp!(0.9, 1.0265167257081753)
  assert_sinh_ulp!(1, 1.1752011936438014)
  assert_sinh_ulp!(3.141592653589793, 11.548739357257748)
  assert_sinh_ulp!(1.5707963267948966, 2.3012989023072947)
  assert_sinh_ulp!(0.7853981633974483, 0.8686709614860095)
  assert_sinh_ulp!(-1, -1.1752011936438014)
  assert_sinh_ulp!(-2, -3.626860407847019)
  assert_sinh_ulp!(-3, -10.017874927409903)
  assert_sinh_ulp!(-4, -27.28991719712775)
  assert_sinh_ulp!(-5, -74.20321057778875)
  assert_sinh_ulp!(-6, -201.71315737027922)
  assert_sinh_ulp!(-7, -548.3161232732465)
  assert_sinh_ulp!(-8, -1490.4788257895502)
  assert_sinh_ulp!(-9, -4051.54190208279)
  assert_sinh_ulp!(1, 1.1752011936438014)
  assert_sinh_ulp!(2, 3.626860407847019)
  assert_sinh_ulp!(3, 10.017874927409903)
  assert_sinh_ulp!(4, 27.28991719712775)
  assert_sinh_ulp!(5, 74.20321057778875)
  assert_sinh_ulp!(6, 201.71315737027922)
  assert_sinh_ulp!(7, 548.3161232732465)
  assert_sinh_ulp!(8, 1490.4788257895502)
  assert_sinh_ulp!(9, 4051.54190208279)
  assert_sinh_ulp!(10, 11013.232874703393)
  assert_sinh_ulp!(100, 13440585709080678000000000000000000000000000)
  assert_sinh_ulp!(1000, @double.infinity)
  assert_sinh_ulp!(10000, @double.infinity)
  assert_sinh_ulp!(2.5, 6.0502044810397875)
  assert_sinh_ulp!(3.4, 14.965363388718343)
  assert_sinh_ulp!(5.3, 100.16590919044238)
  assert_sinh_ulp!(6.2, 246.37350583131)
  assert_sinh_ulp!(7.1, 605.9831246938265)
  assert_sinh_ulp!(8.9, 3665.986701383534)
  assert_sinh_ulp!(9.8, 9016.872436188462)
  assert_sinh_ulp!(10.7, 22177.92755387645)
  assert_sinh_ulp!(101.6, 66571656819937820000000000000000000000000000)
  assert_sinh_ulp!(1.542, 2.2299880096162905)
  assert_sinh_ulp!(2.846, 8.580346334855408)
  assert_sinh_ulp!(7.881, 1323.2586883921372)
  assert_sinh_ulp!(3.772, 21.72195288914397)
  assert_sinh_ulp!(-1.542, -2.2299880096162905)
  assert_sinh_ulp!(-2.846, -8.580346334855408)
  assert_sinh_ulp!(-7.881, -1323.2586883921372)
  assert_sinh_ulp!(-3.772, -21.72195288914397)
  assert_sinh_ulp!(-1, -1.1752011936438014)
  assert_sinh_ulp!(0, 0)
  assert_sinh_ulp!(-0, -0)
  assert_sinh_ulp!(@double.not_a_number, @double.not_a_number)
  assert_sinh_ulp!(@double.infinity, @double.infinity)
  assert_sinh_ulp!(@double.neg_infinity, @double.neg_infinity)
}