#![allow(clippy::float_cmp)]
#![cfg_attr(feature = "arbitrary_precision", allow(dead_code))]
use std::cmp::Ordering;
use std::fmt::{self, Formatter};
use std::hash::Hasher;
use super::number::{from_str_with, InlineNumber, InlineNumberError};
use super::InlineValue;
use crate::number::INumber;
use crate::value::{
number_cmp, Destructured, DestructuredMut, DestructuredRef, IValue, NumVal, ValueType,
};
const EXP_SHIFT: u32 = 4;
const MANTISSA_SHIFT: u32 = 8;
const MANTISSA_BITS: u32 = usize::BITS - MANTISSA_SHIFT;
const _: () = assert!(super::IS_NUMBER < (1usize << EXP_SHIFT));
const EXP_BIAS: i32 = 7;
const INT_EXP0_CODE: usize = 15;
fn integer_decode(value: f64) -> (u64, i32, bool) {
let bits = value.to_bits();
let negative = bits >> 63 != 0;
let raw_exp = ((bits >> 52) & 0x7ff) as i32;
let frac = bits & 0x000f_ffff_ffff_ffff;
if raw_exp == 0 {
(frac, -1074, negative)
} else {
(frac | 0x0010_0000_0000_0000, raw_exp - 1075, negative)
}
}
fn i64_fits_f64(v: i64) -> bool {
v == 0 || 64 - v.unsigned_abs().leading_zeros() - v.unsigned_abs().trailing_zeros() <= 53
}
fn scale_pow2(m: f64, exp: i32) -> f64 {
if exp >= 0 {
m * (1u64 << exp) as f64
} else {
m / (1u64 << (-exp)) as f64
}
}
pub(crate) struct BinaryNumberRepr;
impl BinaryNumberRepr {
fn fits_mantissa(m: i128) -> bool {
let limit = 1i128 << (MANTISSA_BITS - 1);
m >= -limit && m < limit
}
fn exp_code(exp: i32, dot: bool) -> usize {
if exp == 0 && !dot {
INT_EXP0_CODE
} else {
debug_assert!((-7..=7).contains(&exp), "inline exponent out of range");
(exp + EXP_BIAS) as usize
}
}
fn code_exp(code: usize) -> i32 {
if code == INT_EXP0_CODE {
0
} else {
code as i32 - EXP_BIAS
}
}
fn code_has_dot(code: usize) -> bool {
code != INT_EXP0_CODE
}
fn encode(mantissa: i64, code: usize) -> usize {
let bits = super::IS_NUMBER | ((mantissa as usize) << MANTISSA_SHIFT) | (code << EXP_SHIFT);
debug_assert_eq!(
bits & super::TAG_MASK,
0,
"inline number must leave the tag bits clear"
);
bits
}
fn mantissa(bits: usize) -> i64 {
((bits as isize) >> MANTISSA_SHIFT) as i64
}
fn code(bits: usize) -> usize {
(bits >> EXP_SHIFT) & 0xf
}
#[cfg(codegen_probes)]
pub(crate) unsafe fn assume_integer(bits: usize) {
unsafe { std::hint::assert_unchecked(Self::code(bits) == INT_EXP0_CODE) };
}
fn decode(bits: usize) -> (i64, i32) {
(Self::mantissa(bits), Self::code_exp(Self::code(bits)))
}
fn to_i64(m: i64, exp: i32) -> Option<i64> {
if exp >= 0 {
Some(m << exp)
} else {
let k = (-exp) as u32;
(m & ((1i64 << k) - 1) == 0).then(|| m >> k)
}
}
fn to_f64_exact(bits: usize) -> Option<f64> {
let (m, exp) = Self::decode(bits);
i64_fits_f64(m).then(|| scale_pow2(m as f64, exp))
}
fn to_f64_lossy(bits: usize) -> f64 {
let (m, exp) = Self::decode(bits);
scale_pow2(m as f64, exp)
}
fn has_decimal_point(bits: usize) -> bool {
Self::code_has_dot(Self::code(bits))
}
fn num_val(bits: usize) -> NumVal<'static> {
let (m, exp) = Self::decode(bits);
match Self::to_i64(m, exp) {
Some(i) => NumVal::from_i64(i),
None => NumVal::from_f64(scale_pow2(m as f64, exp)),
}
}
}
impl InlineNumber for BinaryNumberRepr {
fn encode_int(value: i64) -> Option<usize> {
let limit = 1i64 << (MANTISSA_BITS - 1);
(value >= -limit && value < limit).then(|| Self::encode(value, Self::exp_code(0, false)))
}
fn encode_f64(value: f64) -> Option<usize> {
if value == 0.0 {
return Some(Self::encode(0, Self::exp_code(0, true)));
}
let (frac, e2, neg) = integer_decode(value);
let tz = frac.trailing_zeros();
let mag = i128::from(frac >> tz);
let exp = e2 + tz as i32;
let m = if neg { -mag } else { mag };
(Self::fits_mantissa(m) && (-EXP_BIAS..=EXP_BIAS).contains(&exp))
.then(|| Self::encode(m as i64, Self::exp_code(exp, true)))
}
fn from_str(s: &str) -> Result<usize, InlineNumberError> {
from_str_with(s, Self::encode_int, |s| {
s.parse::<f64>()
.ok()
.filter(|v| v.is_finite())
.and_then(Self::encode_f64)
})
}
}
impl InlineValue for BinaryNumberRepr {
fn value_type(&self, _v: &IValue) -> ValueType {
ValueType::Number
}
unsafe fn hash(&self, v: &IValue, state: &mut dyn Hasher) {
Self::num_val(v.usize_()).hash(state);
}
unsafe fn eq(&self, a: &IValue, b: &IValue) -> bool {
number_cmp(Self::num_val(a.usize_()), b) == Some(Ordering::Equal)
}
unsafe fn partial_cmp(&self, a: &IValue, b: &IValue) -> Option<Ordering> {
number_cmp(Self::num_val(a.usize_()), b)
}
unsafe fn debug(&self, v: &IValue, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", Self::num_val(v.usize_()))
}
fn destructure(&self, v: IValue) -> Destructured {
Destructured::Number(INumber(v))
}
unsafe fn destructure_ref<'a>(&self, v: &'a IValue) -> DestructuredRef<'a> {
DestructuredRef::Number(v.as_number_unchecked())
}
unsafe fn destructure_mut<'a>(&self, v: &'a mut IValue) -> DestructuredMut<'a> {
DestructuredMut::Number(v.as_number_unchecked_mut())
}
unsafe fn num_val<'a>(&self, v: &'a IValue) -> Option<NumVal<'a>> {
Some(Self::num_val(v.usize_()))
}
fn has_decimal_point(&self, v: &IValue) -> bool {
Self::has_decimal_point(v.usize_())
}
unsafe fn to_f64(&self, v: &IValue) -> Option<f64> {
Self::to_f64_exact(v.usize_())
}
unsafe fn to_f64_lossy(&self, v: &IValue) -> Option<f64> {
Some(Self::to_f64_lossy(v.usize_()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exponent_codes_are_linear_and_niche_safe() {
for exp in -7..=7 {
let c = BinaryNumberRepr::exp_code(exp, true);
assert_ne!(c, INT_EXP0_CODE);
assert_eq!(BinaryNumberRepr::code_exp(c), exp);
assert!(
BinaryNumberRepr::code_has_dot(c),
"dot value exp {} -> code {}",
exp,
c
);
}
assert_eq!(BinaryNumberRepr::exp_code(0, false), INT_EXP0_CODE);
assert_eq!(BinaryNumberRepr::code_exp(INT_EXP0_CODE), 0);
assert!(!BinaryNumberRepr::code_has_dot(INT_EXP0_CODE));
assert_ne!(
BinaryNumberRepr::exp_code(0, false),
BinaryNumberRepr::exp_code(0, true)
);
assert_eq!(
BinaryNumberRepr::encode_int(0),
Some(BinaryNumberRepr::encode(0, INT_EXP0_CODE))
);
assert_ne!(BinaryNumberRepr::encode_int(0), Some(0));
assert_ne!(BinaryNumberRepr::encode_f64(0.0), Some(0));
}
#[test]
fn large_magnitudes_never_misencode() {
for &x in &[
0.5_f64,
1e18,
1e22,
1.7014118346046923e38, f64::MAX,
1e39,
6.022e23,
9.223372036854776e18,
f64::MIN_POSITIVE,
] {
if let Some(bits) = BinaryNumberRepr::encode_f64(x) {
assert_eq!(
BinaryNumberRepr::to_f64_lossy(bits),
x,
"{:e} misencoded inline",
x
);
}
}
}
#[test]
fn inline_floats_are_always_exact_f64() {
let bits = BinaryNumberRepr::encode(1, BinaryNumberRepr::exp_code(-1, true));
assert_eq!(BinaryNumberRepr::to_f64_exact(bits), Some(0.5));
let nv = BinaryNumberRepr::num_val(bits);
assert_eq!(nv.to_i64(), None); assert_eq!(nv.to_f64(), Some(0.5)); }
}