Skip to main content

yo_common/
num.rs

1//! Numbers to text and back, written by hand.
2//!
3//! Every reply that carries a length or an integer goes through here, which is
4//! every reply, so this is as hot as anything in the codec. The formatting
5//! machinery in `core::fmt` would produce the same bytes and would be several
6//! times slower for the two or three digits a bulk header usually needs, so it
7//! is not used for integers.
8//!
9//! Parsing is deliberately strict and matches Redis's `string2ll` byte for
10//! byte, including its refusal of leading zeros and of a leading `+`. The
11//! protocol's own lengths are parsed with it, so a stricter or looser reading
12//! here is a real difference in what the two servers accept.
13
14use core::fmt::Write as _;
15
16/// Every two digit pair, `00` through `99`, laid out end to end.
17///
18/// Two digits per pass rather than one halves the number of divisions, which is
19/// the whole cost of this loop. Built at compile time rather than typed out,
20/// because a two hundred character literal is a typo waiting to happen and the
21/// compiler will do it for free.
22const PAIRS: [u8; 200] = {
23    let mut t = [0u8; 200];
24    let mut i = 0;
25    while i < 100 {
26        t[i * 2] = b'0' + (i / 10) as u8;
27        t[i * 2 + 1] = b'0' + (i % 10) as u8;
28        i += 1;
29    }
30    t
31};
32
33/// The most digits a `u64` can have, which is what `18446744073709551615` needs.
34const U64_DIGITS: usize = 20;
35
36/// Appends the decimal digits of `n`.
37///
38/// The digits go into a fixed twenty byte buffer that is then copied whole,
39/// with the length cut back afterwards to the digits that are actually there.
40/// A copy of a length the compiler can see is a couple of stores it writes
41/// inline; a copy of a length only known at run time is a call into the
42/// platform's `memmove`, and getting into that call costs more than moving one
43/// digit. It showed up as eleven percent of `SADD` on the wire, where the whole
44/// reply is `:0`.
45pub fn push_u64(out: &mut Vec<u8>, n: u64) {
46    let len = u64_len(n);
47    let mut buf = [0u8; U64_DIGITS];
48    let mut i = len;
49    let mut n = n;
50    while n >= 100 {
51        let p = ((n % 100) as usize) * 2;
52        n /= 100;
53        i -= 2;
54        buf[i] = PAIRS[p];
55        buf[i + 1] = PAIRS[p + 1];
56    }
57    if n >= 10 {
58        let p = (n as usize) * 2;
59        buf[0] = PAIRS[p];
60        buf[1] = PAIRS[p + 1];
61    } else {
62        buf[0] = b'0' + n as u8;
63    }
64    let at = out.len();
65    out.extend_from_slice(&buf);
66    out.truncate(at + len);
67}
68
69/// Appends the decimal digits of `n`, with a minus sign if it needs one.
70pub fn push_i64(out: &mut Vec<u8>, n: i64) {
71    if n < 0 {
72        out.push(b'-');
73    }
74    // `unsigned_abs` rather than `-n`, which overflows on `i64::MIN`.
75    push_u64(out, n.unsigned_abs());
76}
77
78/// The number of bytes [`push_i64`] would append.
79///
80/// Used to presize a reply buffer before anything is written to it, which is
81/// the whole point of Y18: the buffer is sized once from what is about to go
82/// into it rather than grown while it is being filled.
83pub const fn i64_len(n: i64) -> usize {
84    (if n < 0 { 1 } else { 0 }) + u64_len(n.unsigned_abs())
85}
86
87/// How many digits `n` has.
88///
89/// `ilog10` and not a loop of divides, because this runs in front of every
90/// integer reply to size the buffer and a divide by ten is twenty cycles the
91/// hardware's leading zero count answers in one.
92#[must_use]
93pub const fn u64_len(n: u64) -> usize {
94    match n.checked_ilog10() {
95        Some(log) => log as usize + 1,
96        // `ilog10` has no answer for zero, which still takes one digit to say.
97        None => 1,
98    }
99}
100
101/// A buffer big enough for the digits of any `i64` or `u64`, sign included.
102///
103/// Twenty digits for `18446744073709551615` and one more for the minus sign
104/// that `-9223372036854775808` needs.
105pub const DIGITS_MAX: usize = U64_DIGITS + 1;
106
107/// The digits of `n`, written backwards into `buf`, and where they start.
108///
109/// One digit a pass rather than the two [`push_u64`] does, because these two
110/// are not on the reply path and the pair table is only worth its branch when
111/// it is.
112fn fill_back(buf: &mut [u8; DIGITS_MAX], n: u64) -> usize {
113    let mut at = DIGITS_MAX;
114    let mut v = n;
115    loop {
116        at -= 1;
117        buf[at] = b'0' + (v % 10) as u8;
118        v /= 10;
119        if v == 0 {
120            return at;
121        }
122    }
123}
124
125/// The decimal digits of `n`, written into the back of `buf`.
126///
127/// The same answer [`push_i64`] gives, for a caller that has nowhere to put a
128/// `Vec`. `SSCAN key 0 MATCH 1*` has to run a glob over a member that is stored
129/// as a number and has no digits anywhere, and doing that through a `Vec` would
130/// be an allocation per member on a thread that must not allocate.
131pub fn i64_digits(buf: &mut [u8; DIGITS_MAX], n: i64) -> &[u8] {
132    let mut at = fill_back(buf, n.unsigned_abs());
133    if n < 0 {
134        at -= 1;
135        buf[at] = b'-';
136    }
137    &buf[at..]
138}
139
140/// The decimal digits of `n`, written into the back of `buf`.
141///
142/// The unsigned form, for the numbers that genuinely do not fit in an `i64`. A
143/// scan cursor is one: ours packs a partition count into the top bits, so a
144/// large enough collection hands the client a number with bit 63 set and
145/// reporting it as a signed integer would report it as negative.
146pub fn u64_digits(buf: &mut [u8; DIGITS_MAX], n: u64) -> &[u8] {
147    let at = fill_back(buf, n);
148    &buf[at..]
149}
150
151/// Parses a signed decimal integer the way Redis's `string2ll` does.
152///
153/// Returns `None` for anything it would reject, which includes an empty slice,
154/// a leading `+`, a leading zero on a non zero number, any non digit anywhere,
155/// and anything that does not fit in an `i64`. The protocol's array and bulk
156/// lengths are parsed with this, so being looser here would mean accepting
157/// frames that Redis rejects, and being stricter would mean the reverse.
158pub fn parse_i64(s: &[u8]) -> Option<i64> {
159    // The longest thing that can parse is `-9223372036854775808`, at twenty.
160    if s.is_empty() || s.len() > 20 {
161        return None;
162    }
163    let (negative, digits) = if s[0] == b'-' {
164        (true, &s[1..])
165    } else {
166        (false, s)
167    };
168    if digits.is_empty() {
169        return None;
170    }
171    // A leading zero is only ever a whole number zero, and only a positive one.
172    // `007` is not seven here and it is not seven in Redis either, and `-0` is
173    // not a number in either: `string2ll` tests its zero case against the length
174    // of the whole string, so the minus sign puts `-0` past it and into the one
175    // to nine gate, which it fails. That matters beyond parsing, because this is
176    // also what decides whether a string is stored int encoded. Accepting `-0`
177    // would store it as the integer zero, and `GET` would then hand the client
178    // back `0` for a value it wrote as `-0`.
179    if digits[0] == b'0' {
180        return if digits.len() == 1 && !negative {
181            Some(0)
182        } else {
183            None
184        };
185    }
186    let mut v: u64 = 0;
187    for &c in digits {
188        if !c.is_ascii_digit() {
189            return None;
190        }
191        v = v.checked_mul(10)?.checked_add(u64::from(c - b'0'))?;
192    }
193    if negative {
194        // One more magnitude is available going negative, and `i64::MIN`
195        // reached through `wrapping_neg` is the one value that cannot be
196        // written as a positive `i64` first.
197        if v > (i64::MAX as u64) + 1 {
198            None
199        } else {
200            Some((v as i64).wrapping_neg())
201        }
202    } else if v > i64::MAX as u64 {
203        None
204    } else {
205        Some(v as i64)
206    }
207}
208
209/// The largest magnitude Redis's `double2ll` will turn into an integer.
210///
211/// `double2ll` refuses anything outside `LLONG_MAX / 2`, which as a double is
212/// exactly two to the sixty second, and then checks that the value survives a
213/// round trip through a `long long`. Everything inside that range and integral
214/// is written with the integer printer rather than the digit generator, and
215/// that decision is visible: at this magnitude the digit generator would switch
216/// to an exponent.
217const DOUBLE_INT_LIMIT: f64 = 4_611_686_018_427_387_904.0; // 2^62
218
219/// Redis's `getLongDoubleFromObject`, as far as the difference is observable.
220///
221/// A float argument and a float value are parsed by the same rules, and the
222/// rules are stricter than Rust's `str::parse`: no leading or trailing
223/// whitespace at all, and `nan` is refused where the infinities are not. Redis
224/// refuses NaN because every command that takes a float goes on to store the
225/// result, and a stored NaN compares false against itself forever after.
226///
227/// This lives here for the same reason [`parse_i64`] does. It is not a codec
228/// question, it is the same question the string type asks of a stored value,
229/// and the storage layer cannot reach into the wire layer to ask it.
230///
231/// It also takes hexadecimal, because `strtold` does and Redis inherits every
232/// bit of that. `INCRBYFLOAT` on a key holding `0x10` counts from sixteen on a
233/// real server, and `INCRBYFLOAT key 0x10` adds sixteen. Nobody designed that
234/// and it is unlikely anyone relies on it, but a client that sends it gets an
235/// answer from Redis and an error from us, and telling a client its value is
236/// not a valid float when the server next door accepts it is the kind of
237/// difference that gets found in production rather than in a test.
238pub fn parse_f64(s: &[u8]) -> Option<f64> {
239    if s.is_empty() || s[0].is_ascii_whitespace() {
240        return None;
241    }
242    let text = core::str::from_utf8(s).ok()?;
243    if text.trim() != text {
244        return None;
245    }
246    let v = if is_hex(text) {
247        parse_hex_f64(text)?
248    } else {
249        text.parse().ok()?
250    };
251    if v.is_nan() { None } else { Some(v) }
252}
253
254/// Does this start the way a C hexadecimal float does?
255///
256/// Only the prefix is checked here. Whether the rest of it is a number at all
257/// is [`parse_hex_f64`]'s problem, and a string that starts `0x` and continues
258/// badly has to be refused rather than falling back to the decimal parser,
259/// which would read `0xzz` as a plain zero.
260fn is_hex(text: &str) -> bool {
261    let body = text.strip_prefix(['+', '-']).unwrap_or(text).as_bytes();
262    body.len() > 2 && body[0] == b'0' && (body[1] | 0x20) == b'x'
263}
264
265/// `0x1.8p1` and the rest of C's hexadecimal float syntax.
266///
267/// The binary exponent is optional, which it is not in a C source literal but
268/// is in `strtod`, so `0x10` on its own is sixteen. The mantissa is gathered
269/// into a `u64` until it is full and after that the digits only move the
270/// exponent, which costs nothing anyone will see: sixteen hex digits is more
271/// precision than a double has to give back.
272fn parse_hex_f64(text: &str) -> Option<f64> {
273    let (negative, rest) = match text.as_bytes()[0] {
274        b'-' => (true, &text[1..]),
275        b'+' => (false, &text[1..]),
276        _ => (false, text),
277    };
278    let body = &rest[2..]; // `is_hex` already checked the `0x`.
279
280    let mut mantissa: u64 = 0;
281    let mut exponent: i32 = 0;
282    let mut digits = 0usize;
283    let mut seen_point = false;
284    let mut at = 0usize;
285    let bytes = body.as_bytes();
286
287    while at < bytes.len() {
288        let c = bytes[at];
289        if c == b'.' {
290            if seen_point {
291                return None;
292            }
293            seen_point = true;
294            at += 1;
295            continue;
296        }
297        let Some(value) = (c as char).to_digit(16) else {
298            break;
299        };
300        digits += 1;
301        if mantissa <= u64::MAX >> 4 {
302            mantissa = (mantissa << 4) | u64::from(value);
303            if seen_point {
304                exponent -= 4;
305            }
306        } else if !seen_point {
307            // Past what a `u64` holds, a digit before the point is worth four
308            // more binary places and nothing else.
309            exponent += 4;
310        }
311        at += 1;
312    }
313    if digits == 0 {
314        return None;
315    }
316
317    if at < bytes.len() {
318        // A binary exponent, and it is the only thing allowed to be here.
319        if (bytes[at] | 0x20) != b'p' {
320            return None;
321        }
322        let written: i32 = rest[2 + at + 1..].parse().ok()?;
323        exponent = exponent.checked_add(written)?;
324    }
325
326    let value = (mantissa as f64) * exp2(exponent);
327    // An overflow to infinity is refused rather than stored. Redis refuses it
328    // too, at a much higher ceiling, and that gap is in the divergence register
329    // rather than pretended away here.
330    if !value.is_finite() {
331        return None;
332    }
333    Some(if negative { -value } else { value })
334}
335
336/// Two to the power of a whole number, without `std`.
337///
338/// `powi` is not in core, and the exponent can be far enough out that squaring
339/// up from one would take a while, so this walks the bits. A power that is out
340/// of range comes back as an infinity and the caller refuses it.
341fn exp2(mut n: i32) -> f64 {
342    let mut base = if n < 0 { 0.5 } else { 2.0 };
343    n = n.abs();
344    let mut out = 1.0f64;
345    while n > 0 {
346        if n & 1 == 1 {
347            out *= base;
348        }
349        base *= base;
350        n >>= 1;
351    }
352    out
353}
354
355/// Room for the longest thing [`write_double`] or [`write_g17`] can write.
356///
357/// Both of them are bounded by the same thing, a mantissa of seventeen digits
358/// with a handful of zeros or a `.` and an exponent around it, and neither can
359/// reach thirty two bytes. It is worth saying out loud that this used to be
360/// three hundred and fifty two, because Rust's printer writes every leading
361/// zero of a subnormal, and the port of Redis's own printer is what shrank it.
362pub const DOUBLE_MAX: usize = 32;
363
364const _: () = assert!(DOUBLE_MAX >= crate::dtoa::MAX);
365
366/// Appends a double the way Redis 8 writes one.
367///
368/// This is `d2string`. Redis stopped using `%.17g` in 7.0 and now writes a
369/// double in two cases: a value that is exactly an integer inside two to the
370/// sixty second is written with the integer printer, and everything else goes
371/// through the Grisu2 in [`crate::dtoa`]. Zero is checked before either of
372/// them, which is the only reason negative zero comes back as `-0` rather than
373/// as `0`.
374///
375/// The infinities and NaN are written as bare words because that is what RESP3
376/// says and what RESP2 clients have always been given.
377pub fn push_double(out: &mut Vec<u8>, d: f64) {
378    let mut buf = [0u8; DOUBLE_MAX];
379    out.extend_from_slice(write_double(&mut buf, d));
380}
381
382/// Appends a double the way `INCRBYFLOAT` and `HINCRBYFLOAT` write one, which
383/// is not the way everything else does.
384///
385/// Those two go through `ld2string` in its human mode rather than through
386/// `d2string`, and the human mode is `%.17Lf` with the trailing zeros taken off
387/// and a lone `-0` turned back into `0`. Being a fixed point conversion it never
388/// writes an exponent, so `INCRBYFLOAT key 1e30` answers a one and thirty zeros
389/// where `ZSCORE` would answer `1e+30` for the same number.
390///
391/// The digits are the shortest ones rather than seventeen decimal places of the
392/// `f64`, and that is the closer answer rather than the lazier one. Redis holds
393/// the value in a long double, so `%.17Lf` of one tenth is `0.10000000000000000`
394/// and comes back as `0.1` once the zeros are stripped. Seventeen decimal places
395/// of the `f64` would be `0.10000000000000001`, which is a worse match for the
396/// same reason D-11 gives: the extra width is what makes the long double print
397/// cleanly, and shortest digits land on the same text without pretending to have
398/// it.
399pub fn push_human(out: &mut Vec<u8>, d: f64) {
400    if d.is_nan() {
401        out.extend_from_slice(b"nan");
402        return;
403    }
404    if d.is_infinite() {
405        out.extend_from_slice(if d > 0.0 { b"inf" } else { b"-inf" });
406        return;
407    }
408    // Negative zero loses its sign here, which is the one thing the human mode
409    // says out loud and `d2string` does the other way round.
410    if d.fract() == 0.0 && d.abs() <= DOUBLE_INT_LIMIT {
411        push_i64(out, d as i64);
412        return;
413    }
414    // Writing through the sink puts the digits straight into the reply buffer.
415    // `format!` would produce the same bytes and one throwaway allocation, and
416    // a shard thread that allocates aborts.
417    let mut sink = Utf8Sink(out);
418    let _ = write!(sink, "{d}");
419}
420
421/// Writes a double into a fixed buffer, byte for byte what [`push_double`]
422/// would append.
423///
424/// The two are the same code now, and this is the one that does the work,
425/// because the digit generator wants somewhere to put eighteen digits before it
426/// knows how many of them it is going to keep. The caller that needs it as a
427/// buffer rather than as a reply is the array type, which stores a value as a
428/// double only when the double prints back as the exact bytes the client sent,
429/// so it formats a candidate, compares, and usually throws it away.
430pub fn write_double(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
431    // Zero first, so that the sign of a negative zero survives. The integer
432    // path below would lose it and Redis checks in this order for that reason.
433    if d == 0.0 {
434        let n = if d.is_sign_negative() {
435            buf[..2].copy_from_slice(b"-0");
436            2
437        } else {
438            buf[0] = b'0';
439            1
440        };
441        return &buf[..n];
442    }
443    if d.is_nan() {
444        buf[..3].copy_from_slice(b"nan");
445        return &buf[..3];
446    }
447    if d.is_infinite() {
448        let text: &[u8] = if d > 0.0 { b"inf" } else { b"-inf" };
449        buf[..text.len()].copy_from_slice(text);
450        return &buf[..text.len()];
451    }
452    if d.fract() == 0.0 && d.abs() <= DOUBLE_INT_LIMIT {
453        let mut digits = [0u8; DIGITS_MAX];
454        let text = i64_digits(&mut digits, d as i64);
455        let n = text.len();
456        buf[..n].copy_from_slice(text);
457        return &buf[..n];
458    }
459    let n = crate::dtoa::dtoa(d, buf);
460    &buf[..n]
461}
462
463/// Writes a double the way C's `%.17g` writes one, which is what `AROP`
464/// replies with.
465///
466/// Redis formats an aggregate through `ld2string` in its automatic mode, and
467/// that mode is a plain `%.17Lg`, so this is the one reply in the whole server
468/// that is not a shortest round trip printer. The difference is visible: three
469/// tenths comes back as `0.29999999999999999` here and as `0.3` from `ZSCORE`,
470/// because seventeen significant digits of the nearest double to three tenths
471/// really are those.
472///
473/// `%g` picks between the two forms the way C says: the exponent form when the
474/// decimal exponent is below minus four or at least the precision, the plain
475/// form otherwise, and trailing zeros come off either way.
476pub fn write_g17(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
477    /// Seventeen significant digits is sixteen after the point.
478    const AFTER: usize = 16;
479    if d.is_nan() {
480        buf[..3].copy_from_slice(b"nan");
481        return &buf[..3];
482    }
483    if d.is_infinite() {
484        let word: &[u8] = if d > 0.0 { b"inf" } else { b"-inf" };
485        buf[..word.len()].copy_from_slice(word);
486        return &buf[..word.len()];
487    }
488    // The exponent C would use is the one the value has after it has been
489    // rounded to seventeen digits, so it has to come from the rounding and not
490    // from a logarithm: 9.9999999999999999e-5 rounds up into the next decade.
491    let mut scratch = [0u8; DOUBLE_MAX];
492    let mut sink = SliceSink {
493        buf: &mut scratch,
494        at: 0,
495    };
496    let _ = write!(sink, "{d:.AFTER$e}");
497    let end = sink.at;
498    let split = scratch[..end]
499        .iter()
500        .position(|&c| c == b'e')
501        .expect("the exponent form always has one");
502    let exp = parse_i64(&scratch[split + 1..end]).expect("a written exponent parses") as i32;
503
504    if !(-4..17).contains(&exp) {
505        // The exponent form, and C writes at least two exponent digits where
506        // Rust writes as few as one.
507        let mantissa = trim_zeros(&scratch[..split]);
508        let n = mantissa.len();
509        buf[..n].copy_from_slice(mantissa);
510        let mut sink = SliceSink { buf, at: n };
511        let sign = if exp < 0 { '-' } else { '+' };
512        let _ = write!(sink, "e{sign}{:02}", exp.unsigned_abs());
513        let at = sink.at;
514        return &buf[..at];
515    }
516    // The plain form, whose precision is what is left of the seventeen digits
517    // once the integer part has had its share.
518    let places = usize::try_from(AFTER as i32 - exp).unwrap_or(0);
519    let mut sink = SliceSink { buf, at: 0 };
520    let _ = write!(sink, "{d:.places$}");
521    let at = sink.at;
522    let n = trim_zeros(&buf[..at]).len();
523    &buf[..n]
524}
525
526/// The same digits [`write_g17`] would write, appended.
527pub fn push_g17(out: &mut Vec<u8>, d: f64) {
528    let mut buf = [0u8; DOUBLE_MAX];
529    out.extend_from_slice(write_g17(&mut buf, d));
530}
531
532/// Takes the trailing zeros off a fixed point number, and the point with them
533/// when nothing is left after it.
534///
535/// A number with no point in it is left alone, because the zeros in `1700` are
536/// not trailing anything.
537fn trim_zeros(text: &[u8]) -> &[u8] {
538    if !text.contains(&b'.') {
539        return text;
540    }
541    let end = text.iter().rposition(|&c| c != b'0').unwrap_or(0);
542    if text[end] == b'.' {
543        &text[..end]
544    } else {
545        &text[..=end]
546    }
547}
548
549/// A `core::fmt::Write` that fills a fixed buffer and stops when it is full.
550///
551/// Running out of room cannot happen here, because [`DOUBLE_MAX`] is sized for
552/// the widest double there is, and it is handled rather than asserted so that a
553/// mistake in that reasoning truncates a number instead of killing a shard.
554struct SliceSink<'a> {
555    buf: &'a mut [u8; DOUBLE_MAX],
556    at: usize,
557}
558
559impl core::fmt::Write for SliceSink<'_> {
560    fn write_str(&mut self, s: &str) -> core::fmt::Result {
561        let n = s.len().min(self.buf.len() - self.at);
562        self.buf[self.at..self.at + n].copy_from_slice(&s.as_bytes()[..n]);
563        self.at += n;
564        Ok(())
565    }
566}
567
568/// A `core::fmt::Write` that appends UTF-8 to a byte buffer.
569///
570/// The float printer only speaks `fmt::Write` and the reply buffer is bytes.
571/// This is the whole adapter, and it exists so that no reply path anywhere ever
572/// builds a `String` it immediately throws away.
573struct Utf8Sink<'a>(&'a mut Vec<u8>);
574
575impl core::fmt::Write for Utf8Sink<'_> {
576    fn write_str(&mut self, s: &str) -> core::fmt::Result {
577        self.0.extend_from_slice(s.as_bytes());
578        Ok(())
579    }
580}
581
582#[cfg(test)]
583mod tests {
584    use super::*;
585
586    fn text(n: i64) -> String {
587        let mut v = Vec::new();
588        push_i64(&mut v, n);
589        String::from_utf8(v).unwrap()
590    }
591
592    #[test]
593    fn integers_round_trip_through_text() {
594        for n in [
595            0,
596            1,
597            9,
598            10,
599            99,
600            100,
601            -1,
602            -9,
603            -10,
604            12345,
605            -12345,
606            i64::MAX,
607            i64::MIN,
608        ] {
609            assert_eq!(text(n), n.to_string(), "writing {n}");
610            assert_eq!(parse_i64(text(n).as_bytes()), Some(n), "reading {n}");
611        }
612    }
613
614    #[test]
615    fn the_length_is_known_before_the_digits_are_written() {
616        for n in [0, 5, 42, -42, 999, 1000, i64::MAX, i64::MIN] {
617            assert_eq!(i64_len(n), text(n).len(), "length of {n}");
618        }
619    }
620
621    /// Every boundary of the two digit loop, since an off by one there is a
622    /// wrong length header rather than a crash and would be found by a client.
623    #[test]
624    fn every_length_of_number_is_written_correctly() {
625        let mut n: u64 = 0;
626        for _ in 0..20 {
627            for probe in [n, n + 1, n.saturating_sub(1)] {
628                let mut v = Vec::new();
629                push_u64(&mut v, probe);
630                assert_eq!(v, probe.to_string().as_bytes(), "writing {probe}");
631            }
632            n = n.saturating_mul(10).max(9);
633            if n == u64::MAX {
634                break;
635            }
636        }
637    }
638
639    #[test]
640    fn the_stack_form_writes_what_the_vec_form_writes() {
641        // Two implementations of the same digits is the shape of bug that only
642        // shows at one boundary, so this checks them against each other rather
643        // than against a literal.
644        let mut buf = [0u8; DIGITS_MAX];
645        let mut n: i64 = 0;
646        for _ in 0..19 {
647            for probe in [n, -n, n + 1, n - 1] {
648                assert_eq!(i64_digits(&mut buf, probe), text(probe).as_bytes());
649            }
650            n = n.saturating_mul(10).max(9);
651        }
652        assert_eq!(i64_digits(&mut buf, i64::MIN), text(i64::MIN).as_bytes());
653        assert_eq!(i64_digits(&mut buf, i64::MAX), text(i64::MAX).as_bytes());
654        assert_eq!(i64_digits(&mut buf, 0), b"0", "and zero is one digit");
655
656        // And the unsigned form past where the signed one stops, which is the
657        // whole reason it is there.
658        for probe in [0, 1, u64::MAX, 1 << 63, i64::MAX as u64 + 1] {
659            let mut v = Vec::new();
660            push_u64(&mut v, probe);
661            assert_eq!(u64_digits(&mut buf, probe), v.as_slice(), "{probe}");
662        }
663    }
664
665    #[test]
666    fn the_parser_refuses_what_redis_refuses() {
667        for bad in [
668            &b""[..],
669            b"-",
670            b"+1",
671            b"01",
672            b"-01",
673            b" 1",
674            b"1 ",
675            b"1a",
676            b"a",
677            b"1.0",
678            b"-0",
679            b"-00",
680            b"9223372036854775808",
681            b"-9223372036854775809",
682            b"99999999999999999999999",
683        ] {
684            assert_eq!(parse_i64(bad), None, "{:?} should not parse", bad);
685        }
686        // The one leading zero that is a number, and the one negative that only
687        // exists going downwards.
688        assert_eq!(parse_i64(b"0"), Some(0));
689        assert_eq!(parse_i64(b"-9223372036854775808"), Some(i64::MIN));
690    }
691
692    #[test]
693    fn the_float_parser_refuses_what_redis_refuses() {
694        assert_eq!(parse_f64(b"3.5"), Some(3.5));
695        assert_eq!(parse_f64(b"-0"), Some(-0.0));
696        assert_eq!(parse_f64(b"3.0e3"), Some(3000.0));
697        assert_eq!(parse_f64(b"inf"), Some(f64::INFINITY));
698        assert_eq!(parse_f64(b"-inf"), Some(f64::NEG_INFINITY));
699        // No whitespace anywhere, nothing trailing, and no NaN, because a
700        // stored NaN compares false against itself for the rest of time.
701        assert_eq!(parse_f64(b" 3.5"), None);
702        assert_eq!(parse_f64(b"3.5 "), None);
703        assert_eq!(parse_f64(b"3.5x"), None);
704        assert_eq!(parse_f64(b""), None);
705        assert_eq!(parse_f64(b"nan"), None);
706    }
707
708    #[test]
709    fn the_float_parser_takes_hexadecimal_because_strtold_does() {
710        // Every one of these was read off a real 8.10.1 before it was written
711        // down here.
712        assert_eq!(parse_f64(b"0x10"), Some(16.0));
713        assert_eq!(parse_f64(b"0X10"), Some(16.0));
714        assert_eq!(parse_f64(b"0X1p4"), Some(16.0));
715        assert_eq!(parse_f64(b"0x1.8p1"), Some(3.0));
716        assert_eq!(parse_f64(b"-0x1.8p1"), Some(-3.0));
717        assert_eq!(parse_f64(b"+0x10"), Some(16.0));
718        assert_eq!(parse_f64(b"0x1p-1"), Some(0.5));
719        assert_eq!(parse_f64(b"0xff"), Some(255.0));
720
721        // A string that starts like a hexadecimal number and then stops being
722        // one is refused rather than falling through to the decimal parser,
723        // which would read the leading zero and call it a day.
724        assert_eq!(parse_f64(b"0x"), None);
725        assert_eq!(parse_f64(b"0xzz"), None);
726        assert_eq!(parse_f64(b"0x1p"), None);
727        assert_eq!(parse_f64(b"0x1.2.3"), None);
728        assert_eq!(parse_f64(b"0x10x"), None);
729        assert_eq!(parse_f64(b"0x1p99999"), None);
730    }
731
732    #[test]
733    fn a_mantissa_longer_than_a_double_still_lands_in_the_right_place() {
734        // Seventeen hex digits, one more than a u64 holds. The digits past the
735        // end are worth four binary places each and nothing else, which is all
736        // a double can use them for anyway.
737        assert_eq!(
738            parse_f64(b"0x10000000000000000"),
739            Some(18446744073709551616.0)
740        );
741        assert_eq!(parse_f64(b"0x1p1024"), None);
742    }
743
744    #[test]
745    fn doubles_are_written_the_way_redis_writes_them() {
746        let cases: &[(f64, &str)] = &[
747            (0.0, "0"),
748            // Redis checks for zero before it checks for an integer, so this
749            // keeps its sign where the integer printer would have dropped it.
750            (-0.0, "-0"),
751            (3.0, "3"),
752            (-3.0, "-3"),
753            (3.5, "3.5"),
754            (0.1, "0.1"),
755            // The integer printer reaches two to the sixty second, and past it
756            // the digit generator takes over and switches to an exponent.
757            (4.611686018427388e18, "4611686018427387904"),
758            (1e19, "1e+19"),
759            (1e30, "1e+30"),
760            (1e-7, "1e-7"),
761            (1e-6, "0.000001"),
762            (5e-324, "5e-324"),
763            (f64::INFINITY, "inf"),
764            (f64::NEG_INFINITY, "-inf"),
765            (f64::NAN, "nan"),
766        ];
767        for &(d, want) in cases {
768            let mut v = Vec::new();
769            push_double(&mut v, d);
770            assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
771        }
772    }
773
774    /// The human printer is the other one, and the difference is the exponent.
775    ///
776    /// `INCRBYFLOAT` and `HINCRBYFLOAT` are the only two commands that use it,
777    /// and the reason it exists as a separate thing is the last four rows: a
778    /// fixed point conversion has no exponent form to switch to, so a magnitude
779    /// that comes back as `1e+30` from a score comes back written out in full
780    /// from an increment.
781    #[test]
782    fn the_increment_printer_never_writes_an_exponent() {
783        let cases: &[(f64, &str)] = &[
784            (0.0, "0"),
785            // The human mode says so explicitly, where `d2string` keeps it.
786            (-0.0, "0"),
787            (3.0, "3"),
788            (3.5, "3.5"),
789            (0.1, "0.1"),
790            (10.5, "10.5"),
791            (0.30000000000000004, "0.30000000000000004"),
792            (1e30, "1000000000000000000000000000000"),
793            (1e19, "10000000000000000000"),
794            (1e-7, "0.0000001"),
795            (f64::INFINITY, "inf"),
796            (f64::NEG_INFINITY, "-inf"),
797            (f64::NAN, "nan"),
798        ];
799        for &(d, want) in cases {
800            let mut v = Vec::new();
801            push_human(&mut v, d);
802            assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
803        }
804        // The smallest subnormal, which is where the lack of an exponent form
805        // costs the most: `0.` and then three hundred and twenty four places.
806        let mut v = Vec::new();
807        push_human(&mut v, 5e-324);
808        assert_eq!(v.len(), 326);
809        assert!(v.starts_with(b"0.0") && v.ends_with(b"5"));
810    }
811
812    /// The two double writers have to agree, because one is used to predict the
813    /// other.
814    ///
815    /// The array type decides whether a value can be stored as a double by
816    /// formatting it with `write_double` and checking the bytes against what the
817    /// client sent, and then the reply comes out of `push_double`. If they ever
818    /// disagreed, a value would go in as a number and come back out as
819    /// different text.
820    #[test]
821    fn the_two_double_writers_agree() {
822        let mut cases = vec![
823            0.0,
824            -0.0,
825            1.0,
826            -1.0,
827            3.5,
828            0.1,
829            -0.1,
830            1e-320,
831            f64::MIN_POSITIVE,
832            f64::MAX,
833            f64::MIN,
834            DOUBLE_INT_LIMIT,
835            -DOUBLE_INT_LIMIT,
836            DOUBLE_INT_LIMIT + 2.0,
837            f64::INFINITY,
838            f64::NEG_INFINITY,
839            f64::NAN,
840        ];
841        // A spread of ordinary values, so the agreement is not only about the
842        // corners that were thought of in advance.
843        for i in -400..400 {
844            cases.push(f64::from(i) / 7.0);
845            cases.push(f64::from(i) * 1e12);
846        }
847        for d in cases {
848            let mut v = Vec::new();
849            push_double(&mut v, d);
850            let mut buf = [0u8; DOUBLE_MAX];
851            assert_eq!(write_double(&mut buf, d), &v[..], "writing {d}");
852        }
853    }
854
855    /// The fixed buffer is big enough for the widest double there is.
856    ///
857    /// `write_double` truncates rather than panicking if it is not, so a bad
858    /// constant would show up as a wrong answer somewhere far away instead of
859    /// here.
860    #[test]
861    fn the_fixed_buffer_holds_the_widest_double() {
862        let mut widest = 0;
863        for d in [f64::MIN, f64::MAX, f64::from_bits(1), -f64::from_bits(1)] {
864            let mut v = Vec::new();
865            push_double(&mut v, d);
866            widest = widest.max(v.len());
867        }
868        assert!(widest <= DOUBLE_MAX, "{widest} bytes needs more than room");
869    }
870
871    /// Seventeen significant digits, the two forms, and the trailing zeros off
872    /// both of them.
873    ///
874    /// The expected bytes here are what C's `%.17g` prints, which is what Redis
875    /// replies to `AROP` with, and it is not what the rest of the server writes
876    /// for a double: three tenths is `0.29999999999999999` in this printer and
877    /// `0.3` in the other one.
878    #[test]
879    fn the_aggregate_printer_writes_seventeen_significant_digits() {
880        let cases: &[(f64, &str)] = &[
881            (0.0, "0"),
882            (-0.0, "-0"),
883            (1.0, "1"),
884            (-1.0, "-1"),
885            (0.5, "0.5"),
886            (0.1, "0.10000000000000001"),
887            (0.3, "0.29999999999999999"),
888            (0.1 + 0.2, "0.30000000000000004"),
889            (1.0 / 3.0, "0.33333333333333331"),
890            (0.0001, "0.0001"),
891            // Below a ten thousandth is where the exponent form starts, and C
892            // writes two exponent digits where Rust would write one.
893            (1.5e-5, "1.5e-05"),
894            (1e-5, "1.0000000000000001e-05"),
895            (1e16, "10000000000000000"),
896            // And it starts again once the digits run out at seventeen.
897            (1e17, "1e+17"),
898            (1e30, "1e+30"),
899            (-1e30, "-1e+30"),
900            (1e100, "1e+100"),
901            (f64::MAX, "1.7976931348623157e+308"),
902            (f64::from_bits(1), "4.9406564584124654e-324"),
903            (12345678901234567.0, "12345678901234568"),
904            (f64::INFINITY, "inf"),
905            (f64::NEG_INFINITY, "-inf"),
906            (f64::NAN, "nan"),
907        ];
908        for &(d, want) in cases {
909            let mut buf = [0u8; DOUBLE_MAX];
910            assert_eq!(
911                core::str::from_utf8(write_g17(&mut buf, d)).unwrap(),
912                want,
913                "writing {d}"
914            );
915            let mut v = Vec::new();
916            push_g17(&mut v, d);
917            assert_eq!(String::from_utf8(v).unwrap(), want, "appending {d}");
918        }
919    }
920}