yo-common 0.3.9

Ids, the generated error model, wyhash and the CRC family for yo.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
//! Numbers to text and back, written by hand.
//!
//! Every reply that carries a length or an integer goes through here, which is
//! every reply, so this is as hot as anything in the codec. The formatting
//! machinery in `core::fmt` would produce the same bytes and would be several
//! times slower for the two or three digits a bulk header usually needs, so it
//! is not used for integers.
//!
//! Parsing is deliberately strict and matches Redis's `string2ll` byte for
//! byte, including its refusal of leading zeros and of a leading `+`. The
//! protocol's own lengths are parsed with it, so a stricter or looser reading
//! here is a real difference in what the two servers accept.

use core::fmt::Write as _;

/// Every two digit pair, `00` through `99`, laid out end to end.
///
/// Two digits per pass rather than one halves the number of divisions, which is
/// the whole cost of this loop. Built at compile time rather than typed out,
/// because a two hundred character literal is a typo waiting to happen and the
/// compiler will do it for free.
const PAIRS: [u8; 200] = {
    let mut t = [0u8; 200];
    let mut i = 0;
    while i < 100 {
        t[i * 2] = b'0' + (i / 10) as u8;
        t[i * 2 + 1] = b'0' + (i % 10) as u8;
        i += 1;
    }
    t
};

/// The most digits a `u64` can have, which is what `18446744073709551615` needs.
const U64_DIGITS: usize = 20;

/// Appends the decimal digits of `n`.
///
/// The digits go into a fixed twenty byte buffer that is then copied whole,
/// with the length cut back afterwards to the digits that are actually there.
/// A copy of a length the compiler can see is a couple of stores it writes
/// inline; a copy of a length only known at run time is a call into the
/// platform's `memmove`, and getting into that call costs more than moving one
/// digit. It showed up as eleven percent of `SADD` on the wire, where the whole
/// reply is `:0`.
pub fn push_u64(out: &mut Vec<u8>, n: u64) {
    let len = u64_len(n);
    let mut buf = [0u8; U64_DIGITS];
    let mut i = len;
    let mut n = n;
    while n >= 100 {
        let p = ((n % 100) as usize) * 2;
        n /= 100;
        i -= 2;
        buf[i] = PAIRS[p];
        buf[i + 1] = PAIRS[p + 1];
    }
    if n >= 10 {
        let p = (n as usize) * 2;
        buf[0] = PAIRS[p];
        buf[1] = PAIRS[p + 1];
    } else {
        buf[0] = b'0' + n as u8;
    }
    let at = out.len();
    out.extend_from_slice(&buf);
    out.truncate(at + len);
}

/// Appends the decimal digits of `n`, with a minus sign if it needs one.
pub fn push_i64(out: &mut Vec<u8>, n: i64) {
    if n < 0 {
        out.push(b'-');
    }
    // `unsigned_abs` rather than `-n`, which overflows on `i64::MIN`.
    push_u64(out, n.unsigned_abs());
}

/// The number of bytes [`push_i64`] would append.
///
/// Used to presize a reply buffer before anything is written to it, which is
/// the whole point of Y18: the buffer is sized once from what is about to go
/// into it rather than grown while it is being filled.
pub const fn i64_len(n: i64) -> usize {
    (if n < 0 { 1 } else { 0 }) + u64_len(n.unsigned_abs())
}

/// How many digits `n` has.
///
/// `ilog10` and not a loop of divides, because this runs in front of every
/// integer reply to size the buffer and a divide by ten is twenty cycles the
/// hardware's leading zero count answers in one.
#[must_use]
pub const fn u64_len(n: u64) -> usize {
    match n.checked_ilog10() {
        Some(log) => log as usize + 1,
        // `ilog10` has no answer for zero, which still takes one digit to say.
        None => 1,
    }
}

/// A buffer big enough for the digits of any `i64` or `u64`, sign included.
///
/// Twenty digits for `18446744073709551615` and one more for the minus sign
/// that `-9223372036854775808` needs.
pub const DIGITS_MAX: usize = U64_DIGITS + 1;

/// The digits of `n`, written backwards into `buf`, and where they start.
///
/// One digit a pass rather than the two [`push_u64`] does, because these two
/// are not on the reply path and the pair table is only worth its branch when
/// it is.
fn fill_back(buf: &mut [u8; DIGITS_MAX], n: u64) -> usize {
    let mut at = DIGITS_MAX;
    let mut v = n;
    loop {
        at -= 1;
        buf[at] = b'0' + (v % 10) as u8;
        v /= 10;
        if v == 0 {
            return at;
        }
    }
}

/// The decimal digits of `n`, written into the back of `buf`.
///
/// The same answer [`push_i64`] gives, for a caller that has nowhere to put a
/// `Vec`. `SSCAN key 0 MATCH 1*` has to run a glob over a member that is stored
/// as a number and has no digits anywhere, and doing that through a `Vec` would
/// be an allocation per member on a thread that must not allocate.
pub fn i64_digits(buf: &mut [u8; DIGITS_MAX], n: i64) -> &[u8] {
    let mut at = fill_back(buf, n.unsigned_abs());
    if n < 0 {
        at -= 1;
        buf[at] = b'-';
    }
    &buf[at..]
}

/// The decimal digits of `n`, written into the back of `buf`.
///
/// The unsigned form, for the numbers that genuinely do not fit in an `i64`. A
/// scan cursor is one: ours packs a partition count into the top bits, so a
/// large enough collection hands the client a number with bit 63 set and
/// reporting it as a signed integer would report it as negative.
pub fn u64_digits(buf: &mut [u8; DIGITS_MAX], n: u64) -> &[u8] {
    let at = fill_back(buf, n);
    &buf[at..]
}

/// Parses a signed decimal integer the way Redis's `string2ll` does.
///
/// Returns `None` for anything it would reject, which includes an empty slice,
/// a leading `+`, a leading zero on a non zero number, any non digit anywhere,
/// and anything that does not fit in an `i64`. The protocol's array and bulk
/// lengths are parsed with this, so being looser here would mean accepting
/// frames that Redis rejects, and being stricter would mean the reverse.
pub fn parse_i64(s: &[u8]) -> Option<i64> {
    // The longest thing that can parse is `-9223372036854775808`, at twenty.
    if s.is_empty() || s.len() > 20 {
        return None;
    }
    let (negative, digits) = if s[0] == b'-' {
        (true, &s[1..])
    } else {
        (false, s)
    };
    if digits.is_empty() {
        return None;
    }
    // A leading zero is only ever a whole number zero, and only a positive one.
    // `007` is not seven here and it is not seven in Redis either, and `-0` is
    // not a number in either: `string2ll` tests its zero case against the length
    // of the whole string, so the minus sign puts `-0` past it and into the one
    // to nine gate, which it fails. That matters beyond parsing, because this is
    // also what decides whether a string is stored int encoded. Accepting `-0`
    // would store it as the integer zero, and `GET` would then hand the client
    // back `0` for a value it wrote as `-0`.
    if digits[0] == b'0' {
        return if digits.len() == 1 && !negative {
            Some(0)
        } else {
            None
        };
    }
    let mut v: u64 = 0;
    for &c in digits {
        if !c.is_ascii_digit() {
            return None;
        }
        v = v.checked_mul(10)?.checked_add(u64::from(c - b'0'))?;
    }
    if negative {
        // One more magnitude is available going negative, and `i64::MIN`
        // reached through `wrapping_neg` is the one value that cannot be
        // written as a positive `i64` first.
        if v > (i64::MAX as u64) + 1 {
            None
        } else {
            Some((v as i64).wrapping_neg())
        }
    } else if v > i64::MAX as u64 {
        None
    } else {
        Some(v as i64)
    }
}

/// The largest magnitude Redis's `double2ll` will turn into an integer.
///
/// `double2ll` refuses anything outside `LLONG_MAX / 2`, which as a double is
/// exactly two to the sixty second, and then checks that the value survives a
/// round trip through a `long long`. Everything inside that range and integral
/// is written with the integer printer rather than the digit generator, and
/// that decision is visible: at this magnitude the digit generator would switch
/// to an exponent.
const DOUBLE_INT_LIMIT: f64 = 4_611_686_018_427_387_904.0; // 2^62

/// Redis's `getLongDoubleFromObject`, as far as the difference is observable.
///
/// A float argument and a float value are parsed by the same rules, and the
/// rules are stricter than Rust's `str::parse`: no leading or trailing
/// whitespace at all, and `nan` is refused where the infinities are not. Redis
/// refuses NaN because every command that takes a float goes on to store the
/// result, and a stored NaN compares false against itself forever after.
///
/// This lives here for the same reason [`parse_i64`] does. It is not a codec
/// question, it is the same question the string type asks of a stored value,
/// and the storage layer cannot reach into the wire layer to ask it.
///
/// It also takes hexadecimal, because `strtold` does and Redis inherits every
/// bit of that. `INCRBYFLOAT` on a key holding `0x10` counts from sixteen on a
/// real server, and `INCRBYFLOAT key 0x10` adds sixteen. Nobody designed that
/// and it is unlikely anyone relies on it, but a client that sends it gets an
/// answer from Redis and an error from us, and telling a client its value is
/// not a valid float when the server next door accepts it is the kind of
/// difference that gets found in production rather than in a test.
pub fn parse_f64(s: &[u8]) -> Option<f64> {
    if s.is_empty() || s[0].is_ascii_whitespace() {
        return None;
    }
    let text = core::str::from_utf8(s).ok()?;
    if text.trim() != text {
        return None;
    }
    let v = if is_hex(text) {
        parse_hex_f64(text)?
    } else {
        text.parse().ok()?
    };
    if v.is_nan() { None } else { Some(v) }
}

/// Does this start the way a C hexadecimal float does?
///
/// Only the prefix is checked here. Whether the rest of it is a number at all
/// is [`parse_hex_f64`]'s problem, and a string that starts `0x` and continues
/// badly has to be refused rather than falling back to the decimal parser,
/// which would read `0xzz` as a plain zero.
fn is_hex(text: &str) -> bool {
    let body = text.strip_prefix(['+', '-']).unwrap_or(text).as_bytes();
    body.len() > 2 && body[0] == b'0' && (body[1] | 0x20) == b'x'
}

/// `0x1.8p1` and the rest of C's hexadecimal float syntax.
///
/// The binary exponent is optional, which it is not in a C source literal but
/// is in `strtod`, so `0x10` on its own is sixteen. The mantissa is gathered
/// into a `u64` until it is full and after that the digits only move the
/// exponent, which costs nothing anyone will see: sixteen hex digits is more
/// precision than a double has to give back.
fn parse_hex_f64(text: &str) -> Option<f64> {
    let (negative, rest) = match text.as_bytes()[0] {
        b'-' => (true, &text[1..]),
        b'+' => (false, &text[1..]),
        _ => (false, text),
    };
    let body = &rest[2..]; // `is_hex` already checked the `0x`.

    let mut mantissa: u64 = 0;
    let mut exponent: i32 = 0;
    let mut digits = 0usize;
    let mut seen_point = false;
    let mut at = 0usize;
    let bytes = body.as_bytes();

    while at < bytes.len() {
        let c = bytes[at];
        if c == b'.' {
            if seen_point {
                return None;
            }
            seen_point = true;
            at += 1;
            continue;
        }
        let Some(value) = (c as char).to_digit(16) else {
            break;
        };
        digits += 1;
        if mantissa <= u64::MAX >> 4 {
            mantissa = (mantissa << 4) | u64::from(value);
            if seen_point {
                exponent -= 4;
            }
        } else if !seen_point {
            // Past what a `u64` holds, a digit before the point is worth four
            // more binary places and nothing else.
            exponent += 4;
        }
        at += 1;
    }
    if digits == 0 {
        return None;
    }

    if at < bytes.len() {
        // A binary exponent, and it is the only thing allowed to be here.
        if (bytes[at] | 0x20) != b'p' {
            return None;
        }
        let written: i32 = rest[2 + at + 1..].parse().ok()?;
        exponent = exponent.checked_add(written)?;
    }

    let value = (mantissa as f64) * exp2(exponent);
    // An overflow to infinity is refused rather than stored. Redis refuses it
    // too, at a much higher ceiling, and that gap is in the divergence register
    // rather than pretended away here.
    if !value.is_finite() {
        return None;
    }
    Some(if negative { -value } else { value })
}

/// Two to the power of a whole number, without `std`.
///
/// `powi` is not in core, and the exponent can be far enough out that squaring
/// up from one would take a while, so this walks the bits. A power that is out
/// of range comes back as an infinity and the caller refuses it.
fn exp2(mut n: i32) -> f64 {
    let mut base = if n < 0 { 0.5 } else { 2.0 };
    n = n.abs();
    let mut out = 1.0f64;
    while n > 0 {
        if n & 1 == 1 {
            out *= base;
        }
        base *= base;
        n >>= 1;
    }
    out
}

/// Room for the longest thing [`write_double`] or [`write_g17`] can write.
///
/// Both of them are bounded by the same thing, a mantissa of seventeen digits
/// with a handful of zeros or a `.` and an exponent around it, and neither can
/// reach thirty two bytes. It is worth saying out loud that this used to be
/// three hundred and fifty two, because Rust's printer writes every leading
/// zero of a subnormal, and the port of Redis's own printer is what shrank it.
pub const DOUBLE_MAX: usize = 32;

const _: () = assert!(DOUBLE_MAX >= crate::dtoa::MAX);

/// Appends a double the way Redis 8 writes one.
///
/// This is `d2string`. Redis stopped using `%.17g` in 7.0 and now writes a
/// double in two cases: a value that is exactly an integer inside two to the
/// sixty second is written with the integer printer, and everything else goes
/// through the Grisu2 in [`crate::dtoa`]. Zero is checked before either of
/// them, which is the only reason negative zero comes back as `-0` rather than
/// as `0`.
///
/// The infinities and NaN are written as bare words because that is what RESP3
/// says and what RESP2 clients have always been given.
pub fn push_double(out: &mut Vec<u8>, d: f64) {
    let mut buf = [0u8; DOUBLE_MAX];
    out.extend_from_slice(write_double(&mut buf, d));
}

/// Appends a double the way `INCRBYFLOAT` and `HINCRBYFLOAT` write one, which
/// is not the way everything else does.
///
/// Those two go through `ld2string` in its human mode rather than through
/// `d2string`, and the human mode is `%.17Lf` with the trailing zeros taken off
/// and a lone `-0` turned back into `0`. Being a fixed point conversion it never
/// writes an exponent, so `INCRBYFLOAT key 1e30` answers a one and thirty zeros
/// where `ZSCORE` would answer `1e+30` for the same number.
///
/// The digits are the shortest ones rather than seventeen decimal places of the
/// `f64`, and that is the closer answer rather than the lazier one. Redis holds
/// the value in a long double, so `%.17Lf` of one tenth is `0.10000000000000000`
/// and comes back as `0.1` once the zeros are stripped. Seventeen decimal places
/// of the `f64` would be `0.10000000000000001`, which is a worse match for the
/// same reason D-11 gives: the extra width is what makes the long double print
/// cleanly, and shortest digits land on the same text without pretending to have
/// it.
pub fn push_human(out: &mut Vec<u8>, d: f64) {
    if d.is_nan() {
        out.extend_from_slice(b"nan");
        return;
    }
    if d.is_infinite() {
        out.extend_from_slice(if d > 0.0 { b"inf" } else { b"-inf" });
        return;
    }
    // Negative zero loses its sign here, which is the one thing the human mode
    // says out loud and `d2string` does the other way round.
    if d.fract() == 0.0 && d.abs() <= DOUBLE_INT_LIMIT {
        push_i64(out, d as i64);
        return;
    }
    // Writing through the sink puts the digits straight into the reply buffer.
    // `format!` would produce the same bytes and one throwaway allocation, and
    // a shard thread that allocates aborts.
    let mut sink = Utf8Sink(out);
    let _ = write!(sink, "{d}");
}

/// Writes a double into a fixed buffer, byte for byte what [`push_double`]
/// would append.
///
/// The two are the same code now, and this is the one that does the work,
/// because the digit generator wants somewhere to put eighteen digits before it
/// knows how many of them it is going to keep. The caller that needs it as a
/// buffer rather than as a reply is the array type, which stores a value as a
/// double only when the double prints back as the exact bytes the client sent,
/// so it formats a candidate, compares, and usually throws it away.
pub fn write_double(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
    // Zero first, so that the sign of a negative zero survives. The integer
    // path below would lose it and Redis checks in this order for that reason.
    if d == 0.0 {
        let n = if d.is_sign_negative() {
            buf[..2].copy_from_slice(b"-0");
            2
        } else {
            buf[0] = b'0';
            1
        };
        return &buf[..n];
    }
    if d.is_nan() {
        buf[..3].copy_from_slice(b"nan");
        return &buf[..3];
    }
    if d.is_infinite() {
        let text: &[u8] = if d > 0.0 { b"inf" } else { b"-inf" };
        buf[..text.len()].copy_from_slice(text);
        return &buf[..text.len()];
    }
    if d.fract() == 0.0 && d.abs() <= DOUBLE_INT_LIMIT {
        let mut digits = [0u8; DIGITS_MAX];
        let text = i64_digits(&mut digits, d as i64);
        let n = text.len();
        buf[..n].copy_from_slice(text);
        return &buf[..n];
    }
    let n = crate::dtoa::dtoa(d, buf);
    &buf[..n]
}

/// Writes a double the way C's `%.17g` writes one, which is what `AROP`
/// replies with.
///
/// Redis formats an aggregate through `ld2string` in its automatic mode, and
/// that mode is a plain `%.17Lg`, so this is the one reply in the whole server
/// that is not a shortest round trip printer. The difference is visible: three
/// tenths comes back as `0.29999999999999999` here and as `0.3` from `ZSCORE`,
/// because seventeen significant digits of the nearest double to three tenths
/// really are those.
///
/// `%g` picks between the two forms the way C says: the exponent form when the
/// decimal exponent is below minus four or at least the precision, the plain
/// form otherwise, and trailing zeros come off either way.
pub fn write_g17(buf: &mut [u8; DOUBLE_MAX], d: f64) -> &[u8] {
    /// Seventeen significant digits is sixteen after the point.
    const AFTER: usize = 16;
    if d.is_nan() {
        buf[..3].copy_from_slice(b"nan");
        return &buf[..3];
    }
    if d.is_infinite() {
        let word: &[u8] = if d > 0.0 { b"inf" } else { b"-inf" };
        buf[..word.len()].copy_from_slice(word);
        return &buf[..word.len()];
    }
    // The exponent C would use is the one the value has after it has been
    // rounded to seventeen digits, so it has to come from the rounding and not
    // from a logarithm: 9.9999999999999999e-5 rounds up into the next decade.
    let mut scratch = [0u8; DOUBLE_MAX];
    let mut sink = SliceSink {
        buf: &mut scratch,
        at: 0,
    };
    let _ = write!(sink, "{d:.AFTER$e}");
    let end = sink.at;
    let split = scratch[..end]
        .iter()
        .position(|&c| c == b'e')
        .expect("the exponent form always has one");
    let exp = parse_i64(&scratch[split + 1..end]).expect("a written exponent parses") as i32;

    if !(-4..17).contains(&exp) {
        // The exponent form, and C writes at least two exponent digits where
        // Rust writes as few as one.
        let mantissa = trim_zeros(&scratch[..split]);
        let n = mantissa.len();
        buf[..n].copy_from_slice(mantissa);
        let mut sink = SliceSink { buf, at: n };
        let sign = if exp < 0 { '-' } else { '+' };
        let _ = write!(sink, "e{sign}{:02}", exp.unsigned_abs());
        let at = sink.at;
        return &buf[..at];
    }
    // The plain form, whose precision is what is left of the seventeen digits
    // once the integer part has had its share.
    let places = usize::try_from(AFTER as i32 - exp).unwrap_or(0);
    let mut sink = SliceSink { buf, at: 0 };
    let _ = write!(sink, "{d:.places$}");
    let at = sink.at;
    let n = trim_zeros(&buf[..at]).len();
    &buf[..n]
}

/// The same digits [`write_g17`] would write, appended.
pub fn push_g17(out: &mut Vec<u8>, d: f64) {
    let mut buf = [0u8; DOUBLE_MAX];
    out.extend_from_slice(write_g17(&mut buf, d));
}

/// Takes the trailing zeros off a fixed point number, and the point with them
/// when nothing is left after it.
///
/// A number with no point in it is left alone, because the zeros in `1700` are
/// not trailing anything.
fn trim_zeros(text: &[u8]) -> &[u8] {
    if !text.contains(&b'.') {
        return text;
    }
    let end = text.iter().rposition(|&c| c != b'0').unwrap_or(0);
    if text[end] == b'.' {
        &text[..end]
    } else {
        &text[..=end]
    }
}

/// A `core::fmt::Write` that fills a fixed buffer and stops when it is full.
///
/// Running out of room cannot happen here, because [`DOUBLE_MAX`] is sized for
/// the widest double there is, and it is handled rather than asserted so that a
/// mistake in that reasoning truncates a number instead of killing a shard.
struct SliceSink<'a> {
    buf: &'a mut [u8; DOUBLE_MAX],
    at: usize,
}

impl core::fmt::Write for SliceSink<'_> {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        let n = s.len().min(self.buf.len() - self.at);
        self.buf[self.at..self.at + n].copy_from_slice(&s.as_bytes()[..n]);
        self.at += n;
        Ok(())
    }
}

/// A `core::fmt::Write` that appends UTF-8 to a byte buffer.
///
/// The float printer only speaks `fmt::Write` and the reply buffer is bytes.
/// This is the whole adapter, and it exists so that no reply path anywhere ever
/// builds a `String` it immediately throws away.
struct Utf8Sink<'a>(&'a mut Vec<u8>);

impl core::fmt::Write for Utf8Sink<'_> {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        self.0.extend_from_slice(s.as_bytes());
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn text(n: i64) -> String {
        let mut v = Vec::new();
        push_i64(&mut v, n);
        String::from_utf8(v).unwrap()
    }

    #[test]
    fn integers_round_trip_through_text() {
        for n in [
            0,
            1,
            9,
            10,
            99,
            100,
            -1,
            -9,
            -10,
            12345,
            -12345,
            i64::MAX,
            i64::MIN,
        ] {
            assert_eq!(text(n), n.to_string(), "writing {n}");
            assert_eq!(parse_i64(text(n).as_bytes()), Some(n), "reading {n}");
        }
    }

    #[test]
    fn the_length_is_known_before_the_digits_are_written() {
        for n in [0, 5, 42, -42, 999, 1000, i64::MAX, i64::MIN] {
            assert_eq!(i64_len(n), text(n).len(), "length of {n}");
        }
    }

    /// Every boundary of the two digit loop, since an off by one there is a
    /// wrong length header rather than a crash and would be found by a client.
    #[test]
    fn every_length_of_number_is_written_correctly() {
        let mut n: u64 = 0;
        for _ in 0..20 {
            for probe in [n, n + 1, n.saturating_sub(1)] {
                let mut v = Vec::new();
                push_u64(&mut v, probe);
                assert_eq!(v, probe.to_string().as_bytes(), "writing {probe}");
            }
            n = n.saturating_mul(10).max(9);
            if n == u64::MAX {
                break;
            }
        }
    }

    #[test]
    fn the_stack_form_writes_what_the_vec_form_writes() {
        // Two implementations of the same digits is the shape of bug that only
        // shows at one boundary, so this checks them against each other rather
        // than against a literal.
        let mut buf = [0u8; DIGITS_MAX];
        let mut n: i64 = 0;
        for _ in 0..19 {
            for probe in [n, -n, n + 1, n - 1] {
                assert_eq!(i64_digits(&mut buf, probe), text(probe).as_bytes());
            }
            n = n.saturating_mul(10).max(9);
        }
        assert_eq!(i64_digits(&mut buf, i64::MIN), text(i64::MIN).as_bytes());
        assert_eq!(i64_digits(&mut buf, i64::MAX), text(i64::MAX).as_bytes());
        assert_eq!(i64_digits(&mut buf, 0), b"0", "and zero is one digit");

        // And the unsigned form past where the signed one stops, which is the
        // whole reason it is there.
        for probe in [0, 1, u64::MAX, 1 << 63, i64::MAX as u64 + 1] {
            let mut v = Vec::new();
            push_u64(&mut v, probe);
            assert_eq!(u64_digits(&mut buf, probe), v.as_slice(), "{probe}");
        }
    }

    #[test]
    fn the_parser_refuses_what_redis_refuses() {
        for bad in [
            &b""[..],
            b"-",
            b"+1",
            b"01",
            b"-01",
            b" 1",
            b"1 ",
            b"1a",
            b"a",
            b"1.0",
            b"-0",
            b"-00",
            b"9223372036854775808",
            b"-9223372036854775809",
            b"99999999999999999999999",
        ] {
            assert_eq!(parse_i64(bad), None, "{:?} should not parse", bad);
        }
        // The one leading zero that is a number, and the one negative that only
        // exists going downwards.
        assert_eq!(parse_i64(b"0"), Some(0));
        assert_eq!(parse_i64(b"-9223372036854775808"), Some(i64::MIN));
    }

    #[test]
    fn the_float_parser_refuses_what_redis_refuses() {
        assert_eq!(parse_f64(b"3.5"), Some(3.5));
        assert_eq!(parse_f64(b"-0"), Some(-0.0));
        assert_eq!(parse_f64(b"3.0e3"), Some(3000.0));
        assert_eq!(parse_f64(b"inf"), Some(f64::INFINITY));
        assert_eq!(parse_f64(b"-inf"), Some(f64::NEG_INFINITY));
        // No whitespace anywhere, nothing trailing, and no NaN, because a
        // stored NaN compares false against itself for the rest of time.
        assert_eq!(parse_f64(b" 3.5"), None);
        assert_eq!(parse_f64(b"3.5 "), None);
        assert_eq!(parse_f64(b"3.5x"), None);
        assert_eq!(parse_f64(b""), None);
        assert_eq!(parse_f64(b"nan"), None);
    }

    #[test]
    fn the_float_parser_takes_hexadecimal_because_strtold_does() {
        // Every one of these was read off a real 8.10.1 before it was written
        // down here.
        assert_eq!(parse_f64(b"0x10"), Some(16.0));
        assert_eq!(parse_f64(b"0X10"), Some(16.0));
        assert_eq!(parse_f64(b"0X1p4"), Some(16.0));
        assert_eq!(parse_f64(b"0x1.8p1"), Some(3.0));
        assert_eq!(parse_f64(b"-0x1.8p1"), Some(-3.0));
        assert_eq!(parse_f64(b"+0x10"), Some(16.0));
        assert_eq!(parse_f64(b"0x1p-1"), Some(0.5));
        assert_eq!(parse_f64(b"0xff"), Some(255.0));

        // A string that starts like a hexadecimal number and then stops being
        // one is refused rather than falling through to the decimal parser,
        // which would read the leading zero and call it a day.
        assert_eq!(parse_f64(b"0x"), None);
        assert_eq!(parse_f64(b"0xzz"), None);
        assert_eq!(parse_f64(b"0x1p"), None);
        assert_eq!(parse_f64(b"0x1.2.3"), None);
        assert_eq!(parse_f64(b"0x10x"), None);
        assert_eq!(parse_f64(b"0x1p99999"), None);
    }

    #[test]
    fn a_mantissa_longer_than_a_double_still_lands_in_the_right_place() {
        // Seventeen hex digits, one more than a u64 holds. The digits past the
        // end are worth four binary places each and nothing else, which is all
        // a double can use them for anyway.
        assert_eq!(
            parse_f64(b"0x10000000000000000"),
            Some(18446744073709551616.0)
        );
        assert_eq!(parse_f64(b"0x1p1024"), None);
    }

    #[test]
    fn doubles_are_written_the_way_redis_writes_them() {
        let cases: &[(f64, &str)] = &[
            (0.0, "0"),
            // Redis checks for zero before it checks for an integer, so this
            // keeps its sign where the integer printer would have dropped it.
            (-0.0, "-0"),
            (3.0, "3"),
            (-3.0, "-3"),
            (3.5, "3.5"),
            (0.1, "0.1"),
            // The integer printer reaches two to the sixty second, and past it
            // the digit generator takes over and switches to an exponent.
            (4.611686018427388e18, "4611686018427387904"),
            (1e19, "1e+19"),
            (1e30, "1e+30"),
            (1e-7, "1e-7"),
            (1e-6, "0.000001"),
            (5e-324, "5e-324"),
            (f64::INFINITY, "inf"),
            (f64::NEG_INFINITY, "-inf"),
            (f64::NAN, "nan"),
        ];
        for &(d, want) in cases {
            let mut v = Vec::new();
            push_double(&mut v, d);
            assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
        }
    }

    /// The human printer is the other one, and the difference is the exponent.
    ///
    /// `INCRBYFLOAT` and `HINCRBYFLOAT` are the only two commands that use it,
    /// and the reason it exists as a separate thing is the last four rows: a
    /// fixed point conversion has no exponent form to switch to, so a magnitude
    /// that comes back as `1e+30` from a score comes back written out in full
    /// from an increment.
    #[test]
    fn the_increment_printer_never_writes_an_exponent() {
        let cases: &[(f64, &str)] = &[
            (0.0, "0"),
            // The human mode says so explicitly, where `d2string` keeps it.
            (-0.0, "0"),
            (3.0, "3"),
            (3.5, "3.5"),
            (0.1, "0.1"),
            (10.5, "10.5"),
            (0.30000000000000004, "0.30000000000000004"),
            (1e30, "1000000000000000000000000000000"),
            (1e19, "10000000000000000000"),
            (1e-7, "0.0000001"),
            (f64::INFINITY, "inf"),
            (f64::NEG_INFINITY, "-inf"),
            (f64::NAN, "nan"),
        ];
        for &(d, want) in cases {
            let mut v = Vec::new();
            push_human(&mut v, d);
            assert_eq!(String::from_utf8(v).unwrap(), want, "writing {d}");
        }
        // The smallest subnormal, which is where the lack of an exponent form
        // costs the most: `0.` and then three hundred and twenty four places.
        let mut v = Vec::new();
        push_human(&mut v, 5e-324);
        assert_eq!(v.len(), 326);
        assert!(v.starts_with(b"0.0") && v.ends_with(b"5"));
    }

    /// The two double writers have to agree, because one is used to predict the
    /// other.
    ///
    /// The array type decides whether a value can be stored as a double by
    /// formatting it with `write_double` and checking the bytes against what the
    /// client sent, and then the reply comes out of `push_double`. If they ever
    /// disagreed, a value would go in as a number and come back out as
    /// different text.
    #[test]
    fn the_two_double_writers_agree() {
        let mut cases = vec![
            0.0,
            -0.0,
            1.0,
            -1.0,
            3.5,
            0.1,
            -0.1,
            1e-320,
            f64::MIN_POSITIVE,
            f64::MAX,
            f64::MIN,
            DOUBLE_INT_LIMIT,
            -DOUBLE_INT_LIMIT,
            DOUBLE_INT_LIMIT + 2.0,
            f64::INFINITY,
            f64::NEG_INFINITY,
            f64::NAN,
        ];
        // A spread of ordinary values, so the agreement is not only about the
        // corners that were thought of in advance.
        for i in -400..400 {
            cases.push(f64::from(i) / 7.0);
            cases.push(f64::from(i) * 1e12);
        }
        for d in cases {
            let mut v = Vec::new();
            push_double(&mut v, d);
            let mut buf = [0u8; DOUBLE_MAX];
            assert_eq!(write_double(&mut buf, d), &v[..], "writing {d}");
        }
    }

    /// The fixed buffer is big enough for the widest double there is.
    ///
    /// `write_double` truncates rather than panicking if it is not, so a bad
    /// constant would show up as a wrong answer somewhere far away instead of
    /// here.
    #[test]
    fn the_fixed_buffer_holds_the_widest_double() {
        let mut widest = 0;
        for d in [f64::MIN, f64::MAX, f64::from_bits(1), -f64::from_bits(1)] {
            let mut v = Vec::new();
            push_double(&mut v, d);
            widest = widest.max(v.len());
        }
        assert!(widest <= DOUBLE_MAX, "{widest} bytes needs more than room");
    }

    /// Seventeen significant digits, the two forms, and the trailing zeros off
    /// both of them.
    ///
    /// The expected bytes here are what C's `%.17g` prints, which is what Redis
    /// replies to `AROP` with, and it is not what the rest of the server writes
    /// for a double: three tenths is `0.29999999999999999` in this printer and
    /// `0.3` in the other one.
    #[test]
    fn the_aggregate_printer_writes_seventeen_significant_digits() {
        let cases: &[(f64, &str)] = &[
            (0.0, "0"),
            (-0.0, "-0"),
            (1.0, "1"),
            (-1.0, "-1"),
            (0.5, "0.5"),
            (0.1, "0.10000000000000001"),
            (0.3, "0.29999999999999999"),
            (0.1 + 0.2, "0.30000000000000004"),
            (1.0 / 3.0, "0.33333333333333331"),
            (0.0001, "0.0001"),
            // Below a ten thousandth is where the exponent form starts, and C
            // writes two exponent digits where Rust would write one.
            (1.5e-5, "1.5e-05"),
            (1e-5, "1.0000000000000001e-05"),
            (1e16, "10000000000000000"),
            // And it starts again once the digits run out at seventeen.
            (1e17, "1e+17"),
            (1e30, "1e+30"),
            (-1e30, "-1e+30"),
            (1e100, "1e+100"),
            (f64::MAX, "1.7976931348623157e+308"),
            (f64::from_bits(1), "4.9406564584124654e-324"),
            (12345678901234567.0, "12345678901234568"),
            (f64::INFINITY, "inf"),
            (f64::NEG_INFINITY, "-inf"),
            (f64::NAN, "nan"),
        ];
        for &(d, want) in cases {
            let mut buf = [0u8; DOUBLE_MAX];
            assert_eq!(
                core::str::from_utf8(write_g17(&mut buf, d)).unwrap(),
                want,
                "writing {d}"
            );
            let mut v = Vec::new();
            push_g17(&mut v, d);
            assert_eq!(String::from_utf8(v).unwrap(), want, "appending {d}");
        }
    }
}