// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// The half is fixed at sixteen bits, so it has no double-precision twin: the
// `f64` package is the same library at `Double`, and a half is a half in
// either. This template is emitted for single precision only, which is how it
// stays out of `src/f64/`.
///|
/// A 16-bit IEEE 754 binary16 value: the half precision a texture, a packed
/// vertex or a GPU buffer carries when thirty-two bits are more than the data
/// needs and sixteen are enough.
///
/// The value is the sixteen bits themselves, so a half read from a buffer is
/// `from_bits` and one written back is `to_bits`. Arithmetic runs at `Float`,
/// the precision the rest of this library works in: a half is a storage format,
/// not a second arithmetic. Conversion rounds to nearest with ties to even, as
/// the standard and the graphics formats do, and the special values — signed
/// zeros, the two infinities, a quiet NaN and the subnormals — survive the round
/// trip.
///
/// Fixed at sixteen bits, this type has no double-precision counterpart; it
/// lives only in the single-precision package.
#derive(Eq)
pub struct Half {
/// The sixteen bits, sign, exponent and fraction, in that order.
bits : UInt16
}
///|
pub extend Half with Eq::{equal, not_equal}
///|
/// The half made of the sixteen raw bits, as a buffer holds it.
pub fn Half::from_bits(bits : UInt16) -> Half {
{ bits, }
}
///|
/// The sixteen raw bits, in the order a buffer expects.
pub fn Half::to_bits(self : Half) -> UInt16 {
self.bits
}
///|
/// The 16-bit encoding of `value`, rounded to the nearest representable half,
/// ties to even.
///
/// Values beyond the largest finite half become an infinity of the same sign;
/// values below the smallest subnormal become a signed zero; a NaN stays a NaN.
pub fn Half::from_float(value : Float) -> Half {
let bits : Int32 = value.reinterpret_as_int()
let one : Int32 = 1
let sign : Int32 = (bits >> 16) & 0x8000
let exponent : Int32 = (bits >> 23) & 0xFF
let mantissa : Int32 = bits & 0x7FFFFF
// The infinity and the NaNs: keep the sign, and a NaN's payload with it.
if exponent == 0xFF {
if mantissa == 0 {
return make(sign | 0x7C00)
}
return make(sign | 0x7E00 | (mantissa >> 13))
}
// The exponent the value would carry as a half, biased by fifteen.
let e : Int32 = exponent - 112
if e >= 0x1F {
// Past the largest finite half; the nearest is an infinity.
return make(sign | 0x7C00)
}
if e >= 1 {
// A normal half: ten bits of mantissa, the thirteenth bit below them is the
// round bit and everything under it is the sticky bit.
let kept : Int32 = mantissa >> 13
let discarded : Int32 = mantissa & 0x1FFF
let mut half_mantissa : Int32 = kept
if discarded > 0x1000 || (discarded == 0x1000 && (kept & one) != 0) {
half_mantissa = half_mantissa + one
}
let mut field : Int32 = e
if half_mantissa >= 0x400 {
// Rounding the mantissa overflowed it into the exponent.
half_mantissa = 0
field = field + one
}
if field >= 0x1F {
return make(sign | 0x7C00)
}
return make(sign | (field << 10) | half_mantissa)
}
// Subnormal or zero. Below the rounding reach of the smallest subnormal, the
// nearest value is a signed zero.
if e < -10 {
return make(sign)
}
let shift : Int32 = 14 - e
let numerator : Int32 = mantissa | 0x800000
let mut half_mantissa : Int32 = numerator >> shift
let remainder : Int32 = numerator & ((one << shift) - one)
let halfway : Int32 = one << (shift - one)
if remainder > halfway || (remainder == halfway && (half_mantissa & one) != 0) {
half_mantissa = half_mantissa + one
}
if half_mantissa >= 0x400 {
// Rounded up to the smallest normal.
return make(sign | 0x0400)
}
make(sign | half_mantissa)
}
///|
/// The `Float` the half stands for.
///
/// Exact: every half is a `Float`, though not every `Float` is a half.
pub fn Half::to_float(self : Half) -> Float {
let bits : Int32 = self.bits.to_int()
let one : Int32 = 1
let sign : Int32 = (bits & 0x8000) << 16
let exponent : Int32 = (bits >> 10) & 0x1F
let mantissa : Int32 = bits & 0x3FF
if exponent == 0 {
if mantissa == 0 {
// A signed zero.
return Float::reinterpret_from_int(sign)
}
// A subnormal: normalise the ten-bit mantissa into the wider fraction.
let mut position : Int32 = 9
while (mantissa & (one << position)) == 0 {
position = position - one
}
let implicit : Int32 = one << position
let fraction : Int32 = (mantissa - implicit) << (23 - position)
let field : Int32 = 103 + position
return Float::reinterpret_from_int(sign | (field << 23) | fraction)
}
if exponent == 0x1F {
if mantissa == 0 {
return Float::reinterpret_from_int(sign | 0x7F800000)
}
return Float::reinterpret_from_int(sign | 0x7FC00000)
}
let field : Int32 = exponent + 112
let fraction : Int32 = mantissa << 13
Float::reinterpret_from_int(sign | (field << 23) | fraction)
}
///|
/// Whether the half is neither an infinity nor a NaN.
pub fn Half::is_finite(self : Half) -> Bool {
(self.bits.to_int() & 0x7C00) != 0x7C00
}
///|
/// Whether the half is a NaN.
pub fn Half::is_nan(self : Half) -> Bool {
let bits : Int32 = self.bits.to_int()
(bits & 0x7C00) == 0x7C00 && (bits & 0x3FF) != 0
}
///|
/// Whether the half is an infinity, of either sign.
pub fn Half::is_inf(self : Half) -> Bool {
let bits : Int32 = self.bits.to_int()
(bits & 0x7C00) == 0x7C00 && (bits & 0x3FF) == 0
}
///|
/// Whether the half is a normal number: not zero, not subnormal, not infinite.
pub fn Half::is_normal(self : Half) -> Bool {
let exponent : Int32 = (self.bits.to_int() >> 10) & 0x1F
exponent != 0 && exponent != 0x1F
}
///|
/// Whether the half is a subnormal: non-zero, smaller than the smallest normal.
pub fn Half::is_denormal(self : Half) -> Bool {
let bits : Int32 = self.bits.to_int()
(bits & 0x7C00) == 0 && (bits & 0x3FF) != 0
}
///|
/// Positive infinity.
pub fn Half::pos_infinity() -> Half {
make(0x7C00)
}
///|
/// Negative infinity.
pub fn Half::neg_infinity() -> Half {
make(0xFC00)
}
///|
/// A quiet NaN.
pub fn Half::nan() -> Half {
make(0x7E00)
}
///|
/// Positive zero.
pub fn Half::zero() -> Half {
make(0x0000)
}
///|
/// One.
pub fn Half::one() -> Half {
make(0x3C00)
}
///|
/// The smallest positive normal, `2^-14`.
pub fn Half::min() -> Half {
make(0x0400)
}
///|
/// The largest finite half, `65504`.
pub fn Half::max() -> Half {
make(0x7BFF)
}
///|
/// The most negative finite half, `-65504`.
pub fn Half::lowest() -> Half {
make(0xFBFF)
}
///|
/// The smallest number that, added to one, gives a half different from one:
/// `2^-10`.
pub fn Half::epsilon() -> Half {
make(0x1400)
}
///|
/// The smallest positive subnormal, `2^-24`.
pub fn Half::denorm_min() -> Half {
make(0x0001)
}
///|
/// Whether the two halves hold the same value to within `eps`, compared at
/// `Float` precision.
pub fn Half::approx(self : Half, other : Half, eps? : Float) -> Bool {
approx(self.to_float(), other.to_float(), eps?)
}
///|
/// The half as the number it holds; a NaN or an infinity prints as it does at
/// `Float`.
pub impl Show for Half with fn output(self, logger) {
logger.write_string("Half(\{self.to_float()})")
}
///|
pub extend Half with Show::{output, to_string}
///|
/// The bits and the value, so a debugger shows the encoding behind the number.
pub impl Debug for Half with fn to_repr(self) {
@debug.Repr::ctor("Half", [
(Some("bits"), @debug.Repr::integer(self.bits.to_string(radix=16))),
(Some("value"), @debug.Repr::float(self.to_float())),
])
}
///|
pub extend Half with Debug::{to_repr}
///|
/// The half from the sixteen bits as an integer, for the constants above.
fn make(bits : Int32) -> Half {
{ bits: bits.to_uint16(), }
}