diff --git a/AK/String.cpp b/AK/String.cpp index f74662b654f..37d8381003b 100644 --- a/AK/String.cpp +++ b/AK/String.cpp @@ -531,4 +531,57 @@ String String::roman_number_from(size_t value, Case target_case) return builder.to_string_without_validation(); } +template +String String::number(T value) +{ + // Maximum number of base-10 digits for T + sign + constexpr size_t max_digits = sizeof(T) * 3 + 2; + char buffer[max_digits]; + char* ptr = buffer + max_digits; + bool is_negative = false; + + using UnsignedT = MakeUnsigned; + + UnsignedT unsigned_value; + if constexpr (IsSigned) { + if (value < 0) { + is_negative = true; + // Handle signed min correctly + unsigned_value = static_cast(0) - static_cast(value); + } else { + unsigned_value = static_cast(value); + } + } else { + unsigned_value = value; + } + + if (unsigned_value == 0) { + *--ptr = '0'; + } else { + while (unsigned_value != 0) { + *--ptr = '0' + (unsigned_value % 10); + unsigned_value /= 10; + } + } + + if (is_negative) { + *--ptr = '-'; + } + + size_t size = buffer + max_digits - ptr; + return from_utf8_without_validation(ReadonlyBytes { ptr, size }); +} + +template String String::number(char); +template String String::number(signed char); +template String String::number(unsigned char); +template String String::number(signed short); +template String String::number(unsigned short); +template String String::number(int); +template String String::number(unsigned int); +template String String::number(long); +template String String::number(unsigned long); +template String String::number(long long); +template String String::number(unsigned long long); + } diff --git a/AK/String.h b/AK/String.h index da02061ea73..c4d42fc9670 100644 --- a/AK/String.h +++ b/AK/String.h @@ -185,7 +185,10 @@ public: [[nodiscard]] u32 ascii_case_insensitive_hash() const; - template + template + [[nodiscard]] static String number(T); + + template [[nodiscard]] static String number(T value) { return MUST(formatted("{}", value)); diff --git a/Tests/AK/TestString.cpp b/Tests/AK/TestString.cpp index 3bf5c38a2bf..89fd1c47935 100644 --- a/Tests/AK/TestString.cpp +++ b/Tests/AK/TestString.cpp @@ -1561,3 +1561,45 @@ TEST_CASE(roman_numerals) auto four_thousand = String::roman_number_from(4000, String::Case::Upper); EXPECT_EQ(four_thousand, "4000"sv); } + +BENCHMARK_CASE(string_number_u16) +{ + for (size_t i = 0; i < 10'000'000; ++i) { + (void)String::number(static_cast(12345)); + } +} + +BENCHMARK_CASE(string_number_u32) +{ + for (size_t i = 0; i < 10'000'000; ++i) { + (void)String::number(static_cast(123456789)); + } +} + +BENCHMARK_CASE(string_number_u64) +{ + for (size_t i = 0; i < 10'000'000; ++i) { + (void)String::number(static_cast(123456789)); + } +} + +BENCHMARK_CASE(string_number_i16) +{ + for (size_t i = 0; i < 10'000'000; ++i) { + (void)String::number(static_cast(-12345)); + } +} + +BENCHMARK_CASE(string_number_i32) +{ + for (size_t i = 0; i < 10'000'000; ++i) { + (void)String::number(static_cast(-123456789)); + } +} + +BENCHMARK_CASE(string_number_i64) +{ + for (size_t i = 0; i < 10'000'000; ++i) { + (void)String::number(static_cast(-123456789)); + } +}