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/// Appends a distance the way the geo commands write one, which is four digits
422/// after the point and no exponent ever.
423///
424/// `GEODIST` and the `WITHDIST` half of a search go through Redis's
425/// `fixedpoint_d2string` rather than through `d2string`, because "166.2742 km
426/// away" reads better than "166.27415156960033 km away" and four places is
427/// still a tenth of a metre when the unit is the kilometre. The trailing zeros
428/// stay, so a whole number of metres comes back as `5.0000` and a distance of
429/// nothing comes back as `0.0000`.
430///
431/// The scaled value is rounded to the nearest, ties to even, which is what
432/// `llrint` does in the default rounding mode and therefore what a real server
433/// answers. Ties are not reachable in practice, since the value being rounded
434/// came out of a square root, but rounding the other way would still be a
435/// divergence that only showed up in somebody's test suite.
436///
437/// A distance too large to scale into an integer writes nothing, which is what
438/// Redis does too: its formatter fails and hands the reply an empty string.
439/// Nothing reaches that from a real search, because the far side of the world
440/// is twenty thousand kilometres away and the scaled form of that is twelve
441/// digits.
442pub fn push_fixed4(out: &mut Vec<u8>, d: f64) {
443    let scaled = (d * 10_000.0).round_ties_even();
444    if !scaled.is_finite() || scaled.abs() >= DOUBLE_INT_LIMIT {
445        return;
446    }
447    let mut whole = scaled as i64;
448    if whole < 0 {
449        out.push(b'-');
450        whole = -whole;
451    }
452    let mut buf = [0u8; DIGITS_MAX];
453    let digits = u64_digits(&mut buf, whole as u64);
454    // Four digits or fewer means there is no integer part, and Redis writes a
455    // zero in front rather than leaving a reply that starts with a point. The
456    // padding is what is left of the four places once the digits are in.
457    if let Some(padding) = 4usize.checked_sub(digits.len()) {
458        out.extend_from_slice(b"0.");
459        out.extend_from_slice(&b"0000"[..padding]);
460        out.extend_from_slice(digits);
461    } else {
462        let (front, back) = digits.split_at(digits.len() - 4);
463        out.extend_from_slice(front);
464        out.push(b'.');
465        out.extend_from_slice(back);
466    }
467}
468
469/// Writes a double into a fixed buffer, byte for byte what [`push_double`]
470/// would append.
471///
472/// The two are the same code now, and this is the one that does the work,
473/// because the digit generator wants somewhere to put eighteen digits before it
474/// knows how many of them it is going to keep. The caller that needs it as a
475/// buffer rather than as a reply is the array type, which stores a value as a
476/// double only when the double prints back as the exact bytes the client sent,
477/// so it formats a candidate, compares, and usually throws it away.
478pub fn write_double(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
479    // Zero first, so that the sign of a negative zero survives. The integer
480    // path below would lose it and Redis checks in this order for that reason.
481    if d == 0.0 {
482        let n = if d.is_sign_negative() {
483            buf[..2].copy_from_slice(b"-0");
484            2
485        } else {
486            buf[0] = b'0';
487            1
488        };
489        return &buf[..n];
490    }
491    if d.is_nan() {
492        buf[..3].copy_from_slice(b"nan");
493        return &buf[..3];
494    }
495    if d.is_infinite() {
496        let text: &[u8] = if d > 0.0 { b"inf" } else { b"-inf" };
497        buf[..text.len()].copy_from_slice(text);
498        return &buf[..text.len()];
499    }
500    if d.fract() == 0.0 && d.abs() <= DOUBLE_INT_LIMIT {
501        let mut digits = [0u8; DIGITS_MAX];
502        let text = i64_digits(&mut digits, d as i64);
503        let n = text.len();
504        buf[..n].copy_from_slice(text);
505        return &buf[..n];
506    }
507    let n = crate::dtoa::dtoa(d, buf);
508    &buf[..n]
509}
510
511/// Writes a double the way C's `%.17g` writes one, which is what `AROP`
512/// replies with.
513///
514/// Redis formats an aggregate through `ld2string` in its automatic mode, and
515/// that mode is a plain `%.17Lg`, so this is the one reply in the whole server
516/// that is not a shortest round trip printer. The difference is visible: three
517/// tenths comes back as `0.29999999999999999` here and as `0.3` from `ZSCORE`,
518/// because seventeen significant digits of the nearest double to three tenths
519/// really are those.
520///
521/// `%g` picks between the two forms the way C says: the exponent form when the
522/// decimal exponent is below minus four or at least the precision, the plain
523/// form otherwise, and trailing zeros come off either way.
524pub fn write_g17(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
525    /// Seventeen significant digits is sixteen after the point.
526    const AFTER: usize = 16;
527    if d.is_nan() {
528        buf[..3].copy_from_slice(b"nan");
529        return &buf[..3];
530    }
531    if d.is_infinite() {
532        let word: &[u8] = if d > 0.0 { b"inf" } else { b"-inf" };
533        buf[..word.len()].copy_from_slice(word);
534        return &buf[..word.len()];
535    }
536    // The exponent C would use is the one the value has after it has been
537    // rounded to seventeen digits, so it has to come from the rounding and not
538    // from a logarithm: 9.9999999999999999e-5 rounds up into the next decade.
539    let mut scratch = [0u8; DOUBLE_MAX];
540    let mut sink = SliceSink {
541        buf: &mut scratch,
542        at: 0,
543    };
544    let _ = write!(sink, "{d:.AFTER$e}");
545    let end = sink.at;
546    let split = scratch[..end]
547        .iter()
548        .position(|&c| c == b'e')
549        .expect("the exponent form always has one");
550    let exp = parse_i64(&scratch[split + 1..end]).expect("a written exponent parses") as i32;
551
552    if !(-4..17).contains(&exp) {
553        // The exponent form, and C writes at least two exponent digits where
554        // Rust writes as few as one.
555        let mantissa = trim_zeros(&scratch[..split]);
556        let n = mantissa.len();
557        buf[..n].copy_from_slice(mantissa);
558        let mut sink = SliceSink { buf, at: n };
559        let sign = if exp < 0 { '-' } else { '+' };
560        let _ = write!(sink, "e{sign}{:02}", exp.unsigned_abs());
561        let at = sink.at;
562        return &buf[..at];
563    }
564    // The plain form, whose precision is what is left of the seventeen digits
565    // once the integer part has had its share.
566    let places = usize::try_from(AFTER as i32 - exp).unwrap_or(0);
567    let mut sink = SliceSink { buf, at: 0 };
568    let _ = write!(sink, "{d:.places$}");
569    let at = sink.at;
570    let n = trim_zeros(&buf[..at]).len();
571    &buf[..n]
572}
573
574/// The same digits [`write_g17`] would write, appended.
575pub fn push_g17(out: &mut Vec<u8>, d: f64) {
576    let mut buf = [0u8; DOUBLE_MAX];
577    out.extend_from_slice(write_g17(&mut buf, d));
578}
579
580/// Writes a double the way the time series module writes a sample value on
581/// RESP2, which is neither of the two printers above.
582///
583/// The module hands the value to a vendored Dragonbox and replies with the
584/// characters that come back, so a sample reads as `1.5` but a tenth reads as
585/// `1E-1` and ten million reads as `10000000`. Deciding which of the two forms
586/// a value takes is the whole problem, because Dragonbox makes that decision
587/// on a decimal it has not finished shortening, and the caller never sees that
588/// intermediate. Its printer takes the plain form when the exponent of that
589/// unshortened decimal is in `-16 ..= 0` and the value has an integer part,
590/// and the exponent form otherwise.
591///
592/// The unshortened exponent can be recovered from the shortest one. Dragonbox
593/// returns one of two adjacent exponents, `floor(e2 * log10(2))` and one above
594/// it, where `e2` is the binary exponent of the significand read as an
595/// integer, so the exponent it used is the shortest value's own exponent
596/// capped at the upper of that pair. A value whose significand bits are all
597/// zero sits on a shorter rounding interval and its pair starts at
598/// `floor(e2 * log10(2) - log10(4 / 3))` instead. Both logarithms are the
599/// usual integer approximations, exact for every binary exponent a double has.
600///
601/// Verified against the module's own Dragonbox over seven and a half million
602/// doubles, random bit patterns, every awkward decade and boundary, and every
603/// one of the 2045 powers of two, with no difference.
604pub fn write_dragonbox(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
605    fn word<'a>(buf: &'a mut [u8; DOUBLE_MAX], text: &[u8]) -> &'a [u8] {
606        buf[..text.len()].copy_from_slice(text);
607        &buf[..text.len()]
608    }
609    if d.is_nan() {
610        return word(buf, b"NaN");
611    }
612    if d.is_infinite() {
613        return word(buf, if d > 0.0 { b"Infinity" } else { b"-Infinity" });
614    }
615    if d == 0.0 {
616        return word(buf, if d.is_sign_negative() { b"-0" } else { b"0" });
617    }
618    // The shortest round trip decimal, as a digit count and the exponent its
619    // first digit carries. A one digit shortest form can round up across a
620    // power of ten, so a magnitude that reads as a `1` is asked again at a
621    // width no rounding can carry.
622    let mut scratch = [0u8; DOUBLE_MAX];
623    let (mut exp, count) = exponent_and_digits(&mut scratch, d.abs(), None);
624    let mut magnitude = exp;
625    if count == 1 {
626        magnitude = exponent_and_digits(&mut scratch, d.abs(), Some(17)).0;
627    }
628
629    // Where Dragonbox would have stopped, which is the shortest value's own
630    // exponent unless the pair it chooses from stops it earlier.
631    let bits = d.abs().to_bits();
632    let raised = (bits >> 52) as i32;
633    let fraction = bits & ((1 << 52) - 1);
634    let e2 = if raised == 0 { -1074 } else { raised - 1075 };
635    let shorter = fraction == 0 && raised > 1;
636    let base = if shorter {
637        ((i64::from(e2) * 631_305 - 261_663) >> 21) as i32
638    } else {
639        ((i64::from(e2) * 315_653) >> 20) as i32
640    };
641    let unshortened = (exp - count as i32 + 1).min(base + 1);
642
643    // The digits Dragonbox prints are the value rounded at that exponent,
644    // which is not the same as the shortest digits. The two differ when the
645    // value sits exactly between two decimals of that width, where Dragonbox
646    // rounds to even and a shortest printer rounds away, and they differ by
647    // the zeros that padding a short value out to that width leaves behind.
648    // Rounding can carry into another digit, which moves the exponent.
649    let places = usize::try_from(magnitude - unshortened)
650        .unwrap_or(0)
651        .min(17);
652    let (carried, mut count) = if shorter {
653        round_shorter(&mut scratch, d.abs(), places, e2)
654    } else {
655        exponent_and_digits(&mut scratch, d.abs(), Some(places))
656    };
657    exp = carried;
658    while count > 1 && scratch[count - 1] == b'0' {
659        count -= 1;
660    }
661    let digits = &scratch[..count];
662
663    let mut sink = SliceSink { buf, at: 0 };
664    if d.is_sign_negative() {
665        let _ = sink.write_str("-");
666    }
667    if exp >= 0 && (-16..=0).contains(&unshortened) {
668        // The plain form. An exponent at least the digit count means the point
669        // would fall past the end, so the tail is zeros and there is no point.
670        let whole = (exp + 1) as usize;
671        let _ = sink.write_str(str::from_utf8(&digits[..whole.min(count)]).expect("digits"));
672        for _ in count..whole {
673            let _ = sink.write_str("0");
674        }
675        if whole < count {
676            let _ = sink.write_str(".");
677            let _ = sink.write_str(str::from_utf8(&digits[whole..]).expect("digits"));
678        }
679    } else {
680        let _ = sink.write_str(str::from_utf8(&digits[..1]).expect("digits"));
681        if count > 1 {
682            let _ = sink.write_str(".");
683            let _ = sink.write_str(str::from_utf8(&digits[1..]).expect("digits"));
684        }
685        let _ = write!(sink, "E{exp}");
686    }
687    let at = sink.at;
688    &buf[..at]
689}
690
691/// Fills `digits` with the decimal digits of `d`, no point among them, and
692/// answers the exponent the first of them carries and how many there are.
693///
694/// With no `places` the digits are the shortest ones that read back as `d`,
695/// and with some they are `d` rounded to that many places after the first,
696/// which is a rounding to even. Rounding can carry, `9.99` at one place is
697/// `1.0e1`, so the exponent that comes back is the rounded value's own.
698fn exponent_and_digits(digits: &mut [u8], d: f64, places: Option<usize>) -> (i32, usize) {
699    let mut text = [0u8; WIDE_MAX];
700    let mut sink = SliceSink {
701        buf: &mut text,
702        at: 0,
703    };
704    let _ = match places {
705        Some(places) => write!(sink, "{d:.places$e}"),
706        None => write!(sink, "{d:e}"),
707    };
708    let end = sink.at;
709    let split = text[..end]
710        .iter()
711        .position(|&c| c == b'e')
712        .expect("the exponent form always has one");
713    let exp = parse_i64(&text[split + 1..end]).expect("a written exponent parses") as i32;
714    let mut count = 0;
715    for &c in &text[..split] {
716        if c != b'.' {
717            digits[count] = c;
718            count += 1;
719        }
720    }
721    (exp, count)
722}
723
724/// Room for the widest thing [`exponent_and_digits`] is asked to write.
725const WIDE_MAX: usize = 48;
726
727/// How many digits past the ones it answers [`round_shorter`] looks at.
728///
729/// The two comparisons it makes are decided by six figures at worst, measured
730/// over every value that reaches it, so eight is enough with room to spare and
731/// keeps the whole thing inside a `u64` of digits.
732const EXTRA: usize = 8;
733
734/// Ten to the [`EXTRA`], as the divisor that turns those digits into a fraction.
735const EXTRA_SCALE: f64 = 100_000_000.0;
736
737/// The one binary exponent where Dragonbox breaks a tie to even.
738///
739/// Its shorter interval case rounds a half away from zero everywhere else, and
740/// only inside a band of binary exponents does it fall back to the tie rule the
741/// caller asked for. For a double that band is a single exponent, and the only
742/// two values that land on an exact half are this one and the one above it, so
743/// the whole of the rule is: `2^-25` rounds to even and `2^-24` rounds up.
744const TIE_TO_EVEN: i32 = -77;
745
746/// Fills `digits` the way [`exponent_and_digits`] does, for a value whose
747/// significand bits are all zero.
748///
749/// A power of two sits on a rounding interval that is not centred on it,
750/// because the gap below is half the gap above, and Dragonbox answers the
751/// nearest decimal inside that interval rather than the nearest decimal full
752/// stop. The two differ when rounding down would land below the interval, which
753/// is a quarter of a gap under the value, so the digits go up whenever what is
754/// left over is more than a fifty four bit relative step. That is the whole
755/// difference from the ordinary case, along with a half rounding away from zero
756/// rather than to even.
757///
758/// Verified against the module's own Dragonbox over all 2045 values that reach
759/// here, which is every power of two a double has.
760fn round_shorter(digits: &mut [u8], d: f64, places: usize, e2: i32) -> (i32, usize) {
761    let mut wide = [0u8; WIDE_MAX];
762    let (mut exp, count) = exponent_and_digits(&mut wide, d, Some(places + EXTRA));
763    let keep = count - EXTRA;
764    let mut whole = 0.0;
765    for &c in &wide[..keep] {
766        whole = whole * 10.0 + f64::from(c - b'0');
767    }
768    let mut left = 0.0;
769    for &c in &wide[keep..count] {
770        left = left * 10.0 + f64::from(c - b'0');
771    }
772    left /= EXTRA_SCALE;
773
774    // A half is a half whichever way it was reached, and nothing else this
775    // rounding ever sees comes within a thousandth of one.
776    let up = if (left - 0.5).abs() < 1e-6 {
777        e2 != TIE_TO_EVEN || wide[keep - 1] % 2 == 1
778    } else {
779        // Either past the halfway point, or short of it but still under the
780        // bottom of the interval, which sits a fifty four bit step below.
781        left > 0.5 || left > whole / 18_014_398_509_481_984.0
782    };
783
784    digits[..keep].copy_from_slice(&wide[..keep]);
785    if up {
786        let mut at = keep;
787        while at > 0 {
788            at -= 1;
789            if digits[at] == b'9' {
790                digits[at] = b'0';
791            } else {
792                digits[at] += 1;
793                break;
794            }
795        }
796        // Every digit was a nine, so the answer is a one and a row of zeros
797        // that is one digit wider, which at a fixed width is a larger exponent.
798        if digits[0] == b'0' {
799            digits[0] = b'1';
800            exp += 1;
801        }
802    }
803    (exp, keep)
804}
805
806/// The same characters [`write_dragonbox`] would write, appended.
807pub fn push_dragonbox(out: &mut Vec<u8>, d: f64) {
808    let mut buf = [0u8; DOUBLE_MAX];
809    out.extend_from_slice(write_dragonbox(&mut buf, d));
810}
811
812/// Takes the trailing zeros off a fixed point number, and the point with them
813/// when nothing is left after it.
814///
815/// A number with no point in it is left alone, because the zeros in `1700` are
816/// not trailing anything.
817fn trim_zeros(text: &[u8]) -> &[u8] {
818    if !text.contains(&b'.') {
819        return text;
820    }
821    let end = text.iter().rposition(|&c| c != b'0').unwrap_or(0);
822    if text[end] == b'.' {
823        &text[..end]
824    } else {
825        &text[..=end]
826    }
827}
828
829/// A `core::fmt::Write` that fills a fixed buffer and stops when it is full.
830///
831/// Running out of room cannot happen here, because [`DOUBLE_MAX`] is sized for
832/// the widest double there is, and it is handled rather than asserted so that a
833/// mistake in that reasoning truncates a number instead of killing a shard.
834struct SliceSink<'a> {
835    buf: &'a mut [u8],
836    at: usize,
837}
838
839impl core::fmt::Write for SliceSink<'_> {
840    fn write_str(&mut self, s: &str) -> core::fmt::Result {
841        let n = s.len().min(self.buf.len() - self.at);
842        self.buf[self.at..self.at + n].copy_from_slice(&s.as_bytes()[..n]);
843        self.at += n;
844        Ok(())
845    }
846}
847
848/// A `core::fmt::Write` that appends UTF-8 to a byte buffer.
849///
850/// The float printer only speaks `fmt::Write` and the reply buffer is bytes.
851/// This is the whole adapter, and it exists so that no reply path anywhere ever
852/// builds a `String` it immediately throws away.
853struct Utf8Sink<'a>(&'a mut Vec<u8>);
854
855impl core::fmt::Write for Utf8Sink<'_> {
856    fn write_str(&mut self, s: &str) -> core::fmt::Result {
857        self.0.extend_from_slice(s.as_bytes());
858        Ok(())
859    }
860}
861
862#[cfg(test)]
863mod tests {
864    use super::*;
865
866    fn text(n: i64) -> String {
867        let mut v = Vec::new();
868        push_i64(&mut v, n);
869        String::from_utf8(v).unwrap()
870    }
871
872    #[test]
873    fn integers_round_trip_through_text() {
874        for n in [
875            0,
876            1,
877            9,
878            10,
879            99,
880            100,
881            -1,
882            -9,
883            -10,
884            12345,
885            -12345,
886            i64::MAX,
887            i64::MIN,
888        ] {
889            assert_eq!(text(n), n.to_string(), "writing {n}");
890            assert_eq!(parse_i64(text(n).as_bytes()), Some(n), "reading {n}");
891        }
892    }
893
894    #[test]
895    fn the_length_is_known_before_the_digits_are_written() {
896        for n in [0, 5, 42, -42, 999, 1000, i64::MAX, i64::MIN] {
897            assert_eq!(i64_len(n), text(n).len(), "length of {n}");
898        }
899    }
900
901    /// Every boundary of the two digit loop, since an off by one there is a
902    /// wrong length header rather than a crash and would be found by a client.
903    #[test]
904    fn every_length_of_number_is_written_correctly() {
905        let mut n: u64 = 0;
906        for _ in 0..20 {
907            for probe in [n, n + 1, n.saturating_sub(1)] {
908                let mut v = Vec::new();
909                push_u64(&mut v, probe);
910                assert_eq!(v, probe.to_string().as_bytes(), "writing {probe}");
911            }
912            n = n.saturating_mul(10).max(9);
913            if n == u64::MAX {
914                break;
915            }
916        }
917    }
918
919    #[test]
920    fn the_stack_form_writes_what_the_vec_form_writes() {
921        // Two implementations of the same digits is the shape of bug that only
922        // shows at one boundary, so this checks them against each other rather
923        // than against a literal.
924        let mut buf = [0u8; DIGITS_MAX];
925        let mut n: i64 = 0;
926        for _ in 0..19 {
927            for probe in [n, -n, n + 1, n - 1] {
928                assert_eq!(i64_digits(&mut buf, probe), text(probe).as_bytes());
929            }
930            n = n.saturating_mul(10).max(9);
931        }
932        assert_eq!(i64_digits(&mut buf, i64::MIN), text(i64::MIN).as_bytes());
933        assert_eq!(i64_digits(&mut buf, i64::MAX), text(i64::MAX).as_bytes());
934        assert_eq!(i64_digits(&mut buf, 0), b"0", "and zero is one digit");
935
936        // And the unsigned form past where the signed one stops, which is the
937        // whole reason it is there.
938        for probe in [0, 1, u64::MAX, 1 << 63, i64::MAX as u64 + 1] {
939            let mut v = Vec::new();
940            push_u64(&mut v, probe);
941            assert_eq!(u64_digits(&mut buf, probe), v.as_slice(), "{probe}");
942        }
943    }
944
945    #[test]
946    fn the_parser_refuses_what_redis_refuses() {
947        for bad in [
948            &b""[..],
949            b"-",
950            b"+1",
951            b"01",
952            b"-01",
953            b" 1",
954            b"1 ",
955            b"1a",
956            b"a",
957            b"1.0",
958            b"-0",
959            b"-00",
960            b"9223372036854775808",
961            b"-9223372036854775809",
962            b"99999999999999999999999",
963        ] {
964            assert_eq!(parse_i64(bad), None, "{:?} should not parse", bad);
965        }
966        // The one leading zero that is a number, and the one negative that only
967        // exists going downwards.
968        assert_eq!(parse_i64(b"0"), Some(0));
969        assert_eq!(parse_i64(b"-9223372036854775808"), Some(i64::MIN));
970    }
971
972    #[test]
973    fn the_float_parser_refuses_what_redis_refuses() {
974        assert_eq!(parse_f64(b"3.5"), Some(3.5));
975        assert_eq!(parse_f64(b"-0"), Some(-0.0));
976        assert_eq!(parse_f64(b"3.0e3"), Some(3000.0));
977        assert_eq!(parse_f64(b"inf"), Some(f64::INFINITY));
978        assert_eq!(parse_f64(b"-inf"), Some(f64::NEG_INFINITY));
979        // No whitespace anywhere, nothing trailing, and no NaN, because a
980        // stored NaN compares false against itself for the rest of time.
981        assert_eq!(parse_f64(b" 3.5"), None);
982        assert_eq!(parse_f64(b"3.5 "), None);
983        assert_eq!(parse_f64(b"3.5x"), None);
984        assert_eq!(parse_f64(b""), None);
985        assert_eq!(parse_f64(b"nan"), None);
986    }
987
988    #[test]
989    fn the_float_parser_takes_hexadecimal_because_strtold_does() {
990        // Every one of these was read off a real 8.10.1 before it was written
991        // down here.
992        assert_eq!(parse_f64(b"0x10"), Some(16.0));
993        assert_eq!(parse_f64(b"0X10"), Some(16.0));
994        assert_eq!(parse_f64(b"0X1p4"), Some(16.0));
995        assert_eq!(parse_f64(b"0x1.8p1"), Some(3.0));
996        assert_eq!(parse_f64(b"-0x1.8p1"), Some(-3.0));
997        assert_eq!(parse_f64(b"+0x10"), Some(16.0));
998        assert_eq!(parse_f64(b"0x1p-1"), Some(0.5));
999        assert_eq!(parse_f64(b"0xff"), Some(255.0));
1000
1001        // A string that starts like a hexadecimal number and then stops being
1002        // one is refused rather than falling through to the decimal parser,
1003        // which would read the leading zero and call it a day.
1004        assert_eq!(parse_f64(b"0x"), None);
1005        assert_eq!(parse_f64(b"0xzz"), None);
1006        assert_eq!(parse_f64(b"0x1p"), None);
1007        assert_eq!(parse_f64(b"0x1.2.3"), None);
1008        assert_eq!(parse_f64(b"0x10x"), None);
1009        assert_eq!(parse_f64(b"0x1p99999"), None);
1010    }
1011
1012    #[test]
1013    fn a_mantissa_longer_than_a_double_still_lands_in_the_right_place() {
1014        // Seventeen hex digits, one more than a u64 holds. The digits past the
1015        // end are worth four binary places each and nothing else, which is all
1016        // a double can use them for anyway.
1017        assert_eq!(
1018            parse_f64(b"0x10000000000000000"),
1019            Some(18446744073709551616.0)
1020        );
1021        assert_eq!(parse_f64(b"0x1p1024"), None);
1022    }
1023
1024    #[test]
1025    fn doubles_are_written_the_way_redis_writes_them() {
1026        let cases: &[(f64, &str)] = &[
1027            (0.0, "0"),
1028            // Redis checks for zero before it checks for an integer, so this
1029            // keeps its sign where the integer printer would have dropped it.
1030            (-0.0, "-0"),
1031            (3.0, "3"),
1032            (-3.0, "-3"),
1033            (3.5, "3.5"),
1034            (0.1, "0.1"),
1035            // The integer printer reaches two to the sixty second, and past it
1036            // the digit generator takes over and switches to an exponent.
1037            (4.611686018427388e18, "4611686018427387904"),
1038            (1e19, "1e+19"),
1039            (1e30, "1e+30"),
1040            (1e-7, "1e-7"),
1041            (1e-6, "0.000001"),
1042            (5e-324, "5e-324"),
1043            (f64::INFINITY, "inf"),
1044            (f64::NEG_INFINITY, "-inf"),
1045            (f64::NAN, "nan"),
1046        ];
1047        for &(d, want) in cases {
1048            let mut v = Vec::new();
1049            push_double(&mut v, d);
1050            assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
1051        }
1052    }
1053
1054    /// The human printer is the other one, and the difference is the exponent.
1055    ///
1056    /// `INCRBYFLOAT` and `HINCRBYFLOAT` are the only two commands that use it,
1057    /// and the reason it exists as a separate thing is the last four rows: a
1058    /// fixed point conversion has no exponent form to switch to, so a magnitude
1059    /// that comes back as `1e+30` from a score comes back written out in full
1060    /// from an increment.
1061    #[test]
1062    fn the_increment_printer_never_writes_an_exponent() {
1063        let cases: &[(f64, &str)] = &[
1064            (0.0, "0"),
1065            // The human mode says so explicitly, where `d2string` keeps it.
1066            (-0.0, "0"),
1067            (3.0, "3"),
1068            (3.5, "3.5"),
1069            (0.1, "0.1"),
1070            (10.5, "10.5"),
1071            (0.30000000000000004, "0.30000000000000004"),
1072            (1e30, "1000000000000000000000000000000"),
1073            (1e19, "10000000000000000000"),
1074            (1e-7, "0.0000001"),
1075            (f64::INFINITY, "inf"),
1076            (f64::NEG_INFINITY, "-inf"),
1077            (f64::NAN, "nan"),
1078        ];
1079        for &(d, want) in cases {
1080            let mut v = Vec::new();
1081            push_human(&mut v, d);
1082            assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
1083        }
1084        // The smallest subnormal, which is where the lack of an exponent form
1085        // costs the most: `0.` and then three hundred and twenty four places.
1086        let mut v = Vec::new();
1087        push_human(&mut v, 5e-324);
1088        assert_eq!(v.len(), 326);
1089        assert!(v.starts_with(b"0.0") && v.ends_with(b"5"));
1090    }
1091
1092    #[test]
1093    fn a_distance_always_has_four_places_after_the_point() {
1094        let cases: &[(f64, &str)] = &[
1095            // Every one of these came off a running 8.10.1, through GEODIST and
1096            // through WITHDIST, in all four units.
1097            (0.0, "0.0000"),
1098            (-0.0, "0.0000"),
1099            (166_274.151_561_39, "166274.1516"),
1100            (166.274_151_561_39, "166.2742"),
1101            (103.318_154_263_49, "103.3182"),
1102            (545_518.869_950_1, "545518.8700"),
1103            (5.0, "5.0000"),
1104            // Under one, where the integer part is a zero that is written rather
1105            // than counted, and under a ten thousandth, where every digit of the
1106            // answer is a leading zero.
1107            (0.5, "0.5000"),
1108            (0.05, "0.0500"),
1109            (0.005, "0.0050"),
1110            (0.0005, "0.0005"),
1111            (0.000_04, "0.0000"),
1112            // The tie goes to the even digit, which is what llrint does.
1113            (0.000_25, "0.0002"),
1114            (0.000_35, "0.0004"),
1115            (-1.5, "-1.5000"),
1116        ];
1117        for &(d, want) in cases {
1118            let mut v = Vec::new();
1119            push_fixed4(&mut v, d);
1120            assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
1121        }
1122        // Nothing at all for the values a real server's formatter refuses,
1123        // which no search can produce and a client can still ask for.
1124        for d in [f64::INFINITY, f64::NAN, 1e30] {
1125            let mut v = Vec::new();
1126            push_fixed4(&mut v, d);
1127            assert!(v.is_empty(), "writing {d}");
1128        }
1129    }
1130
1131    /// The two double writers have to agree, because one is used to predict the
1132    /// other.
1133    ///
1134    /// The array type decides whether a value can be stored as a double by
1135    /// formatting it with `write_double` and checking the bytes against what the
1136    /// client sent, and then the reply comes out of `push_double`. If they ever
1137    /// disagreed, a value would go in as a number and come back out as
1138    /// different text.
1139    #[test]
1140    fn the_two_double_writers_agree() {
1141        let mut cases = vec![
1142            0.0,
1143            -0.0,
1144            1.0,
1145            -1.0,
1146            3.5,
1147            0.1,
1148            -0.1,
1149            1e-320,
1150            f64::MIN_POSITIVE,
1151            f64::MAX,
1152            f64::MIN,
1153            DOUBLE_INT_LIMIT,
1154            -DOUBLE_INT_LIMIT,
1155            DOUBLE_INT_LIMIT + 2.0,
1156            f64::INFINITY,
1157            f64::NEG_INFINITY,
1158            f64::NAN,
1159        ];
1160        // A spread of ordinary values, so the agreement is not only about the
1161        // corners that were thought of in advance.
1162        for i in -400..400 {
1163            cases.push(f64::from(i) / 7.0);
1164            cases.push(f64::from(i) * 1e12);
1165        }
1166        for d in cases {
1167            let mut v = Vec::new();
1168            push_double(&mut v, d);
1169            let mut buf = [0u8; DOUBLE_MAX];
1170            assert_eq!(write_double(&mut buf, d), &v[..], "writing {d}");
1171        }
1172    }
1173
1174    /// The fixed buffer is big enough for the widest double there is.
1175    ///
1176    /// `write_double` truncates rather than panicking if it is not, so a bad
1177    /// constant would show up as a wrong answer somewhere far away instead of
1178    /// here.
1179    #[test]
1180    fn the_fixed_buffer_holds_the_widest_double() {
1181        let mut widest = 0;
1182        for d in [f64::MIN, f64::MAX, f64::from_bits(1), -f64::from_bits(1)] {
1183            let mut v = Vec::new();
1184            push_double(&mut v, d);
1185            widest = widest.max(v.len());
1186        }
1187        assert!(widest <= DOUBLE_MAX, "{widest} bytes needs more than room");
1188    }
1189
1190    /// Seventeen significant digits, the two forms, and the trailing zeros off
1191    /// both of them.
1192    ///
1193    /// The expected bytes here are what C's `%.17g` prints, which is what Redis
1194    /// replies to `AROP` with, and it is not what the rest of the server writes
1195    /// for a double: three tenths is `0.29999999999999999` in this printer and
1196    /// `0.3` in the other one.
1197    #[test]
1198    fn the_aggregate_printer_writes_seventeen_significant_digits() {
1199        let cases: &[(f64, &str)] = &[
1200            (0.0, "0"),
1201            (-0.0, "-0"),
1202            (1.0, "1"),
1203            (-1.0, "-1"),
1204            (0.5, "0.5"),
1205            (0.1, "0.10000000000000001"),
1206            (0.3, "0.29999999999999999"),
1207            (0.1 + 0.2, "0.30000000000000004"),
1208            (1.0 / 3.0, "0.33333333333333331"),
1209            (0.0001, "0.0001"),
1210            // Below a ten thousandth is where the exponent form starts, and C
1211            // writes two exponent digits where Rust would write one.
1212            (1.5e-5, "1.5e-05"),
1213            (1e-5, "1.0000000000000001e-05"),
1214            (1e16, "10000000000000000"),
1215            // And it starts again once the digits run out at seventeen.
1216            (1e17, "1e+17"),
1217            (1e30, "1e+30"),
1218            (-1e30, "-1e+30"),
1219            (1e100, "1e+100"),
1220            (f64::MAX, "1.7976931348623157e+308"),
1221            (f64::from_bits(1), "4.9406564584124654e-324"),
1222            (12345678901234567.0, "12345678901234568"),
1223            (f64::INFINITY, "inf"),
1224            (f64::NEG_INFINITY, "-inf"),
1225            (f64::NAN, "nan"),
1226        ];
1227        for &(d, want) in cases {
1228            let mut buf = [0u8; DOUBLE_MAX];
1229            assert_eq!(
1230                core::str::from_utf8(write_g17(&mut buf, d)).unwrap(),
1231                want,
1232                "writing {d}"
1233            );
1234            let mut v = Vec::new();
1235            push_g17(&mut v, d);
1236            assert_eq!(String::from_utf8(v).unwrap(), want, "appending {d}");
1237        }
1238    }
1239
1240    #[test]
1241    fn dragonbox_writes_what_the_module_writes() {
1242        // Every one of these was read off the module's own Dragonbox rather
1243        // than reasoned about, including the four at the end, which are the
1244        // cases where the shorter rounding interval of a power of two decides
1245        // the last digit.
1246        let cases: &[(f64, &str)] = &[
1247            (0.0, "0"),
1248            (-0.0, "-0"),
1249            (1.0, "1"),
1250            (-1.0, "-1"),
1251            (0.5, "5E-1"),
1252            (0.1, "1E-1"),
1253            (0.3, "3E-1"),
1254            (1.5, "1.5"),
1255            (123.456, "123.456"),
1256            (0.1 + 0.2, "3.0000000000000004E-1"),
1257            (core::f64::consts::PI, "3.141592653589793"),
1258            (-2.5e-8, "-2.5E-8"),
1259            // The plain form runs from a ten thousandth up to the last width
1260            // that has no exponent left over, and nowhere else.
1261            (0.0001, "1E-4"),
1262            (1e7, "10000000"),
1263            (1e16, "1E16"),
1264            (1e17, "1E17"),
1265            (1e-16, "1E-16"),
1266            (1e-17, "1E-17"),
1267            (1e23, "1E23"),
1268            (9_007_199_254_740_993.0, "9007199254740992"),
1269            (f64::MAX, "1.7976931348623157E308"),
1270            (f64::MIN_POSITIVE, "2.2250738585072014E-308"),
1271            (f64::from_bits(1), "5E-324"),
1272            (f64::INFINITY, "Infinity"),
1273            (f64::NEG_INFINITY, "-Infinity"),
1274            (f64::NAN, "NaN"),
1275            (18_014_398_509_481_984.0, "18014398509481984"),
1276            (5.960_464_477_539_063e-8, "5.960464477539063E-8"),
1277            (2.980_232_238_769_531_2e-8, "2.9802322387695312E-8"),
1278            (6.189_700_196_426_902e26, "6.189700196426902E26"),
1279        ];
1280        for &(d, want) in cases {
1281            let mut buf = [0u8; DOUBLE_MAX];
1282            assert_eq!(
1283                core::str::from_utf8(write_dragonbox(&mut buf, d)).unwrap(),
1284                want,
1285                "writing {d}"
1286            );
1287            let mut v = Vec::new();
1288            push_dragonbox(&mut v, d);
1289            assert_eq!(String::from_utf8(v).unwrap(), want, "appending {d}");
1290        }
1291    }
1292}