///|
fn ldexp2(value : Double, exp : Int) -> Double {
  let mut v = value
  let mut n = exp
  while n > 0 {
    v = v * 2.0
    n -= 1
  }
  while n < 0 {
    v = v / 2.0
    n += 1
  }
  v
}

///|
fn is_nan(value : Double) -> Bool {
  value != value
}

///|
fn is_inf(value : Double) -> Bool {
  value != 0.0 && value * 0.5 == value
}

///|
pub fn f64_from_be_bytes(
  b0 : Int,
  b1 : Int,
  b2 : Int,
  b3 : Int,
  b4 : Int,
  b5 : Int,
  b6 : Int,
  b7 : Int,
) -> Double {
  let hi = (b0.to_int64() << 56) |
    (b1.to_int64() << 48) |
    (b2.to_int64() << 40) |
    (b3.to_int64() << 32)
  let lo = (b4.to_int64() << 24) |
    (b5.to_int64() << 16) |
    (b6.to_int64() << 8) |
    b7.to_int64()
  let bits = hi | lo
  f64_from_bits(bits)
}

///|
pub fn f64_from_be4(b0 : Int, b1 : Int, b2 : Int, b3 : Int) -> Double {
  let bits32 = (b0.reinterpret_as_uint() << 24) |
    (b1.reinterpret_as_uint() << 16) |
    (b2.reinterpret_as_uint() << 8) |
    b3.reinterpret_as_uint()
  let sign = bits32 >> 31 != 0U
  let exp = ((bits32 >> 23) & 255U).reinterpret_as_int()
  let frac = (bits32 & 0x7FFFFFU).reinterpret_as_int()
  if exp == 0 {
    if frac == 0 {
      if sign {
        -0.0
      } else {
        0.0
      }
    } else {
      let mag = ldexp2(frac.to_double() / 8388608.0, -126)
      if sign {
        0.0 - mag
      } else {
        mag
      }
    }
  } else if exp == 255 {
    let inf = 1.0 / 0.0
    if frac != 0 {
      inf - inf
    } else if sign {
      0.0 - inf
    } else {
      inf
    }
  } else {
    let mag = ldexp2(1.0 + frac.to_double() / 8388608.0, exp - 127)
    if sign {
      0.0 - mag
    } else {
      mag
    }
  }
}

///|
fn f64_from_bits(bits : Int64) -> Double {
  let sign = bits < 0L
  let exp = ((bits >> 52) & 2047L).to_int()
  let frac = bits & 4503599627370495L
  if exp == 0 {
    if frac == 0L {
      if sign {
        -0.0
      } else {
        0.0
      }
    } else {
      let mag = ldexp2(frac.to_double() / 4503599627370496.0, -1022)
      if sign {
        0.0 - mag
      } else {
        mag
      }
    }
  } else if exp == 2047 {
    let inf = 1.0 / 0.0
    if frac != 0L {
      inf - inf
    } else if sign {
      0.0 - inf
    } else {
      inf
    }
  } else {
    let mag = ldexp2(1.0 + frac.to_double() / 4503599627370496.0, exp - 1023)
    if sign {
      0.0 - mag
    } else {
      mag
    }
  }
}

///|
pub fn f64_to_be_bytes(value : Double) -> Bytes {
  let bits = f64_to_bits(value)
  let out : Array[Byte] = []
  let mut shift = 56
  while shift >= 0 {
    let part = ((bits >> shift) & 255L).to_int()
    out.push(part.to_byte())
    shift -= 8
  }
  array_to_bytes(out)
}

///|
fn f64_to_bits(value : Double) -> Int64 {
  if is_nan(value) {
    0x7FF8000000000000L
  } else if is_inf(value) {
    if value < 0.0 {
      0xFFF0000000000000L
    } else {
      0x7FF0000000000000L
    }
  } else if value == 0.0 {
    if 1.0 / value < 0.0 {
      0x8000000000000000L
    } else {
      0L
    }
  } else {
    let mut sign = 0L
    let mut mag = value
    if mag < 0.0 {
      sign = 1L << 63
      mag = 0.0 - mag
    }
    let mut exp = 0
    while mag >= 2.0 && exp < 1023 {
      mag = mag / 2.0
      exp += 1
    }
    while mag < 1.0 && exp > -1022 {
      mag = mag * 2.0
      exp -= 1
    }
    if mag < 1.0 {
      let frac = (mag * 4503599627370496.0).to_int64()
      sign | (frac & 4503599627370495L)
    } else {
      let biased = exp + 1023
      let frac = ((mag - 1.0) * 4503599627370496.0).to_int64()
      sign | (biased.to_int64() << 52) | (frac & 4503599627370495L)
    }
  }
}