#![cfg(feature = "radix")]
#![doc(hidden)]
use crate::options::{Options, RoundMode};
use crate::shared;
use core::mem;
use lexical_util::algorithm::{ltrim_char_count, rtrim_char_count};
use lexical_util::constants::{FormattedSize, BUFFER_SIZE};
use lexical_util::digit::{char_to_digit_const, digit_to_char_const};
use lexical_util::format::NumberFormat;
use lexical_util::num::Float;
use lexical_write_integer::write::WriteInteger;
#[allow(clippy::collapsible_if)]
pub unsafe fn write_float<F: Float, const FORMAT: u128>(
float: F,
bytes: &mut [u8],
options: &Options,
) -> usize
where
<F as Float>::Unsigned: WriteInteger + FormattedSize,
{
let format = NumberFormat::<{ FORMAT }> {};
assert!(format.is_valid());
debug_assert!(!float.is_special());
debug_assert!(float >= F::ZERO);
debug_assert!(F::BITS <= 64);
debug_assert!(format.mantissa_radix() == format.exponent_base());
const SIZE: usize = 2200;
let buffer: mem::MaybeUninit<[u8; SIZE]> = mem::MaybeUninit::uninit();
let mut buffer = unsafe { buffer.assume_init() };
let initial_cursor: usize = SIZE / 2;
let mut integer_cursor = initial_cursor;
let mut fraction_cursor = initial_cursor;
let base = F::as_cast(format.radix());
let mut integer = float.floor();
let mut fraction = float - integer;
let mut delta = if float.to_bits() == F::MAX.to_bits() {
F::as_cast(0.5) * (float - float.prev_positive())
} else {
F::as_cast(0.5) * (float.next_positive() - float)
};
delta = F::ZERO.next_positive().max_finite(delta);
debug_assert!(delta > F::ZERO);
if fraction > delta {
loop {
fraction *= base;
delta *= base;
let digit = fraction.as_u32();
let c = digit_to_char_const(digit, format.radix());
unsafe { index_unchecked_mut!(buffer[fraction_cursor]) = c };
fraction_cursor += 1;
fraction -= F::as_cast(digit);
if fraction > F::as_cast(0.5) || (fraction == F::as_cast(0.5) && (digit & 1) != 0) {
if fraction + delta > F::ONE {
loop {
fraction_cursor -= 1;
if fraction_cursor == initial_cursor - 1 {
integer += F::ONE;
break;
}
let c = unsafe { index_unchecked!(buffer[fraction_cursor]) };
if let Some(digit) = char_to_digit_const(c, format.radix()) {
let idx = digit + 1;
let c = digit_to_char_const(idx, format.radix());
unsafe { index_unchecked_mut!(buffer[fraction_cursor]) = c };
fraction_cursor += 1;
break;
}
}
break;
}
}
if delta >= fraction {
break;
}
}
}
while (integer / base).exponent() > 0 {
integer /= base;
integer_cursor -= 1;
unsafe { index_unchecked_mut!(buffer[integer_cursor]) = b'0' };
}
loop {
let remainder = integer % base;
integer_cursor -= 1;
let idx = remainder.as_u32();
let c = digit_to_char_const(idx, format.radix());
unsafe { index_unchecked_mut!(buffer[integer_cursor]) = c };
integer = (integer - remainder) / base;
if integer <= F::ZERO {
break;
}
}
let digits = unsafe { &index_unchecked!(buffer[integer_cursor..fraction_cursor]) };
let zero_count = ltrim_char_count(digits, b'0');
let sci_exp: i32 = initial_cursor as i32 - integer_cursor as i32 - zero_count as i32 - 1;
write_float!(
FORMAT,
sci_exp,
options,
write_float_scientific,
write_float_nonscientific,
write_float_nonscientific,
args => sci_exp, &mut buffer, bytes, initial_cursor,
integer_cursor, fraction_cursor, options,
)
}
#[inline]
pub unsafe fn write_float_scientific<const FORMAT: u128>(
sci_exp: i32,
buffer: &mut [u8],
bytes: &mut [u8],
initial_cursor: usize,
integer_cursor: usize,
fraction_cursor: usize,
options: &Options,
) -> usize {
debug_assert!(bytes.len() >= BUFFER_SIZE);
let format = NumberFormat::<{ FORMAT }> {};
assert!(format.is_valid());
let decimal_point = options.decimal_point();
let start: usize = if sci_exp <= 0 {
((initial_cursor as i32) - sci_exp - 1) as usize
} else {
integer_cursor
};
let end = fraction_cursor.min(start + MAX_DIGIT_LENGTH + 1);
let (digit_count, carried) =
unsafe { truncate_and_round(buffer, start, end, format.radix(), options) };
let digits = unsafe { &index_unchecked!(buffer[start..start + digit_count]) };
let sci_exp = sci_exp + carried as i32;
let digit_count = unsafe {
index_unchecked_mut!(bytes[0] = digits[0]);
index_unchecked_mut!(bytes[1]) = decimal_point;
let src = digits.as_ptr().add(1);
let dst = &mut index_unchecked_mut!(bytes[2..digit_count + 1]);
copy_nonoverlapping_unchecked!(dst, src, digit_count - 1);
let zeros = rtrim_char_count(&index_unchecked!(bytes[2..digit_count + 1]), b'0');
digit_count - zeros
};
let mut cursor = digit_count + 1;
let exact_count = shared::min_exact_digits(digit_count, options);
if !format.no_exponent_without_fraction() && cursor == 2 && options.trim_floats() {
cursor -= 1;
} else if exact_count < 2 {
unsafe { index_unchecked_mut!(bytes[cursor]) = b'0' };
cursor += 1;
} else if exact_count > digit_count {
let digits_end = exact_count + 1;
unsafe {
slice_fill_unchecked!(index_unchecked_mut!(bytes[cursor..digits_end]), b'0');
}
cursor = digits_end;
}
unsafe { shared::write_exponent::<FORMAT>(bytes, &mut cursor, sci_exp, options.exponent()) };
cursor
}
#[inline]
pub unsafe fn write_float_nonscientific<const FORMAT: u128>(
_: i32,
buffer: &mut [u8],
bytes: &mut [u8],
initial_cursor: usize,
integer_cursor: usize,
fraction_cursor: usize,
options: &Options,
) -> usize {
debug_assert!(bytes.len() >= BUFFER_SIZE);
let format = NumberFormat::<{ FORMAT }> {};
assert!(format.is_valid());
let decimal_point = options.decimal_point();
let mut start = integer_cursor;
let end = fraction_cursor.min(start + MAX_DIGIT_LENGTH + 1);
let (mut digit_count, carried) =
unsafe { truncate_and_round(buffer, start, end, format.radix(), options) };
if carried {
debug_assert!(digit_count == 1);
start -= 1;
unsafe { index_unchecked_mut!(buffer[start]) = b'1' };
}
let digits = unsafe { &index_unchecked!(buffer[start..start + digit_count]) };
let integer_length = initial_cursor - start;
let integer_count = digit_count.min(integer_length);
unsafe {
let src = digits.as_ptr();
let dst = &mut index_unchecked_mut!(bytes[..integer_count]);
copy_nonoverlapping_unchecked!(dst, src, integer_count);
}
if integer_count < integer_length {
unsafe {
let digits = &mut index_unchecked_mut!(bytes[integer_count..integer_length]);
slice_fill_unchecked!(digits, b'0');
}
}
let mut cursor = integer_length;
unsafe { index_unchecked_mut!(bytes[cursor]) = decimal_point };
cursor += 1;
let digits = unsafe { &index_unchecked!(digits[integer_count..]) };
let fraction_count = digit_count.saturating_sub(integer_length);
if fraction_count > 0 {
unsafe {
let src = digits.as_ptr();
let end = cursor + fraction_count;
let dst = &mut index_unchecked_mut!(bytes[cursor..end]);
copy_nonoverlapping_unchecked!(dst, src, fraction_count);
let zeros = rtrim_char_count(&index_unchecked!(bytes[cursor..end]), b'0');
cursor += fraction_count - zeros;
}
} else if options.trim_floats() {
cursor -= 1;
} else {
unsafe { index_unchecked_mut!(bytes[cursor]) = b'0' };
cursor += 1;
digit_count += 1;
}
let exact_count = shared::min_exact_digits(digit_count, options);
if (fraction_count > 0 || !options.trim_floats()) && exact_count > digit_count {
let digits_end = cursor + exact_count - digit_count;
unsafe {
slice_fill_unchecked!(index_unchecked_mut!(bytes[cursor..digits_end]), b'0');
}
cursor = digits_end;
}
cursor
}
const MAX_NONDIGIT_LENGTH: usize = 25;
const MAX_DIGIT_LENGTH: usize = BUFFER_SIZE - MAX_NONDIGIT_LENGTH;
#[inline]
#[allow(clippy::comparison_chain)]
pub unsafe fn truncate_and_round(
buffer: &mut [u8],
start: usize,
end: usize,
radix: u32,
options: &Options,
) -> (usize, bool) {
debug_assert!(end >= start);
debug_assert!(end <= buffer.len());
let digit_count = end - start;
let max_digits = if let Some(digits) = options.max_significant_digits() {
digits.get()
} else {
return (digit_count, false);
};
if max_digits >= digit_count {
return (digit_count, false);
}
if options.round_mode() == RoundMode::Truncate {
return (max_digits, false);
}
let max_digits = {
let digits = unsafe { &mut index_unchecked_mut!(buffer[start..start + max_digits]) };
max_digits + ltrim_char_count(digits, b'0')
};
let last = unsafe { index_unchecked!(buffer[start + max_digits - 1]) };
let first = unsafe { index_unchecked!(buffer[start + max_digits]) };
let halfway = digit_to_char_const(radix / 2, radix);
let rem = radix % 2;
if first < halfway {
(max_digits, false)
} else if first > halfway {
let digits = unsafe { &mut index_unchecked_mut!(buffer[start..start + max_digits]) };
unsafe { shared::round_up(digits, max_digits, radix) }
} else if rem == 0 {
let truncated = unsafe { &index_unchecked!(buffer[start + max_digits + 1..end]) };
if truncated.iter().all(|&x| x == b'0') && last & 1 == 0 {
(max_digits, false)
} else {
let digits = unsafe { &mut index_unchecked_mut!(buffer[start..start + max_digits]) };
unsafe { shared::round_up(digits, max_digits, radix) }
}
} else {
let truncated = unsafe { &index_unchecked!(buffer[start + max_digits + 1..end]) };
for &c in truncated.iter() {
if c < halfway {
return (max_digits, false);
} else if c > halfway {
let digits =
unsafe { &mut index_unchecked_mut!(buffer[start..start + max_digits]) };
return unsafe { shared::round_up(digits, max_digits, radix) };
}
}
(max_digits, false)
}
}