use core::cmp::Ordering;
use core::fmt;
use core::iter::Sum;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Bf16(u16);
impl Bf16 {
pub const ZERO: Bf16 = Bf16(0x0000);
pub const ONE: Bf16 = Bf16(0x3f80);
pub const NEG_ONE: Bf16 = Bf16(0xbf80);
pub const INFINITY: Bf16 = Bf16(0x7f80);
pub const NEG_INFINITY: Bf16 = Bf16(0xff80);
pub const NAN: Bf16 = Bf16(0x7fc0);
pub const MAX: Bf16 = Bf16(0x7f7f);
pub const MIN: Bf16 = Bf16(0xff7f);
pub const MIN_POSITIVE: Bf16 = Bf16(0x0080);
#[inline]
pub const fn from_bits(bits: u16) -> Self {
Bf16(bits)
}
#[inline]
pub const fn to_bits(self) -> u16 {
self.0
}
#[inline]
pub fn from_f32(value: f32) -> Self {
let bits = value.to_bits();
if value.is_nan() {
return Bf16((bits >> 16) as u16 | 0x0040);
}
let rounding_bias = 0x7fff + ((bits >> 16) & 1);
let rounded = bits.wrapping_add(rounding_bias);
Bf16((rounded >> 16) as u16)
}
#[inline]
pub fn to_f32(self) -> f32 {
f32::from_bits((self.0 as u32) << 16)
}
#[inline]
pub fn is_nan(self) -> bool {
(self.0 & 0x7f80) == 0x7f80 && (self.0 & 0x007f) != 0
}
#[inline]
pub fn is_infinite(self) -> bool {
(self.0 & 0x7fff) == 0x7f80
}
#[inline]
pub fn is_finite(self) -> bool {
(self.0 & 0x7f80) != 0x7f80
}
#[inline]
pub fn is_zero(self) -> bool {
(self.0 & 0x7fff) == 0
}
#[inline]
pub fn is_sign_negative(self) -> bool {
(self.0 & 0x8000) != 0
}
#[inline]
pub fn abs(self) -> Self {
Bf16(self.0 & 0x7fff)
}
#[inline]
pub fn copysign(self, sign: Self) -> Self {
Bf16((self.0 & 0x7fff) | (sign.0 & 0x8000))
}
#[inline]
pub fn max(self, other: Self) -> Self {
if self.is_nan() {
return other;
}
if other.is_nan() {
return self;
}
if self.to_f32() >= other.to_f32() {
self
} else {
other
}
}
#[inline]
pub fn min(self, other: Self) -> Self {
if self.is_nan() {
return other;
}
if other.is_nan() {
return self;
}
if self.to_f32() <= other.to_f32() {
self
} else {
other
}
}
#[inline]
pub fn clamp(self, lo: Self, hi: Self) -> Self {
self.max(lo).min(hi)
}
#[inline]
pub fn recip(self) -> Self {
Bf16::from_f32(self.to_f32().recip())
}
#[inline]
pub fn mul_add(self, a: Self, b: Self) -> Self {
Bf16::from_f32(self.to_f32().mul_add(a.to_f32(), b.to_f32()))
}
#[inline]
pub fn signum(self) -> Self {
if self.is_nan() {
Bf16::NAN
} else if self.is_zero() {
self
} else if self.is_sign_negative() {
Bf16::NEG_ONE
} else {
Bf16::ONE
}
}
#[inline]
pub fn is_normal(self) -> bool {
let exp = self.0 & 0x7f80;
exp != 0 && exp != 0x7f80
}
#[inline]
pub fn to_le_bytes(self) -> [u8; 2] {
self.0.to_le_bytes()
}
#[inline]
pub fn from_le_bytes(bytes: [u8; 2]) -> Self {
Bf16(u16::from_le_bytes(bytes))
}
}
impl From<f32> for Bf16 {
#[inline]
fn from(value: f32) -> Self {
Bf16::from_f32(value)
}
}
impl From<Bf16> for f32 {
#[inline]
fn from(value: Bf16) -> Self {
value.to_f32()
}
}
impl From<f64> for Bf16 {
#[inline]
fn from(value: f64) -> Self {
Bf16::from_f32(value as f32)
}
}
impl From<Bf16> for f64 {
#[inline]
fn from(value: Bf16) -> Self {
value.to_f32() as f64
}
}
impl From<i8> for Bf16 {
#[inline]
fn from(value: i8) -> Self {
Bf16::from_f32(value as f32)
}
}
impl From<u8> for Bf16 {
#[inline]
fn from(value: u8) -> Self {
Bf16::from_f32(value as f32)
}
}
impl From<i16> for Bf16 {
#[inline]
fn from(value: i16) -> Self {
Bf16::from_f32(value as f32)
}
}
impl Default for Bf16 {
#[inline]
fn default() -> Self {
Bf16::ZERO
}
}
impl PartialEq for Bf16 {
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.is_nan() || other.is_nan() {
return false;
}
if self.is_zero() && other.is_zero() {
return true;
}
self.0 == other.0
}
}
impl PartialOrd for Bf16 {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.to_f32().partial_cmp(&other.to_f32())
}
}
impl Neg for Bf16 {
type Output = Bf16;
#[inline]
fn neg(self) -> Self {
Bf16(self.0 ^ 0x8000)
}
}
impl Add for Bf16 {
type Output = Bf16;
#[inline]
fn add(self, rhs: Self) -> Self {
Bf16::from_f32(self.to_f32() + rhs.to_f32())
}
}
impl Sub for Bf16 {
type Output = Bf16;
#[inline]
fn sub(self, rhs: Self) -> Self {
Bf16::from_f32(self.to_f32() - rhs.to_f32())
}
}
impl Mul for Bf16 {
type Output = Bf16;
#[inline]
fn mul(self, rhs: Self) -> Self {
Bf16::from_f32(self.to_f32() * rhs.to_f32())
}
}
impl Div for Bf16 {
type Output = Bf16;
#[inline]
fn div(self, rhs: Self) -> Self {
Bf16::from_f32(self.to_f32() / rhs.to_f32())
}
}
impl AddAssign for Bf16 {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl SubAssign for Bf16 {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl MulAssign for Bf16 {
#[inline]
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl DivAssign for Bf16 {
#[inline]
fn div_assign(&mut self, rhs: Self) {
*self = *self / rhs;
}
}
impl Sum for Bf16 {
#[inline]
fn sum<I: Iterator<Item = Bf16>>(iter: I) -> Self {
Bf16::from_f32(iter.map(|x| x.to_f32()).sum())
}
}
impl fmt::Debug for Bf16 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Bf16({} /* 0x{:04x} */)", self.to_f32(), self.0)
}
}
impl fmt::Display for Bf16 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.to_f32(), f)
}
}