#![no_std]
#[cfg(feature = "num-traits")]
mod num_traits;
#[cfg(feature = "rand_distr")]
mod rand_distr;
use core::{
cmp::Ordering,
f64,
fmt::{self, Debug, Display, LowerExp, LowerHex, UpperExp, UpperHex},
mem,
num::{FpCategory, ParseFloatError},
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
str::FromStr,
};
use half::f16;
#[cfg(feature = "bytemuck")]
use bytemuck::{Pod, Zeroable};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "zerocopy")]
use zerocopy::{AsBytes, FromBytes};
#[derive(Clone, Copy, PartialEq)]
enum Kind {
E4M3,
E5M2,
}
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Default)]
enum SaturationType {
NoSat,
#[default]
SatFinite,
}
const fn convert_to_fp8(x: f64, saturate: SaturationType, fp8_interpretation: Kind) -> u8 {
#[allow(unknown_lints, unnecessary_transmutes)]
let xbits: u64 = unsafe { mem::transmute::<f64, u64>(x) };
let (
fp8_maxnorm,
fp8_mantissa_mask,
fp8_exp_bias,
fp8_significand_bits,
fp8_mindenorm_o2,
fp8_overflow_threshold,
fp8_minnorm,
) = match fp8_interpretation {
Kind::E4M3 => (
0x7E_u8,
0x7_u8,
7_u16,
4_u64,
0x3F50000000000000_u64,
0x407D000000000000_u64,
0x3F90000000000000_u64,
),
Kind::E5M2 => (
0x7B_u8,
0x3_u8,
15_u16,
3_u64,
0x3EE0000000000000_u64,
0x40EE000000000000_u64 - 1,
0x3F10000000000000_u64,
),
};
const DP_INF_BITS: u64 = 0x7FF0000000000000;
let fp8_dp_half_ulp: u64 = 1 << (53 - fp8_significand_bits - 1);
let sign: u8 = ((xbits >> 63) << 7) as u8;
let exp: u8 = ((((xbits >> 52) as u16) & 0x7FF)
.wrapping_sub(1023)
.wrapping_add(fp8_exp_bias)) as u8;
let mantissa: u8 = ((xbits >> (53 - fp8_significand_bits)) & (fp8_mantissa_mask as u64)) as u8;
let absx: u64 = xbits & 0x7FFFFFFFFFFFFFFF;
let res = if absx <= fp8_mindenorm_o2 {
0
} else if absx > DP_INF_BITS {
match fp8_interpretation {
Kind::E4M3 => 0x7F,
Kind::E5M2 => 0x7E | mantissa,
}
} else if absx > fp8_overflow_threshold {
match saturate {
SaturationType::SatFinite => fp8_maxnorm,
SaturationType::NoSat => match fp8_interpretation {
Kind::E4M3 => 0x7F, Kind::E5M2 => 0x7C, },
}
} else if absx >= fp8_minnorm {
let mut res = (exp << (fp8_significand_bits - 1)) | mantissa;
let round = xbits & ((fp8_dp_half_ulp << 1) - 1);
if (round > fp8_dp_half_ulp) || ((round == fp8_dp_half_ulp) && (mantissa & 1 != 0)) {
res = res.wrapping_add(1);
}
res
} else {
let shift = 1_u8.wrapping_sub(exp);
let mantissa = mantissa | (1 << (fp8_significand_bits - 1));
let mut res = mantissa >> shift;
let round = (xbits | (1 << (53 - 1))) & ((fp8_dp_half_ulp << (shift as u64 + 1)) - 1);
if (round > (fp8_dp_half_ulp << shift as u64))
|| ((round == (fp8_dp_half_ulp << shift as u64)) && (res & 1 != 0))
{
res = res.wrapping_add(1);
}
res
};
res | sign
}
const fn convert_fp8_to_fp16(x: u8, fp8_interpretation: Kind) -> u16 {
let mut ur = (x as u16) << 8;
match fp8_interpretation {
Kind::E5M2 => {
if (ur & 0x7FFF) > 0x7C00 {
ur = 0x7FFF;
}
}
Kind::E4M3 => {
let sign = ur & 0x8000;
let mut exponent = ((ur & 0x7800) >> 1).wrapping_add(0x2000);
let mut mantissa = (ur & 0x0700) >> 1;
let absx = 0x7F & x;
if absx == 0x7F {
ur = 0x7FFF;
} else if exponent == 0x2000 {
if mantissa != 0 {
mantissa <<= 1;
while (mantissa & 0x0400) == 0 {
mantissa <<= 1;
exponent = exponent.wrapping_sub(0x0400);
}
mantissa &= 0x03FF;
} else {
exponent = 0;
}
ur = sign | exponent | mantissa;
} else {
ur = sign | exponent | mantissa;
}
}
};
ur
}
#[derive(Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "rkyv", archive(resolver = "F8E4M3Resolver"))]
#[cfg_attr(feature = "bytemuck", derive(Zeroable, Pod))]
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromBytes))]
#[repr(transparent)]
pub struct F8E4M3(u8);
impl F8E4M3 {
const INTERPRETATION: Kind = Kind::E4M3;
pub const fn from_bits(bits: u8) -> Self {
Self(bits)
}
pub const fn to_bits(&self) -> u8 {
self.0
}
pub const fn from_f64(x: f64) -> Self {
Self(convert_to_fp8(
x,
SaturationType::SatFinite,
Self::INTERPRETATION,
))
}
pub const fn from_f32(x: f32) -> Self {
Self::from_f64(x as f64)
}
pub const fn to_f16(&self) -> f16 {
f16::from_bits(convert_fp8_to_fp16(self.0, Self::INTERPRETATION))
}
pub const fn to_f32(&self) -> f32 {
self.to_f16().to_f32_const()
}
pub const fn to_f64(&self) -> f64 {
self.to_f16().to_f64_const()
}
pub fn total_cmp(&self, other: &Self) -> Ordering {
let mut left = self.to_bits() as i8;
let mut right = other.to_bits() as i8;
left ^= (((left >> 7) as u8) >> 1) as i8;
right ^= (((right >> 7) as u8) >> 1) as i8;
left.cmp(&right)
}
pub const fn is_sign_positive(&self) -> bool {
self.0 & 0x80u8 == 0
}
pub const fn is_sign_negative(&self) -> bool {
self.0 & 0x80u8 != 0
}
pub const fn is_nan(&self) -> bool {
self.0 == 0x7Fu8 || self.0 == 0xFFu8
}
pub const fn is_infinite(&self) -> bool {
self.0 & 0x7Fu8 == 0x7Eu8
}
pub const fn is_finite(&self) -> bool {
!(self.is_infinite() || self.is_nan())
}
pub const fn is_normal(&self) -> bool {
#[allow(clippy::unusual_byte_groupings)]
let exp = self.0 & 0b0_1111_000;
exp != 0 && self.is_finite()
}
pub fn min(self, other: Self) -> Self {
if other < self && !other.is_nan() {
other
} else {
self
}
}
pub fn max(self, other: Self) -> Self {
if other > self && !other.is_nan() {
other
} else {
self
}
}
pub fn clamp(self, min: Self, max: Self) -> Self {
assert!(min <= max);
let mut x = self;
if x < min {
x = min;
}
if x > max {
x = max;
}
x
}
pub const fn copysign(self, sign: Self) -> Self {
Self((sign.0 & 0x80u8) | (self.0 & 0x7Fu8))
}
pub const fn signum(self) -> Self {
if self.is_nan() {
self
} else if self.0 & 0x80u8 != 0 {
Self::NEG_ONE
} else {
Self::ONE
}
}
pub const fn classify(&self) -> FpCategory {
if self.is_infinite() {
FpCategory::Infinite
} else if !self.is_normal() {
FpCategory::Subnormal
} else if self.is_nan() {
FpCategory::Nan
} else if self.0 & 0x7Fu8 == 0 {
FpCategory::Zero
} else {
FpCategory::Normal
}
}
}
#[cfg(feature = "serde")]
struct VisitorF8E4M3;
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for F8E4M3 {
fn deserialize<D>(deserializer: D) -> Result<F8E4M3, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_newtype_struct("f8e4m3", VisitorF8E4M3)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::de::Visitor<'de> for VisitorF8E4M3 {
type Value = F8E4M3;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "tuple struct f8e4m3")
}
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(F8E4M3(<u8 as Deserialize>::deserialize(deserializer)?))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse().map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &"a float string")
})
}
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(F8E4M3::from_f32(v))
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(F8E4M3::from_f64(v))
}
}
#[derive(Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "rkyv", archive(resolver = "F8E5M2Resolver"))]
#[cfg_attr(feature = "bytemuck", derive(Zeroable, Pod))]
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromBytes))]
#[repr(transparent)]
pub struct F8E5M2(u8);
impl F8E5M2 {
const INTERPRETATION: Kind = Kind::E5M2;
pub const fn from_bits(bits: u8) -> Self {
Self(bits)
}
pub const fn to_bits(&self) -> u8 {
self.0
}
pub const fn from_f64(x: f64) -> Self {
Self(convert_to_fp8(
x,
SaturationType::SatFinite,
Self::INTERPRETATION,
))
}
pub const fn from_f32(x: f32) -> Self {
Self::from_f64(x as f64)
}
pub const fn to_f16(&self) -> f16 {
f16::from_bits(convert_fp8_to_fp16(self.0, Self::INTERPRETATION))
}
pub const fn to_f32(&self) -> f32 {
self.to_f16().to_f32_const()
}
pub const fn to_f64(&self) -> f64 {
self.to_f16().to_f64_const()
}
pub fn total_cmp(&self, other: &Self) -> Ordering {
let mut left = self.to_bits() as i8;
let mut right = other.to_bits() as i8;
left ^= (((left >> 7) as u8) >> 1) as i8;
right ^= (((right >> 7) as u8) >> 1) as i8;
left.cmp(&right)
}
pub const fn is_sign_positive(&self) -> bool {
self.0 & 0x80u8 == 0
}
pub const fn is_sign_negative(&self) -> bool {
self.0 & 0x80u8 != 0
}
pub const fn is_nan(&self) -> bool {
self.0 == 0x7Eu8 || self.0 == 0xFEu8
}
pub const fn is_infinite(&self) -> bool {
self.0 & 0x7Fu8 == 0x7Bu8
}
pub const fn is_finite(&self) -> bool {
!(self.is_infinite() || self.is_nan())
}
pub const fn is_normal(&self) -> bool {
#[allow(clippy::unusual_byte_groupings)]
let exp = self.0 & 0b0_11111_00;
exp != 0 && self.is_finite()
}
pub fn min(self, other: Self) -> Self {
if other < self && !other.is_nan() {
other
} else {
self
}
}
pub fn max(self, other: Self) -> Self {
if other > self && !other.is_nan() {
other
} else {
self
}
}
pub fn clamp(self, min: Self, max: Self) -> Self {
assert!(min <= max);
let mut x = self;
if x < min {
x = min;
}
if x > max {
x = max;
}
x
}
pub const fn copysign(self, sign: Self) -> Self {
Self((sign.0 & 0x80u8) | (self.0 & 0x7Fu8))
}
pub const fn signum(self) -> Self {
if self.is_nan() {
self
} else if self.0 & 0x80u8 != 0 {
Self::NEG_ONE
} else {
Self::ONE
}
}
pub const fn classify(&self) -> FpCategory {
if self.is_infinite() {
FpCategory::Infinite
} else if !self.is_normal() {
FpCategory::Subnormal
} else if self.is_nan() {
FpCategory::Nan
} else if self.0 & 0x7Fu8 == 0 {
FpCategory::Zero
} else {
FpCategory::Normal
}
}
}
#[cfg(feature = "serde")]
struct VisitorF8E5M2;
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for F8E5M2 {
fn deserialize<D>(deserializer: D) -> Result<F8E5M2, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_newtype_struct("f8e5m2", VisitorF8E5M2)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::de::Visitor<'de> for VisitorF8E5M2 {
type Value = F8E5M2;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "tuple struct f8e5m2")
}
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(F8E5M2(<u8 as Deserialize>::deserialize(deserializer)?))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse().map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &"a float string")
})
}
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(F8E5M2::from_f32(v))
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(F8E5M2::from_f64(v))
}
}
macro_rules! comparison {
($t:ident) => {
impl PartialEq for $t {
fn eq(&self, other: &Self) -> bool {
if self.is_nan() || other.is_nan() {
false
} else {
(self.0 == other.0) || ((self.0 | other.0) & 0x7Fu8 == 0)
}
}
}
impl PartialOrd for $t {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.is_nan() || other.is_nan() {
None
} else {
let neg = self.0 & 0x80u8 != 0;
let other_neg = other.0 & 0x80u8 != 0;
match (neg, other_neg) {
(false, false) => Some(self.0.cmp(&other.0)),
(false, true) => {
if (self.0 | other.0) & 0x7Fu8 == 0 {
Some(Ordering::Equal)
} else {
Some(Ordering::Greater)
}
}
(true, false) => {
if (self.0 | other.0) & 0x7Fu8 == 0 {
Some(Ordering::Equal)
} else {
Some(Ordering::Less)
}
}
(true, true) => Some(other.0.cmp(&self.0)),
}
}
}
fn lt(&self, other: &Self) -> bool {
if self.is_nan() || other.is_nan() {
false
} else {
let neg = self.0 & 0x80u8 != 0;
let other_neg = other.0 & 0x80u8 != 0;
match (neg, other_neg) {
(false, false) => self.0 < other.0,
(false, true) => false,
(true, false) => (self.0 | other.0) & 0x7Fu8 != 0,
(true, true) => self.0 > other.0,
}
}
}
fn le(&self, other: &Self) -> bool {
if self.is_nan() || other.is_nan() {
false
} else {
let neg = self.0 & 0x80u8 != 0;
let other_neg = other.0 & 0x80u8 != 0;
match (neg, other_neg) {
(false, false) => self.0 <= other.0,
(false, true) => (self.0 | other.0) & 0x7Fu8 == 0,
(true, false) => true,
(true, true) => self.0 >= other.0,
}
}
}
fn gt(&self, other: &Self) -> bool {
if self.is_nan() || other.is_nan() {
false
} else {
let neg = self.0 & 0x80u8 != 0;
let other_neg = other.0 & 0x80u8 != 0;
match (neg, other_neg) {
(false, false) => self.0 > other.0,
(false, true) => (self.0 | other.0) & 0x7Fu8 != 0,
(true, false) => false,
(true, true) => self.0 < other.0,
}
}
}
fn ge(&self, other: &Self) -> bool {
if self.is_nan() || other.is_nan() {
false
} else {
let neg = self.0 & 0x80u8 != 0;
let other_neg = other.0 & 0x80u8 != 0;
match (neg, other_neg) {
(false, false) => self.0 >= other.0,
(false, true) => true,
(true, false) => (self.0 | other.0) & 0x7Fu8 == 0,
(true, true) => self.0 <= other.0,
}
}
}
}
};
}
comparison!(F8E4M3);
comparison!(F8E5M2);
macro_rules! constants {
($t:ident) => {
impl $t {
pub const PI: Self = Self::from_f64(f64::consts::PI);
pub const TAU: Self = Self::from_f64(f64::consts::TAU);
pub const FRAC_PI_2: Self = Self::from_f64(f64::consts::FRAC_PI_2);
pub const FRAC_PI_3: Self = Self::from_f64(f64::consts::FRAC_PI_3);
pub const FRAC_PI_4: Self = Self::from_f64(f64::consts::FRAC_PI_4);
pub const FRAC_PI_6: Self = Self::from_f64(f64::consts::FRAC_PI_6);
pub const FRAC_PI_8: Self = Self::from_f64(f64::consts::FRAC_PI_8);
pub const FRAC_1_PI: Self = Self::from_f64(f64::consts::FRAC_1_PI);
pub const FRAC_2_PI: Self = Self::from_f64(f64::consts::FRAC_2_PI);
pub const FRAC_2_SQRT_PI: Self = Self::from_f64(f64::consts::FRAC_2_SQRT_PI);
pub const SQRT_2: Self = Self::from_f64(f64::consts::SQRT_2);
pub const FRAC_1_SQRT_2: Self = Self::from_f64(f64::consts::FRAC_1_SQRT_2);
pub const E: Self = Self::from_f64(f64::consts::E);
pub const LOG2_10: Self = Self::from_f64(f64::consts::LOG2_10);
pub const LOG2_E: Self = Self::from_f64(f64::consts::LOG2_E);
pub const LOG10_2: Self = Self::from_f64(f64::consts::LOG10_2);
pub const LOG10_E: Self = Self::from_f64(f64::consts::LOG10_E);
pub const LN_2: Self = Self::from_f64(f64::consts::LN_2);
pub const LN_10: Self = Self::from_f64(f64::consts::LN_10);
}
};
}
constants!(F8E4M3);
constants!(F8E5M2);
#[allow(clippy::unusual_byte_groupings)]
impl F8E4M3 {
pub const MANTISSA_DIGITS: u32 = 3;
pub const MAX: Self = Self::from_bits(0x7E - 1);
pub const MIN: Self = Self::from_bits(0xFE - 1);
pub const INFINITY: Self = Self::from_bits(0x7E);
pub const NEG_INFINITY: Self = Self::from_bits(0xFE);
pub const MIN_POSITIVE: Self = Self::from_bits(0b0_0001_000);
pub const MIN_POSITIVE_SUBNORMAL: Self = Self::from_bits(0b0_0000_001);
pub const MAX_SUBNORMAL: Self = Self::from_bits(0b0_0000_111);
pub const EPSILON: Self = Self::from_bits(0b0_0100_000);
pub const NAN: Self = Self::from_bits(0x7F);
pub const ONE: Self = Self::from_bits(0b0_0111_000);
pub const ZERO: Self = Self::from_bits(0b0_0000_000);
pub const NEG_ONE: Self = Self::from_bits(0b1_0111_000);
pub const NEG_ZERO: Self = Self::from_bits(0b1_0000_000);
pub const MIN_EXP: i32 = -5;
pub const MIN_10_EXP: i32 = -1;
pub const MAX_EXP: i32 = 7;
pub const MAX_10_EXP: i32 = 2;
pub const DIGITS: u32 = 0;
}
#[allow(clippy::unusual_byte_groupings)]
impl F8E5M2 {
pub const MANTISSA_DIGITS: u32 = 2;
pub const MAX: Self = Self::from_bits(0x7B - 1);
pub const MIN: Self = Self::from_bits(0xFB - 1);
pub const INFINITY: Self = Self::from_bits(0x7B);
pub const NEG_INFINITY: Self = Self::from_bits(0xFB);
pub const MIN_POSITIVE: Self = Self::from_bits(0b0_00001_00);
pub const MIN_POSITIVE_SUBNORMAL: Self = Self::from_bits(0b0_00000_01);
pub const MAX_SUBNORMAL: Self = Self::from_bits(0b0_00000_11);
pub const EPSILON: Self = Self::from_bits(0b0_01101_00);
pub const NAN: Self = Self::from_bits(0x7E);
pub const ONE: Self = Self::from_bits(0b0_01111_00);
pub const ZERO: Self = Self::from_bits(0b0_00000_00);
pub const NEG_ONE: Self = Self::from_bits(0b1_01111_00);
pub const NEG_ZERO: Self = Self::from_bits(0b1_00000_00);
pub const MIN_EXP: i32 = -13;
pub const MIN_10_EXP: i32 = -4;
pub const MAX_EXP: i32 = 15;
pub const MAX_10_EXP: i32 = 4;
pub const DIGITS: u32 = 0;
}
macro_rules! io {
($t:ident) => {
impl Display for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.to_f32(), f)
}
}
impl Debug for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.to_f32(), f)
}
}
impl FromStr for $t {
type Err = ParseFloatError;
fn from_str(src: &str) -> Result<$t, ParseFloatError> {
f32::from_str(src).map($t::from_f32)
}
}
impl From<f16> for $t {
fn from(x: f16) -> $t {
Self::from_f32(x.to_f32())
}
}
impl From<f32> for $t {
fn from(x: f32) -> $t {
Self::from_f32(x)
}
}
impl From<f64> for $t {
fn from(x: f64) -> $t {
Self::from_f64(x)
}
}
impl LowerExp for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:e}", self.to_f32())
}
}
impl LowerHex for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:x}", self.0)
}
}
impl UpperExp for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:E}", self.to_f32())
}
}
impl UpperHex for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:X}", self.0)
}
}
};
}
io!(F8E4M3);
io!(F8E5M2);
macro_rules! binary {
($trait:ident, $fn_name:ident, $t:ident, $op:tt) => {
impl $trait for $t {
type Output = Self;
fn $fn_name(self, rhs: Self) -> Self::Output {
Self::from_f32(self.to_f32() $op rhs.to_f32())
}
}
};
}
macro_rules! assign_binary {
($trait:ident, $fn_name:ident, $t:ident, $op:tt) => {
impl $trait for $t {
fn $fn_name(&mut self, rhs: Self) {
*self = Self::from_f32(self.to_f32() $op rhs.to_f32())
}
}
};
}
macro_rules! unary {
($trait:ident, $fn_name:ident, $t:ident, $op:tt) => {
impl $trait for $t {
type Output = Self;
fn $fn_name(self) -> Self::Output {
Self::from_f32($op self.to_f32())
}
}
};
}
binary!(Add, add, F8E4M3, +);
binary!(Sub, sub, F8E4M3, -);
binary!(Mul, mul, F8E4M3, *);
binary!(Div, div, F8E4M3, /);
binary!(Rem, rem, F8E4M3, %);
assign_binary!(AddAssign, add_assign, F8E4M3, +);
assign_binary!(SubAssign, sub_assign, F8E4M3, -);
assign_binary!(MulAssign, mul_assign, F8E4M3, *);
assign_binary!(DivAssign, div_assign, F8E4M3, /);
assign_binary!(RemAssign, rem_assign, F8E4M3, %);
unary!(Neg, neg, F8E4M3, -);
binary!(Add, add, F8E5M2, +);
binary!(Sub, sub, F8E5M2, -);
binary!(Mul, mul, F8E5M2, *);
binary!(Div, div, F8E5M2, /);
binary!(Rem, rem, F8E5M2, %);
assign_binary!(AddAssign, add_assign, F8E5M2, +);
assign_binary!(SubAssign, sub_assign, F8E5M2, -);
assign_binary!(MulAssign, mul_assign, F8E5M2, *);
assign_binary!(DivAssign, div_assign, F8E5M2, /);
assign_binary!(RemAssign, rem_assign, F8E5M2, %);
unary!(Neg, neg, F8E5M2, -);
macro_rules! from_t {
($t:ident) => {
impl From<$t> for f64 {
fn from(value: $t) -> Self {
value.to_f64()
}
}
impl From<$t> for f32 {
fn from(value: $t) -> Self {
value.to_f32()
}
}
impl From<$t> for f16 {
fn from(value: $t) -> Self {
value.to_f16()
}
}
};
}
from_t!(F8E4M3);
from_t!(F8E5M2);