#![cfg(feature = "compact")]
#![doc(hidden)]
use crate::float::{ExtendedFloat80, RawFloat};
use crate::options::Options;
use crate::shared;
use crate::table::GRISU_POWERS_OF_TEN;
use core::mem;
use lexical_util::algorithm::rtrim_char_count;
#[cfg(feature = "f16")]
use lexical_util::bf16::bf16;
use lexical_util::digit::digit_to_char_const;
#[cfg(feature = "f16")]
use lexical_util::f16::f16;
use lexical_util::format::NumberFormat;
use lexical_util::num::{AsPrimitive, Float};
pub unsafe fn write_float<F: RawFloat, const FORMAT: u128>(
float: F,
bytes: &mut [u8],
options: &Options,
) -> usize {
let format = NumberFormat::<{ FORMAT }> {};
assert!(format.is_valid());
debug_assert!(!float.is_special());
debug_assert!(float >= F::ZERO);
let digits: mem::MaybeUninit<[u8; 32]> = mem::MaybeUninit::uninit();
let mut digits = unsafe { digits.assume_init() };
let (digit_count, kappa, carried) = if float == F::ZERO {
unsafe { index_unchecked_mut!(digits[0]) = b'0' };
(1, 0, false)
} else {
unsafe {
let (start, k) = grisu(float, &mut digits);
let (end, carried) = shared::truncate_and_round_decimal(&mut digits, start, options);
(end, k + start as i32 - end as i32, carried)
}
};
let sci_exp = kappa + digit_count as i32 - 1 + carried as i32;
write_float!(
FORMAT,
sci_exp,
options,
write_float_scientific,
write_float_positive_exponent,
write_float_negative_exponent,
args => bytes, &mut digits, digit_count, sci_exp, options,
)
}
#[allow(clippy::comparison_chain)]
pub unsafe fn write_float_scientific<const FORMAT: u128>(
bytes: &mut [u8],
digits: &mut [u8],
digit_count: usize,
sci_exp: i32,
options: &Options,
) -> usize {
debug_assert!(rtrim_char_count(&digits[..digit_count], b'0') == 0 || digit_count == 1);
debug_assert!(digit_count <= 20);
let format = NumberFormat::<{ FORMAT }> {};
assert!(format.is_valid());
let decimal_point = options.decimal_point();
let exact_count = shared::min_exact_digits(digit_count, options);
let mut cursor: usize;
unsafe {
index_unchecked_mut!(bytes[0] = digits[0]);
index_unchecked_mut!(bytes[1]) = decimal_point;
if !format.no_exponent_without_fraction() && digit_count == 1 && options.trim_floats() {
cursor = 1;
} else if digit_count < exact_count {
let src = index_unchecked!(digits[1..digit_count]).as_ptr();
let dst = &mut index_unchecked_mut!(bytes[2..digit_count + 1]);
copy_nonoverlapping_unchecked!(dst, src, digit_count - 1);
cursor = digit_count + 1;
let zeros = exact_count - digit_count;
slice_fill_unchecked!(index_unchecked_mut!(bytes[cursor..cursor + zeros]), b'0');
cursor += zeros;
} else if digit_count == 1 {
index_unchecked_mut!(bytes[2]) = b'0';
cursor = 3;
} else {
let src = index_unchecked!(digits[1..digit_count]).as_ptr();
let dst = &mut index_unchecked_mut!(bytes[2..digit_count + 1]);
copy_nonoverlapping_unchecked!(dst, src, digit_count - 1);
cursor = digit_count + 1;
}
}
unsafe { shared::write_exponent::<FORMAT>(bytes, &mut cursor, sci_exp, options.exponent()) };
cursor
}
#[allow(clippy::comparison_chain)]
pub unsafe fn write_float_negative_exponent<const FORMAT: u128>(
bytes: &mut [u8],
digits: &mut [u8],
digit_count: usize,
sci_exp: i32,
options: &Options,
) -> usize {
debug_assert!(rtrim_char_count(&digits[..digit_count], b'0') == 0);
debug_assert!(digit_count <= 20);
debug_assert!(sci_exp < 0);
let decimal_point = options.decimal_point();
let sci_exp = sci_exp.wrapping_neg() as usize;
unsafe {
index_unchecked_mut!(bytes[0]) = b'0';
index_unchecked_mut!(bytes[1]) = decimal_point;
let digits = &mut index_unchecked_mut!(bytes[2..sci_exp + 1]);
slice_fill_unchecked!(digits, b'0');
}
let mut cursor = sci_exp + 1;
unsafe {
let src = digits.as_ptr();
let dst = &mut index_unchecked_mut!(bytes[cursor..cursor + digit_count]);
copy_nonoverlapping_unchecked!(dst, src, digit_count);
cursor += digit_count;
}
let exact_count = shared::min_exact_digits(digit_count, options);
if digit_count < exact_count {
let zeros = exact_count - digit_count;
unsafe {
slice_fill_unchecked!(index_unchecked_mut!(bytes[cursor..cursor + zeros]), b'0');
}
cursor += zeros;
}
cursor
}
pub unsafe fn write_float_positive_exponent<const FORMAT: u128>(
bytes: &mut [u8],
digits: &mut [u8],
mut digit_count: usize,
sci_exp: i32,
options: &Options,
) -> usize {
debug_assert!(rtrim_char_count(&digits[..digit_count], b'0') == 0 || digit_count == 1);
debug_assert!(digit_count <= 20);
debug_assert!(sci_exp >= 0);
let decimal_point = options.decimal_point();
let leading_digits = sci_exp as usize + 1;
let mut cursor: usize;
let mut trimmed = false;
if leading_digits >= digit_count {
unsafe {
let src = digits.as_ptr();
let dst = &mut index_unchecked_mut!(bytes[..digit_count]);
copy_nonoverlapping_unchecked!(dst, src, digit_count);
let digits = &mut index_unchecked_mut!(bytes[digit_count..leading_digits]);
slice_fill_unchecked!(digits, b'0');
}
cursor = leading_digits;
digit_count = leading_digits;
if !options.trim_floats() {
unsafe { index_unchecked_mut!(bytes[cursor]) = decimal_point };
cursor += 1;
unsafe { index_unchecked_mut!(bytes[cursor]) = b'0' };
cursor += 1;
digit_count += 1;
} else {
trimmed = true;
}
} else {
unsafe {
let src = digits.as_ptr();
let dst = &mut index_unchecked_mut!(bytes[..leading_digits]);
copy_nonoverlapping_unchecked!(dst, src, leading_digits);
index_unchecked_mut!(bytes[leading_digits]) = decimal_point;
}
unsafe {
let src = index_unchecked!(digits[leading_digits..digit_count]).as_ptr();
let dst = &mut index_unchecked_mut!(bytes[leading_digits + 1..digit_count + 1]);
copy_nonoverlapping_unchecked!(dst, src, digit_count - leading_digits);
}
cursor = digit_count + 1;
}
let exact_count = shared::min_exact_digits(digit_count, options);
if !trimmed && exact_count > digit_count {
let zeros = exact_count - digit_count;
unsafe {
let digits = &mut index_unchecked_mut!(bytes[cursor..cursor + zeros]);
slice_fill_unchecked!(digits, b'0');
}
cursor += zeros;
}
cursor
}
unsafe fn round_digit(
digits: &mut [u8],
digit_count: usize,
delta: u64,
mut rem: u64,
kappa: u64,
mant: u64,
) {
debug_assert!((1..=digits.len()).contains(&digit_count));
while rem < mant
&& delta - rem >= kappa
&& (rem + kappa < mant || mant - rem > rem + kappa - mant)
{
unsafe { index_unchecked_mut!(digits[digit_count - 1]) -= 1 };
rem += kappa;
}
}
pub unsafe fn generate_digits(
fp: &ExtendedFloat80,
upper: &ExtendedFloat80,
lower: &ExtendedFloat80,
digits: &mut [u8],
mut k: i32,
) -> (usize, i32) {
debug_assert!(fp.mant != 0);
let wmant = upper.mant - fp.mant;
let mut delta = upper.mant - lower.mant;
let one = ExtendedFloat80 {
mant: 1 << -upper.exp,
exp: upper.exp,
};
let mut part1 = upper.mant >> -one.exp;
let mut part2 = upper.mant & (one.mant - 1);
let mut idx: usize = 0;
let mut kappa: i32 = 10;
let mut div = 1000000000;
while kappa > 0 {
let digit = part1 / div;
if digit != 0 || idx != 0 {
unsafe { index_unchecked_mut!(digits[idx]) = digit_to_char_const(digit as u32, 10) };
idx += 1;
}
part1 -= digit as u64 * div;
kappa -= 1;
let tmp = (part1 << -one.exp) + part2;
if tmp <= delta {
k += kappa;
unsafe { round_digit(digits, idx, delta, tmp, div << -one.exp, wmant) };
return (idx, k);
}
div /= 10;
}
let mut ten = 10;
loop {
part2 *= 10;
delta *= 10;
kappa -= 1;
let digit = part2 >> -one.exp;
if digit != 0 || idx != 0 {
unsafe { index_unchecked_mut!(digits[idx]) = digit_to_char_const(digit as u32, 10) };
idx += 1;
}
part2 &= one.mant - 1;
if part2 < delta {
k += kappa;
unsafe { round_digit(digits, idx, delta, part2, one.mant, wmant * ten) };
return (idx, k);
}
ten *= 10;
}
}
pub unsafe fn grisu<F: Float>(float: F, digits: &mut [u8]) -> (usize, i32) {
debug_assert!(float != F::ZERO);
let mut w = from_float(float);
let (lower, upper) = normalized_boundaries::<F>(&w);
normalize(&mut w);
let (cp, ki) = unsafe { cached_grisu_power(upper.exp) };
let w = mul(&w, &cp);
let mut upper = mul(&upper, &cp);
let mut lower = mul(&lower, &cp);
lower.mant += 1;
upper.mant -= 1;
let k = -ki;
unsafe { generate_digits(&w, &upper, &lower, digits, k) }
}
pub fn from_float<F: Float>(float: F) -> ExtendedFloat80 {
ExtendedFloat80 {
mant: float.mantissa().as_u64(),
exp: float.exponent(),
}
}
pub fn normalize(fp: &mut ExtendedFloat80) {
if fp.mant != 0 {
let shift = fp.mant.leading_zeros() as i32;
fp.mant <<= shift;
fp.exp -= shift;
}
}
pub fn normalized_boundaries<F: Float>(fp: &ExtendedFloat80) -> (ExtendedFloat80, ExtendedFloat80) {
let mut upper = ExtendedFloat80 {
mant: (fp.mant << 1) + 1,
exp: fp.exp - 1,
};
normalize(&mut upper);
let is_hidden = fp.mant == F::HIDDEN_BIT_MASK.as_u64();
let l_shift: i32 = is_hidden as i32 + 1;
let mut lower = ExtendedFloat80 {
mant: (fp.mant << l_shift) - 1,
exp: fp.exp - l_shift,
};
lower.mant <<= lower.exp - upper.exp;
lower.exp = upper.exp;
(lower, upper)
}
pub fn mul(x: &ExtendedFloat80, y: &ExtendedFloat80) -> ExtendedFloat80 {
debug_assert!(x.mant >> 32 != 0);
debug_assert!(y.mant >> 32 != 0);
const LOMASK: u64 = u32::MAX as u64;
let x1 = x.mant >> 32;
let x0 = x.mant & LOMASK;
let y1 = y.mant >> 32;
let y0 = y.mant & LOMASK;
let x1_y0 = x1 * y0;
let x0_y1 = x0 * y1;
let x0_y0 = x0 * y0;
let x1_y1 = x1 * y1;
let mut tmp = (x1_y0 & LOMASK) + (x0_y1 & LOMASK) + (x0_y0 >> 32);
tmp += 1 << (32 - 1);
ExtendedFloat80 {
mant: x1_y1 + (x1_y0 >> 32) + (x0_y1 >> 32) + (tmp >> 32),
exp: x.exp + y.exp + 64,
}
}
unsafe fn cached_grisu_power(exp: i32) -> (ExtendedFloat80, i32) {
debug_assert!(((-1075 - 64 - 1)..=(1024 + 64 + 1)).contains(&exp));
const ONE_LOG_TEN: f64 = 0.30102999566398114;
const NPOWERS: i32 = 87;
const FIRSTPOWER: i32 = -348; const STEPPOWERS: i32 = 8;
const EXPMAX: i32 = -32;
const EXPMIN: i32 = -60;
let approx = -((exp + NPOWERS) as f64) * ONE_LOG_TEN;
let approx = approx as i32;
let mut idx = ((approx - FIRSTPOWER) / STEPPOWERS) as usize;
loop {
let mant = unsafe { f64::grisu_power(idx) };
let decexp = fast_decimal_power(idx);
let binexp = fast_binary_power(decexp);
let current = exp + binexp + 64;
if current < EXPMIN {
idx += 1;
continue;
}
if current > EXPMAX {
idx -= 1;
continue;
}
let k = FIRSTPOWER + idx as i32 * STEPPOWERS;
let power = ExtendedFloat80 {
mant,
exp: binexp,
};
return (power, k);
}
}
fn fast_binary_power(q: i32) -> i32 {
(q.wrapping_mul(152_170 + 65536) >> 16) - 63
}
fn fast_decimal_power(index: usize) -> i32 {
index as i32 * 8 - 348
}
pub trait GrisuFloat: Float {
#[inline(always)]
unsafe fn grisu_power(index: usize) -> u64 {
debug_assert!(index <= GRISU_POWERS_OF_TEN.len());
unsafe { index_unchecked!(GRISU_POWERS_OF_TEN[index]) }
}
}
macro_rules! grisu_impl {
($($t:ident)*) => ($(
impl GrisuFloat for $t {}
)*);
}
grisu_impl! { f32 f64 }
#[cfg(feature = "f16")]
macro_rules! grisu_unimpl {
($($t:ident)*) => ($(
impl GrisuFloat for $t {
#[inline(always)]
unsafe fn grisu_power(_: usize) -> u64 {
unimplemented!()
}
}
)*);
}
#[cfg(feature = "f16")]
grisu_unimpl! { bf16 f16 }