use super::binary::Format;
use super::kernel::{self, Class, IntValue, Outcome, Parts, Rounded};
use super::{Env, Flags, IntOverflow, Spec};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct F80 {
pub sign_exp: u16,
pub sig: u64,
}
const BIAS: i32 = 16383;
const EMAX: i32 = 16383;
const EXP_FIELD_MAX: u16 = 0x7fff;
const INTEGER_BIT: u64 = 1 << 63;
const QUIET_BIT: u64 = 1 << 62;
const FRAC_MASK: u64 = INTEGER_BIT - 1;
impl F80 {
pub const SPEC: Spec = Spec::interchange(64, EMAX);
pub const ZERO: F80 = F80 {
sign_exp: 0,
sig: 0,
};
pub const INFINITY: F80 = F80 {
sign_exp: EXP_FIELD_MAX,
sig: INTEGER_BIT,
};
pub const INDEFINITE: F80 = F80 {
sign_exp: 0x8000 | EXP_FIELD_MAX,
sig: INTEGER_BIT | QUIET_BIT,
};
pub const MAX_FINITE: F80 = F80 {
sign_exp: EXP_FIELD_MAX - 1,
sig: u64::MAX,
};
#[must_use]
pub const fn new(sign_exp: u16, sig: u64) -> F80 {
F80 { sign_exp, sig }
}
#[must_use]
#[inline]
pub const fn sign(self) -> bool {
self.sign_exp & 0x8000 != 0
}
#[must_use]
#[inline]
pub const fn exp_field(self) -> u16 {
self.sign_exp & EXP_FIELD_MAX
}
#[must_use]
pub fn from_bytes(bytes: [u8; 10]) -> F80 {
let mut sig = [0u8; 8];
sig.copy_from_slice(&bytes[..8]);
F80 {
sig: u64::from_le_bytes(sig),
sign_exp: u16::from_le_bytes([bytes[8], bytes[9]]),
}
}
#[must_use]
pub fn to_bytes(self) -> [u8; 10] {
let mut out = [0u8; 10];
out[..8].copy_from_slice(&self.sig.to_le_bytes());
out[8..].copy_from_slice(&self.sign_exp.to_le_bytes());
out
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Precision {
Single,
Double,
#[default]
Extended,
}
impl Precision {
#[must_use]
pub const fn from_pc(bits: u32) -> Option<Precision> {
match bits & 3 {
0 => Some(Precision::Single),
2 => Some(Precision::Double),
3 => Some(Precision::Extended),
_ => None,
}
}
#[must_use]
pub const fn pc(self) -> u32 {
match self {
Precision::Single => 0,
Precision::Double => 2,
Precision::Extended => 3,
}
}
#[must_use]
pub const fn bits(self) -> u32 {
match self {
Precision::Single => 24,
Precision::Double => 53,
Precision::Extended => 64,
}
}
#[must_use]
pub const fn spec(self) -> Spec {
F80::SPEC.with_precision(self.bits())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X87Class {
Ieee(super::Category),
Unsupported,
}
fn decode(v: F80) -> Option<Parts> {
let sign = v.sign();
let field = v.exp_field();
let integer = v.sig & INTEGER_BIT != 0;
let frac = v.sig & FRAC_MASK;
if field == EXP_FIELD_MAX {
if !integer {
return None;
}
if frac == 0 {
return Some(Parts {
sign,
exp: 0,
frac: 0,
class: Class::Inf,
snan: false,
});
}
return Some(Parts {
sign,
exp: 0,
frac,
class: Class::Nan,
snan: v.sig & QUIET_BIT == 0,
});
}
if field == 0 {
return Some(Parts {
sign,
exp: F80::SPEC.min_ulp,
frac: v.sig,
class: if v.sig == 0 {
Class::Zero
} else {
Class::Finite
},
snan: false,
});
}
if !integer {
return None;
}
Some(Parts {
sign,
exp: i32::from(field) - BIAS - 63,
frac: v.sig,
class: Class::Finite,
snan: false,
})
}
#[must_use]
pub fn classify(v: F80) -> X87Class {
use super::Category;
let Some(p) = decode(v) else {
return X87Class::Unsupported;
};
X87Class::Ieee(match p.class {
Class::Nan => {
if p.snan {
Category::SignalingNan
} else {
Category::QuietNan
}
}
Class::Inf => {
if p.sign {
Category::NegativeInfinity
} else {
Category::PositiveInfinity
}
}
Class::Zero => {
if p.sign {
Category::NegativeZero
} else {
Category::PositiveZero
}
}
Class::Finite => {
let subnormal = v.exp_field() == 0 && v.sig & INTEGER_BIT == 0;
match (p.sign, subnormal) {
(true, false) => Category::NegativeNormal,
(true, true) => Category::NegativeSubnormal,
(false, true) => Category::PositiveSubnormal,
(false, false) => Category::PositiveNormal,
}
}
})
}
fn unpack(v: F80, env: Env) -> (Option<Parts>, Flags) {
let Some(p) = decode(v) else {
return (None, Flags::INVALID);
};
let subnormal = p.class == Class::Finite && v.exp_field() == 0 && v.sig & INTEGER_BIT == 0;
if !subnormal {
return (Some(p), Flags::NONE);
}
let flags = if env.subnormal_inputs.reports() {
Flags::DENORMAL
} else {
Flags::NONE
};
if env.subnormal_inputs.flushes() {
(Some(Parts::zero(p.sign)), flags)
} else {
(Some(p), flags)
}
}
fn encode(out: Outcome, env: Env) -> F80 {
let sign_exp = |sign: bool, field: u16| if sign { 0x8000 | field } else { field };
match out {
Outcome::DefaultNan => F80 {
sign_exp: sign_exp(env.nan.default_sign, EXP_FIELD_MAX),
sig: INTEGER_BIT | QUIET_BIT,
},
Outcome::Nan { sign, payload } => F80 {
sign_exp: sign_exp(sign, EXP_FIELD_MAX),
sig: INTEGER_BIT | QUIET_BIT | (payload & FRAC_MASK),
},
Outcome::Num(sign, Rounded::Zero) => F80 {
sign_exp: sign_exp(sign, 0),
sig: 0,
},
Outcome::Num(sign, Rounded::Inf) => F80 {
sign_exp: sign_exp(sign, EXP_FIELD_MAX),
sig: INTEGER_BIT,
},
Outcome::Num(sign, Rounded::Finite { exp, frac }) => {
let msb = 63 - frac.leading_zeros();
let lead = exp + msb as i32;
if lead >= F80::SPEC.emin() {
F80 {
sign_exp: sign_exp(sign, (lead + BIAS) as u16),
sig: frac << (63 - msb),
}
} else {
F80 {
sign_exp: sign_exp(sign, 0),
sig: frac << (exp - F80::SPEC.min_ulp),
}
}
}
}
}
fn binop(
a: F80,
b: F80,
pc: Precision,
env: Env,
op: fn(Parts, Parts, Spec, Env) -> (Outcome, Flags),
) -> (F80, Flags) {
let (pa, fa) = unpack(a, env);
let (pb, fb) = unpack(b, env);
let (Some(pa), Some(pb)) = (pa, pb) else {
return (encode(Outcome::DefaultNan, env), Flags::INVALID | fa | fb);
};
let (out, f) = op(pa, pb, pc.spec(), env);
(encode(out, env), f | fa | fb)
}
pub fn add(a: F80, b: F80, pc: Precision, env: Env) -> (F80, Flags) {
binop(a, b, pc, env, |x, y, s, e| kernel::add(x, y, false, s, e))
}
pub fn sub(a: F80, b: F80, pc: Precision, env: Env) -> (F80, Flags) {
binop(a, b, pc, env, |x, y, s, e| kernel::add(x, y, true, s, e))
}
pub fn mul(a: F80, b: F80, pc: Precision, env: Env) -> (F80, Flags) {
binop(a, b, pc, env, kernel::mul)
}
pub fn div(a: F80, b: F80, pc: Precision, env: Env) -> (F80, Flags) {
binop(a, b, pc, env, kernel::div)
}
pub fn sqrt(a: F80, pc: Precision, env: Env) -> (F80, Flags) {
let (pa, fa) = unpack(a, env);
let Some(pa) = pa else {
return (encode(Outcome::DefaultNan, env), Flags::INVALID | fa);
};
let (out, f) = kernel::sqrt(pa, pc.spec(), env);
(encode(out, env), f | fa)
}
pub fn fma(a: F80, b: F80, c: F80, pc: Precision, env: Env) -> (F80, Flags) {
let (pa, fa) = unpack(a, env);
let (pb, fb) = unpack(b, env);
let (pc_parts, fc) = unpack(c, env);
let (Some(pa), Some(pb), Some(pc_parts)) = (pa, pb, pc_parts) else {
return (
encode(Outcome::DefaultNan, env),
Flags::INVALID | fa | fb | fc,
);
};
let (out, f) = kernel::fma(pa, pb, pc_parts, pc.spec(), env);
(encode(out, env), f | fa | fb | fc)
}
pub fn round_to_integral(a: F80, env: Env) -> (F80, Flags) {
let (pa, fa) = unpack(a, env);
let Some(p) = pa else {
return (encode(Outcome::DefaultNan, env), Flags::INVALID | fa);
};
match p.class {
Class::Nan => {
let flags = if p.snan { Flags::INVALID } else { Flags::NONE };
(encode(quiet_nan(p, env), env), flags | fa)
}
Class::Inf => (encode(Outcome::Num(p.sign, Rounded::Inf), env), fa),
_ if p.exp >= 0 => (a, fa),
_ => match kernel::to_integer(p, env) {
IntValue::Value {
sign,
magnitude,
inexact,
} => {
let (out, f) = kernel::round_exact(sign, 0, magnitude, F80::SPEC, env);
let inx = if inexact { Flags::INEXACT } else { Flags::NONE };
(encode(out, env), f | inx | fa)
}
_ => (encode(Outcome::DefaultNan, env), Flags::INVALID | fa),
},
}
}
pub fn scale(a: F80, by: i64, env: Env) -> (F80, Flags) {
let (pa, fa) = unpack(a, env);
let Some(p) = pa else {
return (encode(Outcome::DefaultNan, env), Flags::INVALID | fa);
};
match p.class {
Class::Nan => {
let flags = if p.snan { Flags::INVALID } else { Flags::NONE };
(encode(quiet_nan(p, env), env), flags | fa)
}
Class::Inf => (encode(Outcome::Num(p.sign, Rounded::Inf), env), fa),
Class::Zero => (encode(Outcome::Num(p.sign, Rounded::Zero), env), fa),
Class::Finite => {
let by = by.clamp(-(1 << 20), 1 << 20) as i32;
let (out, f) = kernel::round_exact(
p.sign,
p.exp.saturating_add(by),
u128::from(p.frac),
F80::SPEC,
env,
);
(encode(out, env), f | fa)
}
}
}
pub fn extract(a: F80, env: Env) -> (F80, F80, Flags) {
let (pa, fa) = unpack(a, env);
let Some(p) = pa else {
let ind = encode(Outcome::DefaultNan, env);
return (ind, ind, Flags::INVALID | fa);
};
match p.class {
Class::Nan => {
let flags = if p.snan { Flags::INVALID } else { Flags::NONE };
let n = encode(quiet_nan(p, env), env);
(n, n, flags | fa)
}
Class::Zero => {
let zero = encode(Outcome::Num(p.sign, Rounded::Zero), env);
let neg_inf = encode(Outcome::Num(true, Rounded::Inf), env);
(neg_inf, zero, Flags::DIV_BY_ZERO | fa)
}
Class::Inf => {
let inf = encode(Outcome::Num(p.sign, Rounded::Inf), env);
let pos_inf = encode(Outcome::Num(false, Rounded::Inf), env);
(pos_inf, inf, fa)
}
Class::Finite => {
let msb = 63 - p.frac.leading_zeros() as i32;
let unbiased = p.exp + msb;
let field = BIAS as u16;
let sig = F80 {
sign_exp: if p.sign { 0x8000 | field } else { field },
sig: p.frac << (63 - msb as u32),
};
let (exp, ef) = from_signed(i64::from(unbiased), 64, env);
(exp, sig, ef | fa)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Remainder {
pub value: F80,
pub flags: Flags,
pub quotient: u8,
pub incomplete: bool,
}
pub fn remainder(a: F80, b: F80, ieee: bool, env: Env) -> Remainder {
let out = |value: F80, flags: Flags| Remainder {
value,
flags,
quotient: 0,
incomplete: false,
};
let (pa, fa) = unpack(a, env);
let (pb, fb) = unpack(b, env);
let (Some(pa), Some(pb)) = (pa, pb) else {
return out(encode(Outcome::DefaultNan, env), Flags::INVALID | fa | fb);
};
if let Some((o, f)) = kernel::nan_result(&[pa, pb], env) {
return out(encode(o, env), f | fa | fb);
}
if pa.class == Class::Inf || pb.class == Class::Zero {
return out(encode(Outcome::DefaultNan, env), Flags::INVALID | fa | fb);
}
if pa.class == Class::Zero || pb.class == Class::Inf {
return out(a, fa | fb);
}
let norm = |p: Parts| -> (i32, u64) {
let n = p.frac.leading_zeros();
(p.exp - n as i32, p.frac << n)
};
let (ea, sig_a) = norm(pa);
let (eb, sig_b) = norm(pb);
let d = ea - eb;
if d >= 64 {
let k = d - 63;
let n = u128::from(sig_a) << 63;
let r = n % u128::from(sig_b);
let (o, f) = kernel::round_exact(pa.sign, eb + k, r, F80::SPEC, env);
return Remainder {
value: encode(o, env),
flags: f | fa | fb,
quotient: 0,
incomplete: true,
};
}
if d <= -2 {
return out(a, fa | fb);
}
let (n, den, scale) = if d >= 0 {
(u128::from(sig_a) << d, u128::from(sig_b), eb)
} else {
(u128::from(sig_a), u128::from(sig_b) << 1, ea)
};
let mut q = n / den;
let mut r = n % den;
let mut sign = pa.sign;
if ieee {
let twice = r * 2;
if twice > den || (twice == den && q & 1 == 1) {
q += 1;
r = den - r;
sign = !sign;
}
}
let (o, f) = kernel::round_exact(sign, scale, r, F80::SPEC, env);
Remainder {
value: encode(o, env),
flags: f | fa | fb,
quotient: (q & 7) as u8,
incomplete: false,
}
}
fn quiet_nan(p: Parts, env: Env) -> Outcome {
if env.nan.propagate == super::Propagate::Default {
Outcome::DefaultNan
} else {
Outcome::Nan {
sign: p.sign,
payload: p.frac,
}
}
}
#[must_use]
pub fn compare(a: F80, b: F80) -> Option<core::cmp::Ordering> {
let (pa, pb) = (decode(a)?, decode(b)?);
if pa.class == Class::Nan || pb.class == Class::Nan {
return None;
}
Some(kernel::compare(pa, pb))
}
pub fn from_binary<F: Format>(bits: u64, env: Env) -> (F80, Flags) {
let p = super::binary::classify::<F>(bits);
let sign = bits & F::SIGN != 0;
let field = (bits >> F::SIG_BITS) & F::EXP_FIELD_MAX;
let frac = bits & F::SIG_MASK;
match p {
super::Category::SignalingNan | super::Category::QuietNan => {
let quiet = frac & F::QUIET != 0;
let payload = (frac & !F::QUIET) << (63 - F::SIG_BITS);
let out = if env.nan.propagate == super::Propagate::Default {
Outcome::DefaultNan
} else {
Outcome::Nan { sign, payload }
};
let flags = if quiet { Flags::NONE } else { Flags::INVALID };
(encode(out, env), flags)
}
super::Category::NegativeInfinity | super::Category::PositiveInfinity => {
(encode(Outcome::Num(sign, Rounded::Inf), env), Flags::NONE)
}
super::Category::NegativeZero | super::Category::PositiveZero => {
(encode(Outcome::Num(sign, Rounded::Zero), env), Flags::NONE)
}
_ => {
let (frac, exp) = if field == 0 {
(frac, F::SPEC.min_ulp)
} else {
(
frac | (1u64 << F::SIG_BITS),
field as i32 - F::BIAS - F::SIG_BITS as i32,
)
};
let denorm = if field == 0 && env.subnormal_inputs.reports() {
Flags::DENORMAL
} else {
Flags::NONE
};
let (out, f) = kernel::round_exact(sign, exp, u128::from(frac), F80::SPEC, env);
(encode(out, env), f | denorm)
}
}
}
pub fn to_binary<F: Format>(v: F80, env: Env) -> (u64, Flags) {
let (p, fin) = unpack(v, env);
let Some(p) = p else {
let sign = if env.nan.default_sign { F::SIGN } else { 0 };
return (sign | F::QUIET_NAN, Flags::INVALID);
};
match p.class {
Class::Nan => {
let flags = if p.snan { Flags::INVALID } else { Flags::NONE };
let bits = if env.nan.propagate == super::Propagate::Default {
let sign = if env.nan.default_sign { F::SIGN } else { 0 };
sign | F::QUIET_NAN
} else {
let sign = if p.sign { F::SIGN } else { 0 };
let payload = (p.frac >> (63 - F::SIG_BITS)) & F::SIG_MASK;
sign | F::INF | F::QUIET | payload
};
(bits, flags)
}
_ => {
let (out, f) = match p.class {
Class::Inf => (Outcome::Num(p.sign, Rounded::Inf), Flags::NONE),
Class::Zero => (Outcome::Num(p.sign, Rounded::Zero), Flags::NONE),
_ => kernel::round_exact(p.sign, p.exp, u128::from(p.frac), F::SPEC, env),
};
(super::binary::encode_outcome::<F>(out, env), f | fin)
}
}
}
pub fn to_signed(v: F80, bits: u32, env: Env) -> (i64, Flags) {
let (p, fin) = unpack(v, env);
let max: u128 = (1u128 << (bits - 1)) - 1;
let min_mag: u128 = 1u128 << (bits - 1);
let indefinite = if bits >= 64 {
min_mag as i64
} else {
((min_mag as u64) << (64 - bits)) as i64 >> (64 - bits)
};
let Some(p) = p else {
return (indefinite, Flags::INVALID);
};
let saturate = |sign: bool| {
if env.int_overflow == IntOverflow::Indefinite || sign {
indefinite
} else {
max as i64
}
};
match kernel::to_integer(p, env) {
IntValue::Nan => {
let v = match env.int_overflow {
IntOverflow::SaturateNanMax => max as i64,
IntOverflow::SaturateNanZero => 0,
IntOverflow::Indefinite => indefinite,
};
(v, Flags::INVALID | fin)
}
IntValue::Inf(sign) => (saturate(sign), Flags::INVALID | fin),
IntValue::Value {
sign,
magnitude,
inexact,
} => {
let fits = if sign {
magnitude <= min_mag
} else {
magnitude <= max
};
if !fits {
return (saturate(sign), Flags::INVALID | fin);
}
let v = if sign {
let neg = (magnitude as u64).wrapping_neg();
if bits >= 64 {
neg as i64
} else {
((neg << (64 - bits)) as i64) >> (64 - bits)
}
} else {
magnitude as i64
};
let f = if inexact { Flags::INEXACT } else { Flags::NONE };
(v, f | fin)
}
}
}
pub fn from_signed(value: i64, bits: u32, env: Env) -> (F80, Flags) {
let v = if bits >= 64 {
value
} else {
((value as u64) << (64 - bits)) as i64 >> (64 - bits)
};
if v == 0 {
return (F80::ZERO, Flags::NONE);
}
let (out, f) = kernel::round_exact(v < 0, 0, u128::from(v.unsigned_abs()), F80::SPEC, env);
(encode(out, env), f)
}