///|
/// Compute hyperbolic sine function of a double-precision floating point number.
///
/// # Examples
///
/// ```moonbit nocheck
/// 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) raise {
    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)
}