#![allow(clippy::float_cmp)]
#![cfg_attr(not(feature = "arbitrary_precision"), allow(dead_code))]
use std::cmp::Ordering;
use std::convert::TryFrom;
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::{
decimal_to_f64_exact, decimal_to_f64_lossy, 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;
const _: () = {
let min_exp = -(EXP_BIAS as i64);
let max_exp = EXP_BIAS as i64;
let max_mantissa_digits = decimal_digits_of_pow2(MANTISSA_BITS - 1);
assert!(min_exp >= super::super::DECIMAL_MIN_EXP);
assert!(max_mantissa_digits + max_exp <= super::super::DECIMAL_MAX_MAGNITUDE);
};
const fn decimal_digits_of_pow2(exp2: u32) -> i64 {
let mut value: u128 = 1 << exp2;
let mut digits = 1;
while value >= 10 {
value /= 10;
digits += 1;
}
digits
}
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)
}
}
const POW5: [u128; 8] = [1, 5, 25, 125, 625, 3125, 15625, 78125];
fn shl_checked(x: u128, n: u32) -> Option<u128> {
if x == 0 {
Some(0)
} else if n <= x.leading_zeros() {
Some(x << n)
} else {
None
}
}
fn f64_as_integer(m: u64, e2: i32, neg: bool) -> Option<i128> {
let mag: u128 = if e2 >= 0 {
shl_checked(u128::from(m), e2 as u32)?
} else {
let sh = (-e2) as u32;
if sh >= 64 || m & ((1u64 << sh) - 1) != 0 {
return None;
}
u128::from(m >> sh)
};
let mag = i128::try_from(mag).ok()?;
Some(if neg { -mag } else { mag })
}
fn f64_scaled_integer(m: u64, e2: i32, neg: bool, k: u32) -> Option<i128> {
let e = e2 + k as i32;
let mag: u128 = if e >= 0 {
shl_checked(u128::from(m).checked_mul(POW5[k as usize])?, e as u32)?
} else {
let sh = (-e) as u32;
if sh >= 64 || m & ((1u64 << sh) - 1) != 0 {
return None;
}
u128::from(m >> sh).checked_mul(POW5[k as usize])?
};
let mag = i128::try_from(mag).ok()?;
Some(if neg { -mag } else { mag })
}
pub(crate) struct DecimalNumberRepr;
impl DecimalNumberRepr {
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 encode_int_float(value: i128) -> Option<usize> {
let mut m = value;
let mut exp = 0i32;
loop {
if Self::fits_mantissa(m) {
return Some(Self::encode(m as i64, Self::exp_code(exp, true)));
}
if exp >= 7 || m % 10 != 0 {
return None;
}
m /= 10;
exp += 1;
}
}
fn encode_decimal(mantissa: i128, exp: i32) -> Option<usize> {
if mantissa == 0 {
return Some(Self::encode(0, Self::exp_code(0, true)));
}
let mut m = mantissa;
let mut e = exp;
while m % 10 == 0 {
m /= 10;
e += 1;
}
if e >= 0 {
Self::encode_int_float(m.checked_mul(10i128.checked_pow(e as u32)?)?)
} else {
(Self::fits_mantissa(m) && e >= -EXP_BIAS)
.then(|| Self::encode(m as i64, Self::exp_code(e, true)))
}
}
fn parse_decimal(s: &str) -> Option<(i128, i32)> {
let b = s.as_bytes();
let mut i = 0;
let neg = b[0] == b'-';
if neg {
i += 1;
}
let mut mantissa: i128 = 0;
let mut frac_digits: i32 = 0;
let mut seen_point = false;
let mut explicit_exp: i32 = 0;
while i < b.len() {
match b[i] {
c @ b'0'..=b'9' => {
mantissa = mantissa
.checked_mul(10)?
.checked_add(i128::from(c - b'0'))?;
if seen_point {
frac_digits += 1;
}
i += 1;
}
b'.' => {
seen_point = true;
i += 1;
}
b'e' | b'E' => {
i += 1;
let exp_neg = b[i] == b'-';
if b[i] == b'+' || b[i] == b'-' {
i += 1;
}
let mut e: i32 = 0;
while i < b.len() {
e = e.checked_mul(10)?.checked_add(i32::from(b[i] - b'0'))?;
i += 1;
}
explicit_exp = if exp_neg { -e } else { e };
break;
}
_ => return None,
}
}
let exp = explicit_exp.checked_sub(frac_digits)?;
Some((if neg { -mantissa } else { mantissa }, exp))
}
fn to_f64_exact(bits: usize) -> Option<f64> {
let (m, exp) = Self::decode(bits);
decimal_to_f64_exact(m, exp)
}
fn to_f64_lossy(bits: usize) -> f64 {
let (m, exp) = Self::decode(bits);
decimal_to_f64_lossy(m, exp)
}
fn has_decimal_point(bits: usize) -> bool {
Self::code_has_dot(Self::code(bits))
}
fn num_val(bits: usize) -> NumVal<'static> {
let (mantissa, exp) = Self::decode(bits);
NumVal::from_decimal(mantissa, exp)
}
}
impl InlineNumber for DecimalNumberRepr {
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 (m, e2, neg) = integer_decode(value);
if let Some(int) = f64_as_integer(m, e2, neg) {
return Self::encode_int_float(int);
}
for k in 1..=7u32 {
if let Some(d) = f64_scaled_integer(m, e2, neg, k) {
return if Self::fits_mantissa(d) {
Some(Self::encode(d as i64, Self::exp_code(-(k as i32), true)))
} else {
None
};
}
}
None
}
fn from_str(s: &str) -> Result<usize, InlineNumberError> {
from_str_with(s, Self::encode_int, |s| {
Self::parse_decimal(s).and_then(|(m, e)| Self::encode_decimal(m, e))
})
}
}
impl InlineValue for DecimalNumberRepr {
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 = DecimalNumberRepr::exp_code(exp, true);
assert_ne!(c, INT_EXP0_CODE);
assert_eq!(DecimalNumberRepr::code_exp(c), exp);
assert!(
DecimalNumberRepr::code_has_dot(c),
"dot value exp {} -> code {}",
exp,
c
);
}
assert_eq!(DecimalNumberRepr::exp_code(0, false), INT_EXP0_CODE);
assert_eq!(DecimalNumberRepr::code_exp(INT_EXP0_CODE), 0);
assert!(!DecimalNumberRepr::code_has_dot(INT_EXP0_CODE));
assert_ne!(
DecimalNumberRepr::exp_code(0, false),
DecimalNumberRepr::exp_code(0, true)
);
assert_eq!(
DecimalNumberRepr::encode_int(0),
Some(DecimalNumberRepr::encode(0, INT_EXP0_CODE))
);
assert_ne!(DecimalNumberRepr::encode_int(0), Some(0));
assert_ne!(DecimalNumberRepr::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) = DecimalNumberRepr::encode_f64(x) {
assert_eq!(
DecimalNumberRepr::to_f64_lossy(bits),
x,
"{:e} misencoded inline",
x
);
}
}
}
#[test]
fn num_val_is_total_over_the_inline_domain() {
let bits = DecimalNumberRepr::encode(1, DecimalNumberRepr::exp_code(-1, true)); assert!(
DecimalNumberRepr::to_f64_exact(bits).is_none(),
"0.1 is not exactly representable as f64"
);
let nv = DecimalNumberRepr::num_val(bits);
assert_eq!(nv.to_i64(), None);
assert_eq!(nv.to_f64(), None);
assert_eq!(nv.to_f64_lossy(), 0.1);
}
}