use crate::NiceWrapper;
const SIZE: usize = 36;
const MIN_OVERFLOW_FROM: usize = SIZE - 29;
const MAX_OVERFLOW_FROM: usize = SIZE - 28;
const IDX_DOT: usize = 27;
const PRECISION: u32 = 100_000_000;
macro_rules! inner {
($sep:expr) => ([b' ', b'0', b'0', $sep, b'0', b'0', b'0', $sep, b'0', b'0', b'0', $sep, b'0', b'0', b'0', $sep, b'0', b'0', b'0', $sep, b'0', b'0', b'0', $sep, b'0', b'0', b'0', b'.', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0']);
}
pub type NiceFloat = NiceWrapper<SIZE>;
impl Default for NiceFloat {
fn default() -> Self {
Self {
inner: inner!(b','),
from: IDX_DOT - 1,
}
}
}
impl From<f32> for NiceFloat {
fn from(num: f32) -> Self { Self::from(FloatKind::from(num)) }
}
impl From<f64> for NiceFloat {
fn from(num: f64) -> Self { Self::from(FloatKind::from(num)) }
}
impl From<FloatKind> for NiceFloat {
fn from(kind: FloatKind) -> Self {
match kind {
FloatKind::NaN => Self::nan(),
FloatKind::Zero => Self::default(),
FloatKind::Normal(top, bottom, neg) => {
let mut out = Self::default();
out.parse_top(top, neg);
out.parse_bottom(bottom);
out
},
FloatKind::Overflow(neg) => Self::overflow(neg),
FloatKind::Infinity => Self::infinity(),
}
}
}
impl NiceFloat {
#[must_use]
pub const fn infinity() -> Self {
Self {
inner: [
b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0',
b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0',
b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0',
b'0', b'0', b'0', 226, 136, 158,
],
from: SIZE - 3,
}
}
#[must_use]
pub const fn nan() -> Self {
Self {
inner: *b"000000000000000000000000000000000NaN",
from: SIZE - 3,
}
}
#[must_use]
pub const fn overflow(neg: bool) -> Self {
if neg {
Self {
inner: *b"0000000< -18,446,744,073,709,551,615",
from: MIN_OVERFLOW_FROM,
}
}
else {
Self {
inner: *b"00000000> 18,446,744,073,709,551,615",
from: MAX_OVERFLOW_FROM,
}
}
}
#[must_use]
pub fn with_separator(num: f64, sep: u8, point: u8) -> Self {
assert!(sep.is_ascii(), "Invalid separator.");
assert!(point.is_ascii(), "Invalid decimal point.");
match FloatKind::from(num) {
FloatKind::NaN => Self::nan(),
FloatKind::Zero => {
let mut out = Self::default();
out.inner[IDX_DOT] = point;
out
},
FloatKind::Normal(top, bottom, neg) => {
let mut out = Self {
inner: inner!(sep),
from: IDX_DOT - 1,
};
out.inner[IDX_DOT] = point;
out.parse_top(top, neg);
out.parse_bottom(bottom);
out
},
FloatKind::Overflow(neg) => {
let mut out = Self::overflow(neg);
if sep != b',' {
for b in &mut out.inner {
if b','.eq(b) { *b = sep; }
}
}
out
},
FloatKind::Infinity => Self::infinity(),
}
}
}
impl NiceFloat {
#[must_use]
pub fn compact_bytes(&self) -> &[u8] {
let mut out = self.as_bytes();
if self.from < IDX_DOT {
let mut idx: u8 = 0;
while let [rest @ .., last] = out {
if idx == 8 {
out = rest;
break;
}
else if b'0'.eq(last) {
out = rest;
}
else { break; }
idx += 1;
}
}
out
}
#[allow(unsafe_code)]
#[must_use]
pub fn compact_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(self.compact_bytes()) }
}
#[must_use]
pub fn precise_bytes(&self, precision: usize) -> &[u8] {
if precision < 8 && self.has_dot() {
if precision == 0 { &self.inner[self.from..IDX_DOT] }
else { &self.inner[self.from..=IDX_DOT + precision] }
}
else { self.as_bytes() }
}
#[allow(unsafe_code)]
#[must_use]
pub fn precise_str(&self, precision: usize) -> &str {
unsafe { std::str::from_utf8_unchecked(self.precise_bytes(precision)) }
}
}
impl NiceFloat {
const fn has_dot(&self) -> bool {
self.from < IDX_DOT &&
! (
self.from == MIN_OVERFLOW_FROM &&
self.inner[MIN_OVERFLOW_FROM] == b'<'
) &&
! (
self.from == MAX_OVERFLOW_FROM &&
self.inner[MAX_OVERFLOW_FROM] == b'>'
)
}
#[allow(unsafe_code)]
#[allow(clippy::cast_possible_truncation)] fn parse_top(&mut self, mut top: u64, neg: bool) {
if 0 != top {
self.from = IDX_DOT;
let ptr = self.inner.as_mut_ptr();
while 999 < top {
let (div, rem) = crate::div_mod(top, 1000);
self.from -= 4;
unsafe { super::write_u8_3(ptr.add(self.from + 1), rem as u16); }
top = div;
}
if 99 < top {
self.from -= 3;
unsafe { super::write_u8_3(ptr.add(self.from), top as u16); }
}
else if 9 < top {
self.from -= 2;
unsafe {
std::ptr::copy_nonoverlapping(
crate::double_ptr(top as usize),
ptr.add(self.from),
2
);
}
}
else {
self.from -= 1;
unsafe { std::ptr::write(ptr.add(self.from), top as u8 + b'0'); }
}
if neg {
self.from -= 1;
unsafe {
std::ptr::write(ptr.add(self.from), b'-');
}
}
}
}
#[allow(unsafe_code)]
fn parse_bottom(&mut self, mut bottom: u32) {
if 0 != bottom {
let ptr = self.inner.as_mut_ptr();
let mut divisor = 1_000_000_u32;
let mut idx = IDX_DOT + 1;
for _ in 0..4 {
let (a, b) = crate::div_mod(bottom, divisor);
if 0 != a {
unsafe {
std::ptr::copy_nonoverlapping(
crate::double_ptr(a as usize),
ptr.add(idx),
2,
);
}
}
if 0 == b { break; }
bottom = b;
divisor /= 100;
idx += 2;
}
}
}
}
#[derive(Debug, Clone, Copy, Default, Eq, Hash, PartialEq)]
pub enum FloatKind {
NaN,
#[default]
Zero,
Normal(u64, u32, bool),
Overflow(bool),
Infinity,
}
impl From<f32> for FloatKind {
#[allow(clippy::integer_division)]
fn from(num: f32) -> Self {
if num.is_nan() { Self::NaN }
else if num.is_infinite() { Self::Infinity }
else { parse_finite_f32(num) }
}
}
impl From<f64> for FloatKind {
#[allow(clippy::integer_division)]
fn from(num: f64) -> Self {
if num.is_nan() { Self::NaN }
else if num.is_infinite() { Self::Infinity }
else { parse_finite_f64(num) }
}
}
#[allow(clippy::cast_lossless)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::integer_division)]
fn parse_finite_f32(num: f32) -> FloatKind {
const MIN_EXP: i16 = 1 - (1 << 8) / 2;
const MANT_MASK: u32 = (1 << 23) - 1;
const EXP_MASK: u32 = (1 << 8) - 1;
let bits = num.abs().to_bits();
let mant = (bits & MANT_MASK) | (MANT_MASK + 1);
let exp = ((bits >> 23) & EXP_MASK) as i16 + MIN_EXP;
let (top, bottom) =
if exp < -31 { (0, 0) }
else if exp < 0 {
let t = u64::from(mant) << (41 + exp);
(0, round_tie_even(23 + 41, u128::from(t)))
}
else if exp < 23 {
let top = u64::from(mant >> (23 - exp));
let bottom = round_tie_even(23, u128::from((mant << exp) & MANT_MASK));
(top, bottom)
}
else if exp < 64 {
let top = u64::from(mant) << (exp - 23);
(top, 0)
}
else { return FloatKind::Overflow(num.is_sign_negative()); };
if top == 0 && bottom == 0 { FloatKind::Zero }
else { FloatKind::Normal(top, bottom, num.is_sign_negative()) }
}
#[allow(clippy::cast_lossless)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::integer_division)]
fn parse_finite_f64(num: f64) -> FloatKind {
const MIN_EXP: i16 = 1 - (1 << 11) / 2;
const MANT_MASK: u64 = (1 << 52) - 1;
const EXP_MASK: u64 = (1 << 11) - 1;
let bits = num.abs().to_bits();
let mant = (bits & MANT_MASK) | (MANT_MASK + 1);
let exp = ((bits >> 52) & EXP_MASK) as i16 + MIN_EXP;
let (top, bottom) =
if exp < -31 { (0, 0) }
else if exp < 0 {
let bottom = round_tie_even(52 + 44, u128::from(mant) << (44 + exp));
if bottom == PRECISION { (1, 0) }
else { (0, bottom) }
}
else if exp < 52 {
let top = mant >> (52 - exp);
let bottom = round_tie_even(52, u128::from((mant << exp) & MANT_MASK));
if bottom == PRECISION { (top + 1, 0) }
else { (top, bottom) }
}
else if exp < 64 {
let top = mant << (exp - 52);
(top, 0)
}
else { return FloatKind::Overflow(num.is_sign_negative()); };
if top == 0 && bottom == 0 { FloatKind::Zero }
else { FloatKind::Normal(top, bottom, num.is_sign_negative()) }
}
#[allow(clippy::cast_possible_truncation)]
const fn round_tie_even(offset: u128, tmp: u128) -> u32 {
let tmp = PRECISION as u128 * tmp;
let val = (tmp >> offset) as u32;
let rem_mask = (1 << offset) - 1;
let rem_msb_mask = 1 << (offset - 1);
let rem = tmp & rem_mask;
let is_tie = rem == rem_msb_mask;
let is_even = (val & 1) == 0;
let rem_msb = tmp & rem_msb_mask == 0;
if rem_msb || (is_even && is_tie) { val }
else { val + 1 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn t_nice_float() {
assert_eq!(NiceFloat::from(0_f64).as_str(), "0.00000000");
assert_eq!(NiceFloat::from(-0_f64).as_str(), "0.00000000");
assert_eq!(NiceFloat::from(-10_f64).as_str(), "-10.00000000");
assert_eq!(NiceFloat::from(1.03_f64).as_str(), "1.03000000");
assert_eq!(NiceFloat::from(1.0202020202_f64).as_str(), "1.02020202");
assert_eq!(NiceFloat::from(-11_323.03_f64).as_str(), "-11,323.03000000");
assert_eq!(NiceFloat::from(0.123456789_f64).as_str(), "0.12345679");
assert_eq!(NiceFloat::from(1.000000046_f64).as_str(), "1.00000005");
assert_eq!(NiceFloat::from(1.000000035_f64).as_str(), "1.00000004");
assert_eq!(NiceFloat::from(1.000000044_f64).as_str(), "1.00000004");
assert_eq!(NiceFloat::from(1.000000045_f64).as_str(), "1.00000004");
assert_eq!(NiceFloat::from(1.000000055_f64).as_str(), "1.00000006");
assert_eq!(NiceFloat::from(f64::NAN).as_str(), "NaN");
assert_eq!(NiceFloat::from(f64::INFINITY).as_str(), "∞");
assert_eq!(NiceFloat::from(f64::NEG_INFINITY).as_str(), "∞");
assert_eq!(NiceFloat::from(1.0e-308_f64).as_str(), "0.00000000");
}
#[test]
fn t_compact() {
assert_eq!(NiceFloat::from(0_f64).compact_str(), "0");
assert_eq!(NiceFloat::with_separator(0_f64, b'0', b'0').compact_str(), "0");
assert_eq!(NiceFloat::from(0.0102003_f64).compact_str(), "0.0102003");
assert_eq!(NiceFloat::from(0.00000001_f64).compact_str(), "0.00000001");
assert_eq!(NiceFloat::from(0.000000001_f64).compact_str(), "0");
assert_eq!(NiceFloat::from(f64::NAN).compact_str(), "NaN");
assert_eq!(NiceFloat::from(f64::INFINITY).compact_str(), "∞");
assert_eq!(NiceFloat::with_separator(f64::NAN, b'-', b'_').compact_str(), "NaN");
assert_eq!(NiceFloat::with_separator(f64::INFINITY, b'-', b'_').compact_str(), "∞");
assert_eq!(NiceFloat::overflow(true).compact_str(), "< -18,446,744,073,709,551,615");
assert_eq!(NiceFloat::overflow(false).compact_str(), "> 18,446,744,073,709,551,615");
assert_eq!(NiceFloat::with_separator(f64::MIN, b'!', b'?').compact_str(), "< -18!446!744!073!709!551!615");
assert_eq!(NiceFloat::with_separator(f64::MAX, b'!', b'?').compact_str(), "> 18!446!744!073!709!551!615");
}
#[test]
fn t_precise() {
assert_eq!(NiceFloat::from(0_f64).precise_str(1), "0.0");
assert_eq!(NiceFloat::from(0_f64).precise_str(0), "0");
assert_eq!(NiceFloat::nan().precise_str(3), "NaN");
assert_eq!(NiceFloat::infinity().precise_str(3), "∞");
assert_eq!(NiceFloat::overflow(true).precise_str(3), "< -18,446,744,073,709,551,615");
assert_eq!(NiceFloat::overflow(false).precise_str(3), "> 18,446,744,073,709,551,615");
assert_eq!(NiceFloat::with_separator(f64::MIN, b'!', b'?').precise_str(3), "< -18!446!744!073!709!551!615");
assert_eq!(NiceFloat::with_separator(f64::MAX, b'!', b'?').precise_str(3), "> 18!446!744!073!709!551!615");
}
#[test]
fn t_has_dot() {
assert!(NiceFloat::from(0_f64).has_dot());
assert!(NiceFloat::from(1.234_f64).has_dot());
assert!(NiceFloat::with_separator(1.234_f64, b'!', b'?').has_dot());
assert!(! NiceFloat::nan().has_dot());
assert!(! NiceFloat::infinity().has_dot());
assert!(! NiceFloat::overflow(true).has_dot());
assert!(! NiceFloat::overflow(false).has_dot());
assert!(! NiceFloat::with_separator(f64::MIN, b'!', b'?').has_dot());
assert!(! NiceFloat::with_separator(f64::MAX, b'!', b'?').has_dot());
}
}