// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// Half-precision samples, in and out.
//
// A half is a float with five bits of exponent and ten of mantissa: the same
// shape as a float, narrow enough that a picture of light fits in two bytes a
// sample. OpenEXR stores its pictures this way by default and BC6H stores
// nothing else, so the arithmetic lives beside the pixel formats rather than in
// either of them, and a pixel that is four floats can be built from a block of
// sixteen bytes without a second copy of it.
///|
/// A half-precision sample as a float.
pub fn half_to_float(bits : Int32) -> Float {
let sign = (bits & 0x8000) << 16
let exponent = (bits >> 10) & 0x1F
let mantissa = bits & 0x3FF
let word = if exponent == 0 {
if mantissa == 0 {
sign
} else {
let mut m = mantissa
let mut shift = 0
while (m & 0x400) == 0 {
m = m << 1
shift = shift + 1
}
let rest = m & 0x3FF
sign | ((127 - 14 - shift) << 23) | (rest << 13)
}
} else if exponent == 31 {
sign | 0x7F800000 | (mantissa << 13)
} else {
sign | ((exponent - 15 + 127) << 23) | (mantissa << 13)
}
Float::reinterpret_from_uint(word.reinterpret_as_uint())
}
///|
/// A float as a half-precision sample, rounded to nearest.
pub fn float_to_half(value : Float) -> Int32 {
let word = value.reinterpret_as_uint().reinterpret_as_int()
let sign = (word >> 16) & 0x8000
let exponent = (word >> 23) & 0xFF
let mantissa = word & 0x7FFFFF
if exponent == 0xFF {
return sign | 0x7C00 | (if mantissa != 0 { 0x200 } else { 0 })
}
let shifted = exponent - 127 + 15
if shifted >= 0x1F {
return sign | 0x7C00
}
if shifted <= 0 {
if shifted < -10 {
return sign
}
let mant = (mantissa | 0x800000) >> (14 - shifted)
let rounded = (mant + 1) >> 1
return sign | rounded
}
let mant = (mantissa + 0x1000) >> 13
if mant >= 0x400 {
sign | ((shifted + 1) << 10)
} else {
sign | (shifted << 10) | mant
}
}