///|
priv struct ExpSig16 {
  exp : Int32
  sig : UInt16
}

///|
priv struct ExpSig32 {
  exp : Int32
  sig : UInt32
}

///|
priv struct ExpSig64 {
  exp : Int32
  sig : UInt64
}

///|
fn round_pack_increment(
  rounding : RoundingMode,
  sign : Bool,
  half : UInt32,
) -> UInt32 {
  match rounding {
    NearEven | NearMaxMag => half
    Min if sign => (half << 1) - 1U
    Max if !sign => (half << 1) - 1U
    _ => 0U
  }
}

///|
fn round_pack_increment64(
  rounding : RoundingMode,
  sign : Bool,
  half : UInt64,
) -> UInt64 {
  match rounding {
    NearEven | NearMaxMag => half
    Min if sign => (half << 1) - 1UL
    Max if !sign => (half << 1) - 1UL
    _ => 0UL
  }
}

///|
fn round_pack_raise_overflow_inexact(context : Context) -> Context {
  context.raise_flags(
    ExceptionFlags::overflow().merge(ExceptionFlags::inexact()),
  )
}

///|
/// Ports `softfloat_normSubnormalF16Sig` from `s_normSubnormalF16Sig.c`.
fn norm_subnormal_f16_sig(sig : UInt16) -> ExpSig16 {
  let shift_dist = count_leading_zeros16(sig) - 5
  { exp: 1 - shift_dist, sig: sig << shift_dist, }
}

///|
/// Ports `softfloat_normSubnormalF32Sig` from `s_normSubnormalF32Sig.c`.
fn norm_subnormal_f32_sig(sig : UInt32) -> ExpSig32 {
  let shift_dist = count_leading_zeros32(sig) - 8
  { exp: 1 - shift_dist, sig: sig << shift_dist, }
}

///|
/// Ports `softfloat_normSubnormalF64Sig` from `s_normSubnormalF64Sig.c`.
fn norm_subnormal_f64_sig(sig : UInt64) -> ExpSig64 {
  let shift_dist = count_leading_zeros64(sig) - 11
  { exp: 1 - shift_dist, sig: sig << shift_dist, }
}

///|
/// Ports `softfloat_roundPackToF16` from `s_roundPackToF16.c`.
fn round_pack_to_f16(
  context : Context,
  sign : Bool,
  exp : Int32,
  sig : UInt32,
) -> SoftResult[F16] {
  let round_increment = round_pack_increment(context.rounding, sign, 0x8U)
  let mut exp = exp
  let mut sig = sig
  let mut round_bits = sig & 0xFU
  let mut context = context
  if exp < 0 || exp >= 0x1D {
    if exp < 0 {
      let is_tiny = context.tininess == BeforeRounding ||
        exp < -1 ||
        sig + round_increment < 0x8000U
      sig = shift_right_jam32(sig, -exp)
      exp = 0
      round_bits = sig & 0xFU
      if is_tiny && round_bits != 0U {
        context = context.raise_flags(ExceptionFlags::underflow())
      }
    } else if exp > 0x1D || sig + round_increment >= 0x8000U {
      let inf = pack_to_f16_ui(sign, 0x1F, 0)
      let value = if round_increment == 0U { inf - 1 } else { inf }
      return {
        value: F16::from_bits(value),
        context: round_pack_raise_overflow_inexact(context),
      }
    }
  }
  sig = (sig + round_increment) >> 4
  if round_bits != 0U {
    context = context.raise_flags(ExceptionFlags::inexact())
    if context.rounding == Odd {
      sig = sig | 1U
      return {
        value: F16::from_bits(pack_to_f16_ui(sign, exp, sig.to_uint16())),
        context,
      }
    }
  }
  if round_bits == 0x8U && context.rounding == NearEven {
    sig = sig & 0xFFFFFFFEU
  }
  if sig == 0U {
    exp = 0
  }
  {
    value: F16::from_bits(pack_to_f16_ui(sign, exp, sig.to_uint16())),
    context,
  }
}

///|
/// Ports `softfloat_roundPackToBF16` from `s_roundPackToBF16.c`.
fn round_pack_to_bf16(
  context : Context,
  sign : Bool,
  exp : Int32,
  sig : UInt32,
) -> SoftResult[BF16] {
  let round_increment = round_pack_increment(context.rounding, sign, 0x40U)
  let mut exp = exp
  let mut sig = sig
  let mut round_bits = sig & 0x7FU
  let mut context = context
  if exp < 0 || exp >= 0xFD {
    if exp < 0 {
      let is_tiny = context.tininess == BeforeRounding ||
        exp < -1 ||
        sig + round_increment < 0x8000U
      sig = shift_right_jam32(sig, -exp)
      exp = 0
      round_bits = sig & 0x7FU
      if is_tiny && round_bits != 0U {
        context = context.raise_flags(ExceptionFlags::underflow())
      }
    } else if exp > 0xFD || sig + round_increment >= 0x8000U {
      let inf = pack_to_bf16_ui(sign, 0xFF, 0)
      let value = if round_increment == 0U { inf - 1 } else { inf }
      return {
        value: BF16::from_bits(value),
        context: round_pack_raise_overflow_inexact(context),
      }
    }
  }
  sig = (sig + round_increment) >> 7
  if round_bits != 0U {
    context = context.raise_flags(ExceptionFlags::inexact())
    if context.rounding == Odd {
      sig = sig | 1U
      return {
        value: BF16::from_bits(pack_to_bf16_ui(sign, exp, sig.to_uint16())),
        context,
      }
    }
  }
  if round_bits == 0x40U && context.rounding == NearEven {
    sig = sig & 0xFFFFFFFEU
  }
  if sig == 0U {
    exp = 0
  }
  {
    value: BF16::from_bits(pack_to_bf16_ui(sign, exp, sig.to_uint16())),
    context,
  }
}

///|
/// Ports `softfloat_roundPackToF32` from `s_roundPackToF32.c`.
fn round_pack_to_f32(
  context : Context,
  sign : Bool,
  exp : Int32,
  sig : UInt32,
) -> SoftResult[F32] {
  let round_increment = round_pack_increment(context.rounding, sign, 0x40U)
  let mut exp = exp
  let mut sig = sig
  let mut round_bits = sig & 0x7FU
  let mut context = context
  if exp < 0 || exp >= 0xFD {
    if exp < 0 {
      let is_tiny = context.tininess == BeforeRounding ||
        exp < -1 ||
        sig + round_increment < 0x80000000U
      sig = shift_right_jam32(sig, -exp)
      exp = 0
      round_bits = sig & 0x7FU
      if is_tiny && round_bits != 0U {
        context = context.raise_flags(ExceptionFlags::underflow())
      }
    } else if exp > 0xFD || sig + round_increment >= 0x80000000U {
      let inf = pack_to_f32_ui(sign, 0xFF, 0U)
      let value = if round_increment == 0U { inf - 1U } else { inf }
      return {
        value: F32::from_bits(value),
        context: round_pack_raise_overflow_inexact(context),
      }
    }
  }
  sig = (sig + round_increment) >> 7
  if round_bits != 0U {
    context = context.raise_flags(ExceptionFlags::inexact())
    if context.rounding == Odd {
      sig = sig | 1U
      return { value: F32::from_bits(pack_to_f32_ui(sign, exp, sig)), context, }
    }
  }
  if round_bits == 0x40U && context.rounding == NearEven {
    sig = sig & 0xFFFFFFFEU
  }
  if sig == 0U {
    exp = 0
  }
  { value: F32::from_bits(pack_to_f32_ui(sign, exp, sig)), context, }
}

///|
/// Ports `softfloat_roundPackToF64` from `s_roundPackToF64.c`.
fn round_pack_to_f64(
  context : Context,
  sign : Bool,
  exp : Int32,
  sig : UInt64,
) -> SoftResult[F64] {
  let round_increment = round_pack_increment64(context.rounding, sign, 0x200UL)
  let mut exp = exp
  let mut sig = sig
  let mut round_bits = sig & 0x3FFUL
  let mut context = context
  if exp < 0 || exp >= 0x7FD {
    if exp < 0 {
      let is_tiny = context.tininess == BeforeRounding ||
        exp < -1 ||
        sig + round_increment < 0x8000000000000000UL
      sig = shift_right_jam64(sig, -exp)
      exp = 0
      round_bits = sig & 0x3FFUL
      if is_tiny && round_bits != 0UL {
        context = context.raise_flags(ExceptionFlags::underflow())
      }
    } else if exp > 0x7FD || sig + round_increment >= 0x8000000000000000UL {
      let inf = pack_to_f64_ui(sign, 0x7FF, 0UL)
      let value = if round_increment == 0UL { inf - 1UL } else { inf }
      return {
        value: F64::from_bits(value),
        context: round_pack_raise_overflow_inexact(context),
      }
    }
  }
  sig = (sig + round_increment) >> 10
  if round_bits != 0UL {
    context = context.raise_flags(ExceptionFlags::inexact())
    if context.rounding == Odd {
      sig = sig | 1UL
      return { value: F64::from_bits(pack_to_f64_ui(sign, exp, sig)), context, }
    }
  }
  if round_bits == 0x200UL && context.rounding == NearEven {
    sig = sig & 0xFFFFFFFFFFFFFFFEUL
  }
  if sig == 0UL {
    exp = 0
  }
  { value: F64::from_bits(pack_to_f64_ui(sign, exp, sig)), context, }
}

///|
/// Ports `softfloat_normRoundPackToF16` from `s_normRoundPackToF16.c`.
fn norm_round_pack_to_f16(
  context : Context,
  sign : Bool,
  exp : Int32,
  sig : UInt32,
) -> SoftResult[F16] {
  let shift_dist = count_leading_zeros16(sig.to_uint16()) - 1
  let exp = exp - shift_dist
  if shift_dist >= 4 && exp >= 0 && exp < 0x1D {
    let packed_sig = if sig == 0U { 0U } else { sig << (shift_dist - 4) }
    let packed_exp = if sig == 0U { 0 } else { exp }
    {
      value: F16::from_bits(
        pack_to_f16_ui(sign, packed_exp, packed_sig.to_uint16()),
      ),
      context,
    }
  } else {
    round_pack_to_f16(context, sign, exp, sig << shift_dist)
  }
}

///|
/// Ports `softfloat_normRoundPackToF32` from `s_normRoundPackToF32.c`.
fn norm_round_pack_to_f32(
  context : Context,
  sign : Bool,
  exp : Int32,
  sig : UInt32,
) -> SoftResult[F32] {
  let shift_dist = count_leading_zeros32(sig) - 1
  let exp = exp - shift_dist
  if shift_dist >= 7 && exp >= 0 && exp < 0xFD {
    let packed_sig = if sig == 0U { 0U } else { sig << (shift_dist - 7) }
    let packed_exp = if sig == 0U { 0 } else { exp }
    {
      value: F32::from_bits(pack_to_f32_ui(sign, packed_exp, packed_sig)),
      context,
    }
  } else {
    round_pack_to_f32(context, sign, exp, sig << shift_dist)
  }
}

///|
/// Ports `softfloat_normRoundPackToF64` from `s_normRoundPackToF64.c`.
fn norm_round_pack_to_f64(
  context : Context,
  sign : Bool,
  exp : Int32,
  sig : UInt64,
) -> SoftResult[F64] {
  let shift_dist = count_leading_zeros64(sig) - 1
  let exp = exp - shift_dist
  if shift_dist >= 10 && exp >= 0 && exp < 0x7FD {
    let packed_sig = if sig == 0UL { 0UL } else { sig << (shift_dist - 10) }
    let packed_exp = if sig == 0UL { 0 } else { exp }
    {
      value: F64::from_bits(pack_to_f64_ui(sign, packed_exp, packed_sig)),
      context,
    }
  } else {
    round_pack_to_f64(context, sign, exp, sig << shift_dist)
  }
}