Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5b74275

Browse files
committed
Auto merge of #142294 - GuillaumeGomez:specialize-tostring-on-128-integers, r=tgross35
Use a distinct `ToString` implementation for `u128` and `i128` Part of #135543. Follow-up of #136264. When working on #142098, I realized that `i128` and `u128` could also benefit from a distinct `ToString` implementation so here it. The last commit is just me realizing that I forgot to add the format tests for `usize` and `isize`. Here is the bench comparison: | bench name | last nightly | with this PR | diff | |-|-|-|-| | bench_i128 | 29.25 ns/iter (+/- 0.66) | 17.52 ns/iter (+/- 0.7) | -40.1% | | bench_u128 | 34.06 ns/iter (+/- 0.21) | 16.1 ns/iter (+/- 0.6) | -52.7% | I used this code to test: ```rust #![feature(test)] extern crate test; use test::{Bencher, black_box}; #[inline(always)] fn convert_to_string<T: ToString>(n: T) -> String { n.to_string() } macro_rules! decl_benches { ($($name:ident: $ty:ident,)+) => { $( #[bench] fn $name(c: &mut Bencher) { c.iter(|| convert_to_string(black_box({ let nb: $ty = 20; nb }))); } )+ } } decl_benches! { bench_u128: u128, bench_i128: i128, } ```
2 parents 255aa22 + 9b09948 commit 5b74275

File tree

3 files changed

+110
-94
lines changed

3 files changed

+110
-94
lines changed

‎library/alloc/src/string.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2877,6 +2877,7 @@ impl_to_string! {
28772877
i32, u32,
28782878
i64, u64,
28792879
isize, usize,
2880+
i128, u128,
28802881
}
28812882

28822883
#[cfg(not(no_global_oom_handling))]

‎library/alloctests/tests/num.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ int_to_s!(
5252
i32,
5353
test_i64_to_string,
5454
i64,
55+
test_isize_to_string,
56+
isize,
5557
test_i128_to_string,
5658
i128,
5759
);
@@ -64,6 +66,8 @@ uint_to_s!(
6466
u32,
6567
test_u64_to_string,
6668
u64,
69+
test_usize_to_string,
70+
usize,
6771
test_u128_to_string,
6872
u128,
6973
);

‎library/core/src/fmt/num.rs‎

Lines changed: 105 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -565,123 +565,134 @@ mod imp {
565565
}
566566
impl_Exp!(i128, u128 as u128 via to_u128 named exp_u128);
567567

568+
const U128_MAX_DEC_N: usize = u128::MAX.ilog(10) as usize + 1;
569+
568570
#[stable(feature = "rust1", since = "1.0.0")]
569571
impl fmt::Display for u128 {
570572
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
571-
fmt_u128(*self, true, f)
573+
let mut buf = [MaybeUninit::<u8>::uninit(); U128_MAX_DEC_N];
574+
575+
f.pad_integral(true, "", self._fmt(&mut buf))
572576
}
573577
}
574578

575579
#[stable(feature = "rust1", since = "1.0.0")]
576580
impl fmt::Display for i128 {
577581
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578-
fmt_u128(self.unsigned_abs(), *self >= 0, f)
582+
// This is not a typo, we use the maximum number of digits of `u128`, hence why we use
583+
// `U128_MAX_DEC_N`.
584+
let mut buf = [MaybeUninit::<u8>::uninit(); U128_MAX_DEC_N];
585+
586+
let is_nonnegative = *self >= 0;
587+
f.pad_integral(is_nonnegative, "", self.unsigned_abs()._fmt(&mut buf))
579588
}
580589
}
581590

582-
/// Format optimized for u128. Computation of 128 bits is limited by proccessing
583-
/// in batches of 16 decimals at a time.
584-
fn fmt_u128(n: u128, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585-
// Optimize common-case zero, which would also need special treatment due to
586-
// its "leading" zero.
587-
if n == 0 {
588-
return f.pad_integral(true, "", "0");
589-
}
591+
impl u128 {
592+
/// Format optimized for u128. Computation of 128 bits is limited by proccessing
593+
/// in batches of 16 decimals at a time.
594+
#[doc(hidden)]
595+
#[unstable(
596+
feature = "fmt_internals",
597+
reason = "specialized method meant to only be used by `SpecToString` implementation",
598+
issue = "none"
599+
)]
600+
pub fn _fmt<'a>(self, buf: &'a mut [MaybeUninit<u8>]) -> &'a str {
601+
// Optimize common-case zero, which would also need special treatment due to
602+
// its "leading" zero.
603+
if self == 0 {
604+
return "0";
605+
}
590606

591-
// U128::MAX has 39 significant-decimals.
592-
const MAX_DEC_N: usize = u128::MAX.ilog(10) as usize + 1;
593-
// Buffer decimals with right alignment.
594-
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
595-
596-
// Take the 16 least-significant decimals.
597-
let (quot_1e16, mod_1e16) = div_rem_1e16(n);
598-
let (mut remain, mut offset) = if quot_1e16 == 0 {
599-
(mod_1e16, MAX_DEC_N)
600-
} else {
601-
// Write digits at buf[23..39].
602-
enc_16lsd::<{ MAX_DEC_N - 16 }>(&mut buf, mod_1e16);
603-
604-
// Take another 16 decimals.
605-
let (quot2, mod2) = div_rem_1e16(quot_1e16);
606-
if quot2 == 0 {
607-
(mod2, MAX_DEC_N - 16)
607+
// Take the 16 least-significant decimals.
608+
let (quot_1e16, mod_1e16) = div_rem_1e16(self);
609+
let (mut remain, mut offset) = if quot_1e16 == 0 {
610+
(mod_1e16, U128_MAX_DEC_N)
608611
} else {
609-
// Write digits at buf[7..23].
610-
enc_16lsd::<{ MAX_DEC_N - 32 }>(&mut buf, mod2);
611-
// Quot2 has at most 7 decimals remaining after two 1e16 divisions.
612-
(quot2 as u64, MAX_DEC_N - 32)
613-
}
614-
};
612+
// Write digits at buf[23..39].
613+
enc_16lsd::<{ U128_MAX_DEC_N - 16 }>(buf, mod_1e16);
615614

616-
// Format per four digits from the lookup table.
617-
while remain > 999 {
618-
// SAFETY: All of the decimals fit in buf due to MAX_DEC_N
619-
// and the while condition ensures at least 4 more decimals.
620-
unsafe { core::hint::assert_unchecked(offset >= 4) }
621-
// SAFETY: The offset counts down from its initial buf.len()
622-
// without underflow due to the previous precondition.
623-
unsafe { core::hint::assert_unchecked(offset <= buf.len()) }
624-
offset -= 4;
615+
// Take another 16 decimals.
616+
let (quot2, mod2) = div_rem_1e16(quot_1e16);
617+
if quot2 == 0 {
618+
(mod2, U128_MAX_DEC_N - 16)
619+
} else {
620+
// Write digits at buf[7..23].
621+
enc_16lsd::<{ U128_MAX_DEC_N - 32 }>(buf, mod2);
622+
// Quot2 has at most 7 decimals remaining after two 1e16 divisions.
623+
(quot2 as u64, U128_MAX_DEC_N - 32)
624+
}
625+
};
625626

626-
// pull two pairs
627-
let quad = remain % 1_00_00;
628-
remain /= 1_00_00;
629-
let pair1 = (quad / 100) as usize;
630-
let pair2 = (quad % 100) as usize;
631-
buf[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]);
632-
buf[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]);
633-
buf[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]);
634-
buf[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]);
635-
}
627+
// Format per four digits from the lookup table.
628+
while remain > 999 {
629+
// SAFETY: All of the decimals fit in buf due to U128_MAX_DEC_N
630+
// and the while condition ensures at least 4 more decimals.
631+
unsafe { core::hint::assert_unchecked(offset >= 4) }
632+
// SAFETY: The offset counts down from its initial buf.len()
633+
// without underflow due to the previous precondition.
634+
unsafe { core::hint::assert_unchecked(offset <= buf.len()) }
635+
offset -= 4;
636+
637+
// pull two pairs
638+
let quad = remain % 1_00_00;
639+
remain /= 1_00_00;
640+
let pair1 = (quad / 100) as usize;
641+
let pair2 = (quad % 100) as usize;
642+
buf[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]);
643+
buf[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]);
644+
buf[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]);
645+
buf[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]);
646+
}
636647

637-
// Format per two digits from the lookup table.
638-
if remain > 9 {
639-
// SAFETY: All of the decimals fit in buf due to MAX_DEC_N
640-
// and the if condition ensures at least 2 more decimals.
641-
unsafe { core::hint::assert_unchecked(offset >= 2) }
642-
// SAFETY: The offset counts down from its initial buf.len()
643-
// without underflow due to the previous precondition.
644-
unsafe { core::hint::assert_unchecked(offset <= buf.len()) }
645-
offset -= 2;
646-
647-
let pair = (remain % 100) as usize;
648-
remain /= 100;
649-
buf[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]);
650-
buf[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]);
651-
}
648+
// Format per two digits from the lookup table.
649+
if remain > 9 {
650+
// SAFETY: All of the decimals fit in buf due to U128_MAX_DEC_N
651+
// and the if condition ensures at least 2 more decimals.
652+
unsafe { core::hint::assert_unchecked(offset >= 2) }
653+
// SAFETY: The offset counts down from its initial buf.len()
654+
// without underflow due to the previous precondition.
655+
unsafe { core::hint::assert_unchecked(offset <= buf.len()) }
656+
offset -= 2;
657+
658+
let pair = (remain % 100) as usize;
659+
remain /= 100;
660+
buf[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]);
661+
buf[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]);
662+
}
652663

653-
// Format the last remaining digit, if any.
654-
if remain != 0 {
655-
// SAFETY: All of the decimals fit in buf due to MAX_DEC_N
656-
// and the if condition ensures (at least) 1 more decimals.
657-
unsafe { core::hint::assert_unchecked(offset >= 1) }
658-
// SAFETY: The offset counts down from its initial buf.len()
659-
// without underflow due to the previous precondition.
660-
unsafe { core::hint::assert_unchecked(offset <= buf.len()) }
661-
offset -= 1;
662-
663-
// Either the compiler sees that remain < 10, or it prevents
664-
// a boundary check up next.
665-
let last = (remain & 15) as usize;
666-
buf[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
667-
// not used: remain = 0;
668-
}
664+
// Format the last remaining digit, if any.
665+
if remain != 0 {
666+
// SAFETY: All of the decimals fit in buf due to U128_MAX_DEC_N
667+
// and the if condition ensures (at least) 1 more decimals.
668+
unsafe { core::hint::assert_unchecked(offset >= 1) }
669+
// SAFETY: The offset counts down from its initial buf.len()
670+
// without underflow due to the previous precondition.
671+
unsafe { core::hint::assert_unchecked(offset <= buf.len()) }
672+
offset -= 1;
673+
674+
// Either the compiler sees that remain < 10, or it prevents
675+
// a boundary check up next.
676+
let last = (remain & 15) as usize;
677+
buf[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
678+
// not used: remain = 0;
679+
}
669680

670-
// SAFETY: All buf content since offset is set.
671-
let written = unsafe { buf.get_unchecked(offset..) };
672-
// SAFETY: Writes use ASCII from the lookup table exclusively.
673-
let as_str = unsafe {
674-
str::from_utf8_unchecked(slice::from_raw_parts(
675-
MaybeUninit::slice_as_ptr(written),
676-
written.len(),
677-
))
678-
};
679-
f.pad_integral(is_nonnegative,"", as_str)
681+
// SAFETY: All buf content since offset is set.
682+
let written = unsafe { buf.get_unchecked(offset..) };
683+
// SAFETY: Writes use ASCII from the lookup table exclusively.
684+
unsafe {
685+
str::from_utf8_unchecked(slice::from_raw_parts(
686+
MaybeUninit::slice_as_ptr(written),
687+
written.len(),
688+
))
689+
}
690+
}
680691
}
681692

682693
/// Encodes the 16 least-significant decimals of n into `buf[OFFSET .. OFFSET +
683694
/// 16 ]`.
684-
fn enc_16lsd<const OFFSET: usize>(buf: &mut [MaybeUninit<u8>;39], n: u64) {
695+
fn enc_16lsd<const OFFSET: usize>(buf: &mut [MaybeUninit<u8>], n: u64) {
685696
// Consume the least-significant decimals from a working copy.
686697
let mut remain = n;
687698

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /