use core::fmt::Write as _;
const PAIRS: [u8; 200] = {
let mut t = [0u8; 200];
let mut i = 0;
while i < 100 {
t[i * 2] = b'0' + (i / 10) as u8;
t[i * 2 + 1] = b'0' + (i % 10) as u8;
i += 1;
}
t
};
const U64_DIGITS: usize = 20;
pub fn push_u64(out: &mut Vec<u8>, n: u64) {
let len = u64_len(n);
let mut buf = [0u8; U64_DIGITS];
let mut i = len;
let mut n = n;
while n >= 100 {
let p = ((n % 100) as usize) * 2;
n /= 100;
i -= 2;
buf[i] = PAIRS[p];
buf[i + 1] = PAIRS[p + 1];
}
if n >= 10 {
let p = (n as usize) * 2;
buf[0] = PAIRS[p];
buf[1] = PAIRS[p + 1];
} else {
buf[0] = b'0' + n as u8;
}
let at = out.len();
out.extend_from_slice(&buf);
out.truncate(at + len);
}
pub fn push_i64(out: &mut Vec<u8>, n: i64) {
if n < 0 {
out.push(b'-');
}
push_u64(out, n.unsigned_abs());
}
pub const fn i64_len(n: i64) -> usize {
(if n < 0 { 1 } else { 0 }) + u64_len(n.unsigned_abs())
}
#[must_use]
pub const fn u64_len(n: u64) -> usize {
match n.checked_ilog10() {
Some(log) => log as usize + 1,
None => 1,
}
}
pub const DIGITS_MAX: usize = U64_DIGITS + 1;
fn fill_back(buf: &mut [u8; DIGITS_MAX], n: u64) -> usize {
let mut at = DIGITS_MAX;
let mut v = n;
loop {
at -= 1;
buf[at] = b'0' + (v % 10) as u8;
v /= 10;
if v == 0 {
return at;
}
}
}
pub fn i64_digits(buf: &mut [u8; DIGITS_MAX], n: i64) -> &[u8] {
let mut at = fill_back(buf, n.unsigned_abs());
if n < 0 {
at -= 1;
buf[at] = b'-';
}
&buf[at..]
}
pub fn u64_digits(buf: &mut [u8; DIGITS_MAX], n: u64) -> &[u8] {
let at = fill_back(buf, n);
&buf[at..]
}
pub fn parse_i64(s: &[u8]) -> Option<i64> {
if s.is_empty() || s.len() > 20 {
return None;
}
let (negative, digits) = if s[0] == b'-' {
(true, &s[1..])
} else {
(false, s)
};
if digits.is_empty() {
return None;
}
if digits[0] == b'0' {
return if digits.len() == 1 && !negative {
Some(0)
} else {
None
};
}
let mut v: u64 = 0;
for &c in digits {
if !c.is_ascii_digit() {
return None;
}
v = v.checked_mul(10)?.checked_add(u64::from(c - b'0'))?;
}
if negative {
if v > (i64::MAX as u64) + 1 {
None
} else {
Some((v as i64).wrapping_neg())
}
} else if v > i64::MAX as u64 {
None
} else {
Some(v as i64)
}
}
const DOUBLE_INT_LIMIT: f64 = 4_611_686_018_427_387_904.0;
pub fn parse_f64(s: &[u8]) -> Option<f64> {
if s.is_empty() || s[0].is_ascii_whitespace() {
return None;
}
let text = core::str::from_utf8(s).ok()?;
if text.trim() != text {
return None;
}
let v = if is_hex(text) {
parse_hex_f64(text)?
} else {
text.parse().ok()?
};
if v.is_nan() { None } else { Some(v) }
}
fn is_hex(text: &str) -> bool {
let body = text.strip_prefix(['+', '-']).unwrap_or(text).as_bytes();
body.len() > 2 && body[0] == b'0' && (body[1] | 0x20) == b'x'
}
fn parse_hex_f64(text: &str) -> Option<f64> {
let (negative, rest) = match text.as_bytes()[0] {
b'-' => (true, &text[1..]),
b'+' => (false, &text[1..]),
_ => (false, text),
};
let body = &rest[2..];
let mut mantissa: u64 = 0;
let mut exponent: i32 = 0;
let mut digits = 0usize;
let mut seen_point = false;
let mut at = 0usize;
let bytes = body.as_bytes();
while at < bytes.len() {
let c = bytes[at];
if c == b'.' {
if seen_point {
return None;
}
seen_point = true;
at += 1;
continue;
}
let Some(value) = (c as char).to_digit(16) else {
break;
};
digits += 1;
if mantissa <= u64::MAX >> 4 {
mantissa = (mantissa << 4) | u64::from(value);
if seen_point {
exponent -= 4;
}
} else if !seen_point {
exponent += 4;
}
at += 1;
}
if digits == 0 {
return None;
}
if at < bytes.len() {
if (bytes[at] | 0x20) != b'p' {
return None;
}
let written: i32 = rest[2 + at + 1..].parse().ok()?;
exponent = exponent.checked_add(written)?;
}
let value = (mantissa as f64) * exp2(exponent);
if !value.is_finite() {
return None;
}
Some(if negative { -value } else { value })
}
fn exp2(mut n: i32) -> f64 {
let mut base = if n < 0 { 0.5 } else { 2.0 };
n = n.abs();
let mut out = 1.0f64;
while n > 0 {
if n & 1 == 1 {
out *= base;
}
base *= base;
n >>= 1;
}
out
}
pub const DOUBLE_MAX: usize = 32;
const _: () = assert!(DOUBLE_MAX >= crate::dtoa::MAX);
pub fn push_double(out: &mut Vec<u8>, d: f64) {
let mut buf = [0u8; DOUBLE_MAX];
out.extend_from_slice(write_double(&mut buf, d));
}
pub fn push_human(out: &mut Vec<u8>, d: f64) {
if d.is_nan() {
out.extend_from_slice(b"nan");
return;
}
if d.is_infinite() {
out.extend_from_slice(if d > 0.0 { b"inf" } else { b"-inf" });
return;
}
if d.fract() == 0.0 && d.abs() <= DOUBLE_INT_LIMIT {
push_i64(out, d as i64);
return;
}
let mut sink = Utf8Sink(out);
let _ = write!(sink, "{d}");
}
pub fn push_fixed4(out: &mut Vec<u8>, d: f64) {
let scaled = (d * 10_000.0).round_ties_even();
if !scaled.is_finite() || scaled.abs() >= DOUBLE_INT_LIMIT {
return;
}
let mut whole = scaled as i64;
if whole < 0 {
out.push(b'-');
whole = -whole;
}
let mut buf = [0u8; DIGITS_MAX];
let digits = u64_digits(&mut buf, whole as u64);
if let Some(padding) = 4usize.checked_sub(digits.len()) {
out.extend_from_slice(b"0.");
out.extend_from_slice(&b"0000"[..padding]);
out.extend_from_slice(digits);
} else {
let (front, back) = digits.split_at(digits.len() - 4);
out.extend_from_slice(front);
out.push(b'.');
out.extend_from_slice(back);
}
}
pub fn write_double(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
if d == 0.0 {
let n = if d.is_sign_negative() {
buf[..2].copy_from_slice(b"-0");
2
} else {
buf[0] = b'0';
1
};
return &buf[..n];
}
if d.is_nan() {
buf[..3].copy_from_slice(b"nan");
return &buf[..3];
}
if d.is_infinite() {
let text: &[u8] = if d > 0.0 { b"inf" } else { b"-inf" };
buf[..text.len()].copy_from_slice(text);
return &buf[..text.len()];
}
if d.fract() == 0.0 && d.abs() <= DOUBLE_INT_LIMIT {
let mut digits = [0u8; DIGITS_MAX];
let text = i64_digits(&mut digits, d as i64);
let n = text.len();
buf[..n].copy_from_slice(text);
return &buf[..n];
}
let n = crate::dtoa::dtoa(d, buf);
&buf[..n]
}
pub fn write_g17(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
const AFTER: usize = 16;
if d.is_nan() {
buf[..3].copy_from_slice(b"nan");
return &buf[..3];
}
if d.is_infinite() {
let word: &[u8] = if d > 0.0 { b"inf" } else { b"-inf" };
buf[..word.len()].copy_from_slice(word);
return &buf[..word.len()];
}
let mut scratch = [0u8; DOUBLE_MAX];
let mut sink = SliceSink {
buf: &mut scratch,
at: 0,
};
let _ = write!(sink, "{d:.AFTER$e}");
let end = sink.at;
let split = scratch[..end]
.iter()
.position(|&c| c == b'e')
.expect("the exponent form always has one");
let exp = parse_i64(&scratch[split + 1..end]).expect("a written exponent parses") as i32;
if !(-4..17).contains(&exp) {
let mantissa = trim_zeros(&scratch[..split]);
let n = mantissa.len();
buf[..n].copy_from_slice(mantissa);
let mut sink = SliceSink { buf, at: n };
let sign = if exp < 0 { '-' } else { '+' };
let _ = write!(sink, "e{sign}{:02}", exp.unsigned_abs());
let at = sink.at;
return &buf[..at];
}
let places = usize::try_from(AFTER as i32 - exp).unwrap_or(0);
let mut sink = SliceSink { buf, at: 0 };
let _ = write!(sink, "{d:.places$}");
let at = sink.at;
let n = trim_zeros(&buf[..at]).len();
&buf[..n]
}
pub fn push_g17(out: &mut Vec<u8>, d: f64) {
let mut buf = [0u8; DOUBLE_MAX];
out.extend_from_slice(write_g17(&mut buf, d));
}
fn trim_zeros(text: &[u8]) -> &[u8] {
if !text.contains(&b'.') {
return text;
}
let end = text.iter().rposition(|&c| c != b'0').unwrap_or(0);
if text[end] == b'.' {
&text[..end]
} else {
&text[..=end]
}
}
struct SliceSink<'a> {
buf: &'a mut [u8; DOUBLE_MAX],
at: usize,
}
impl core::fmt::Write for SliceSink<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let n = s.len().min(self.buf.len() - self.at);
self.buf[self.at..self.at + n].copy_from_slice(&s.as_bytes()[..n]);
self.at += n;
Ok(())
}
}
struct Utf8Sink<'a>(&'a mut Vec<u8>);
impl core::fmt::Write for Utf8Sink<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.0.extend_from_slice(s.as_bytes());
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn text(n: i64) -> String {
let mut v = Vec::new();
push_i64(&mut v, n);
String::from_utf8(v).unwrap()
}
#[test]
fn integers_round_trip_through_text() {
for n in [
0,
1,
9,
10,
99,
100,
-1,
-9,
-10,
12345,
-12345,
i64::MAX,
i64::MIN,
] {
assert_eq!(text(n), n.to_string(), "writing {n}");
assert_eq!(parse_i64(text(n).as_bytes()), Some(n), "reading {n}");
}
}
#[test]
fn the_length_is_known_before_the_digits_are_written() {
for n in [0, 5, 42, -42, 999, 1000, i64::MAX, i64::MIN] {
assert_eq!(i64_len(n), text(n).len(), "length of {n}");
}
}
#[test]
fn every_length_of_number_is_written_correctly() {
let mut n: u64 = 0;
for _ in 0..20 {
for probe in [n, n + 1, n.saturating_sub(1)] {
let mut v = Vec::new();
push_u64(&mut v, probe);
assert_eq!(v, probe.to_string().as_bytes(), "writing {probe}");
}
n = n.saturating_mul(10).max(9);
if n == u64::MAX {
break;
}
}
}
#[test]
fn the_stack_form_writes_what_the_vec_form_writes() {
let mut buf = [0u8; DIGITS_MAX];
let mut n: i64 = 0;
for _ in 0..19 {
for probe in [n, -n, n + 1, n - 1] {
assert_eq!(i64_digits(&mut buf, probe), text(probe).as_bytes());
}
n = n.saturating_mul(10).max(9);
}
assert_eq!(i64_digits(&mut buf, i64::MIN), text(i64::MIN).as_bytes());
assert_eq!(i64_digits(&mut buf, i64::MAX), text(i64::MAX).as_bytes());
assert_eq!(i64_digits(&mut buf, 0), b"0", "and zero is one digit");
for probe in [0, 1, u64::MAX, 1 << 63, i64::MAX as u64 + 1] {
let mut v = Vec::new();
push_u64(&mut v, probe);
assert_eq!(u64_digits(&mut buf, probe), v.as_slice(), "{probe}");
}
}
#[test]
fn the_parser_refuses_what_redis_refuses() {
for bad in [
&b""[..],
b"-",
b"+1",
b"01",
b"-01",
b" 1",
b"1 ",
b"1a",
b"a",
b"1.0",
b"-0",
b"-00",
b"9223372036854775808",
b"-9223372036854775809",
b"99999999999999999999999",
] {
assert_eq!(parse_i64(bad), None, "{:?} should not parse", bad);
}
assert_eq!(parse_i64(b"0"), Some(0));
assert_eq!(parse_i64(b"-9223372036854775808"), Some(i64::MIN));
}
#[test]
fn the_float_parser_refuses_what_redis_refuses() {
assert_eq!(parse_f64(b"3.5"), Some(3.5));
assert_eq!(parse_f64(b"-0"), Some(-0.0));
assert_eq!(parse_f64(b"3.0e3"), Some(3000.0));
assert_eq!(parse_f64(b"inf"), Some(f64::INFINITY));
assert_eq!(parse_f64(b"-inf"), Some(f64::NEG_INFINITY));
assert_eq!(parse_f64(b" 3.5"), None);
assert_eq!(parse_f64(b"3.5 "), None);
assert_eq!(parse_f64(b"3.5x"), None);
assert_eq!(parse_f64(b""), None);
assert_eq!(parse_f64(b"nan"), None);
}
#[test]
fn the_float_parser_takes_hexadecimal_because_strtold_does() {
assert_eq!(parse_f64(b"0x10"), Some(16.0));
assert_eq!(parse_f64(b"0X10"), Some(16.0));
assert_eq!(parse_f64(b"0X1p4"), Some(16.0));
assert_eq!(parse_f64(b"0x1.8p1"), Some(3.0));
assert_eq!(parse_f64(b"-0x1.8p1"), Some(-3.0));
assert_eq!(parse_f64(b"+0x10"), Some(16.0));
assert_eq!(parse_f64(b"0x1p-1"), Some(0.5));
assert_eq!(parse_f64(b"0xff"), Some(255.0));
assert_eq!(parse_f64(b"0x"), None);
assert_eq!(parse_f64(b"0xzz"), None);
assert_eq!(parse_f64(b"0x1p"), None);
assert_eq!(parse_f64(b"0x1.2.3"), None);
assert_eq!(parse_f64(b"0x10x"), None);
assert_eq!(parse_f64(b"0x1p99999"), None);
}
#[test]
fn a_mantissa_longer_than_a_double_still_lands_in_the_right_place() {
assert_eq!(
parse_f64(b"0x10000000000000000"),
Some(18446744073709551616.0)
);
assert_eq!(parse_f64(b"0x1p1024"), None);
}
#[test]
fn doubles_are_written_the_way_redis_writes_them() {
let cases: &[(f64, &str)] = &[
(0.0, "0"),
(-0.0, "-0"),
(3.0, "3"),
(-3.0, "-3"),
(3.5, "3.5"),
(0.1, "0.1"),
(4.611686018427388e18, "4611686018427387904"),
(1e19, "1e+19"),
(1e30, "1e+30"),
(1e-7, "1e-7"),
(1e-6, "0.000001"),
(5e-324, "5e-324"),
(f64::INFINITY, "inf"),
(f64::NEG_INFINITY, "-inf"),
(f64::NAN, "nan"),
];
for &(d, want) in cases {
let mut v = Vec::new();
push_double(&mut v, d);
assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
}
}
#[test]
fn the_increment_printer_never_writes_an_exponent() {
let cases: &[(f64, &str)] = &[
(0.0, "0"),
(-0.0, "0"),
(3.0, "3"),
(3.5, "3.5"),
(0.1, "0.1"),
(10.5, "10.5"),
(0.30000000000000004, "0.30000000000000004"),
(1e30, "1000000000000000000000000000000"),
(1e19, "10000000000000000000"),
(1e-7, "0.0000001"),
(f64::INFINITY, "inf"),
(f64::NEG_INFINITY, "-inf"),
(f64::NAN, "nan"),
];
for &(d, want) in cases {
let mut v = Vec::new();
push_human(&mut v, d);
assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
}
let mut v = Vec::new();
push_human(&mut v, 5e-324);
assert_eq!(v.len(), 326);
assert!(v.starts_with(b"0.0") && v.ends_with(b"5"));
}
#[test]
fn a_distance_always_has_four_places_after_the_point() {
let cases: &[(f64, &str)] = &[
(0.0, "0.0000"),
(-0.0, "0.0000"),
(166_274.151_561_39, "166274.1516"),
(166.274_151_561_39, "166.2742"),
(103.318_154_263_49, "103.3182"),
(545_518.869_950_1, "545518.8700"),
(5.0, "5.0000"),
(0.5, "0.5000"),
(0.05, "0.0500"),
(0.005, "0.0050"),
(0.0005, "0.0005"),
(0.000_04, "0.0000"),
(0.000_25, "0.0002"),
(0.000_35, "0.0004"),
(-1.5, "-1.5000"),
];
for &(d, want) in cases {
let mut v = Vec::new();
push_fixed4(&mut v, d);
assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
}
for d in [f64::INFINITY, f64::NAN, 1e30] {
let mut v = Vec::new();
push_fixed4(&mut v, d);
assert!(v.is_empty(), "writing {d}");
}
}
#[test]
fn the_two_double_writers_agree() {
let mut cases = vec![
0.0,
-0.0,
1.0,
-1.0,
3.5,
0.1,
-0.1,
1e-320,
f64::MIN_POSITIVE,
f64::MAX,
f64::MIN,
DOUBLE_INT_LIMIT,
-DOUBLE_INT_LIMIT,
DOUBLE_INT_LIMIT + 2.0,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NAN,
];
for i in -400..400 {
cases.push(f64::from(i) / 7.0);
cases.push(f64::from(i) * 1e12);
}
for d in cases {
let mut v = Vec::new();
push_double(&mut v, d);
let mut buf = [0u8; DOUBLE_MAX];
assert_eq!(write_double(&mut buf, d), &v[..], "writing {d}");
}
}
#[test]
fn the_fixed_buffer_holds_the_widest_double() {
let mut widest = 0;
for d in [f64::MIN, f64::MAX, f64::from_bits(1), -f64::from_bits(1)] {
let mut v = Vec::new();
push_double(&mut v, d);
widest = widest.max(v.len());
}
assert!(widest <= DOUBLE_MAX, "{widest} bytes needs more than room");
}
#[test]
fn the_aggregate_printer_writes_seventeen_significant_digits() {
let cases: &[(f64, &str)] = &[
(0.0, "0"),
(-0.0, "-0"),
(1.0, "1"),
(-1.0, "-1"),
(0.5, "0.5"),
(0.1, "0.10000000000000001"),
(0.3, "0.29999999999999999"),
(0.1 + 0.2, "0.30000000000000004"),
(1.0 / 3.0, "0.33333333333333331"),
(0.0001, "0.0001"),
(1.5e-5, "1.5e-05"),
(1e-5, "1.0000000000000001e-05"),
(1e16, "10000000000000000"),
(1e17, "1e+17"),
(1e30, "1e+30"),
(-1e30, "-1e+30"),
(1e100, "1e+100"),
(f64::MAX, "1.7976931348623157e+308"),
(f64::from_bits(1), "4.9406564584124654e-324"),
(12345678901234567.0, "12345678901234568"),
(f64::INFINITY, "inf"),
(f64::NEG_INFINITY, "-inf"),
(f64::NAN, "nan"),
];
for &(d, want) in cases {
let mut buf = [0u8; DOUBLE_MAX];
assert_eq!(
core::str::from_utf8(write_g17(&mut buf, d)).unwrap(),
want,
"writing {d}"
);
let mut v = Vec::new();
push_g17(&mut v, d);
assert_eq!(String::from_utf8(v).unwrap(), want, "appending {d}");
}
}
}