Hexadecimal printing function
Add IntToHexString, a simple hexadecimal output function.
PointerToString uses IntToHexString instead of IntToString.
Change-Id: Ib64ab888850d5050da84b4b84a2a279f0381f48f
diff --git a/pw_string/public/pw_string/type_to_string.h b/pw_string/public/pw_string/type_to_string.h
index f2f031d..90a91cd 100644
--- a/pw_string/public/pw_string/type_to_string.h
+++ b/pw_string/public/pw_string/type_to_string.h
@@ -30,6 +30,12 @@
// non-negative integer. Returns 1 for 0 or 1 + log base 10 for other numbers.
uint_fast8_t DecimalDigitCount(uint64_t integer);
+// Returns the number of digits in the hexadecimal representation of the
+// provided non-negative integer.
+constexpr uint_fast8_t HexDigitCount(uint64_t integer) {
+ return (64u - __builtin_clzll(integer | 1u) + 3u) / 4u;
+}
+
// Writes an integer as a null-terminated string in base 10. Returns the number
// of characters written, excluding the null terminator, and the status.
//
@@ -63,6 +69,10 @@
template <>
StatusWithSize IntToString(int64_t integer, const span<char>& buffer);
+// Writes an integer as a hexadecimal string. Semantics match IntToString. The
+// output is lowercase without a leading 0x.
+StatusWithSize IntToHexString(uint64_t value, const span<char>& buffer);
+
// Rounds a floating point number to an integer and writes it as a
// null-terminated string. Returns the number of characters written, excluding
// the null terminator, and the status.
diff --git a/pw_string/to_string_test.cc b/pw_string/to_string_test.cc
index e34f0ec..754c96f 100644
--- a/pw_string/to_string_test.cc
+++ b/pw_string/to_string_test.cc
@@ -115,7 +115,7 @@
CustomType custom;
const size_t length = std::snprintf(expected,
sizeof(expected),
- "%" PRIdPTR,
+ "%" PRIxPTR,
reinterpret_cast<intptr_t>(&custom));
EXPECT_EQ(length, ToString(&custom, buffer).size());
diff --git a/pw_string/type_to_string.cc b/pw_string/type_to_string.cc
index 829dfdd..74235b5 100644
--- a/pw_string/type_to_string.cc
+++ b/pw_string/type_to_string.cc
@@ -47,6 +47,13 @@
10000000000000000000ull, // 10^19
};
+StatusWithSize HandleExhaustedBuffer(const span<char>& buffer) {
+ if (!buffer.empty()) {
+ buffer[0] = '\0';
+ }
+ return StatusWithSize(Status::RESOURCE_EXHAUSTED, 0);
+}
+
} // namespace
uint_fast8_t DecimalDigitCount(uint64_t integer) {
@@ -73,10 +80,7 @@
const uint_fast8_t total_digits = DecimalDigitCount(value);
if (total_digits >= buffer.size()) {
- if (!buffer.empty()) {
- buffer[0] = '\0';
- }
- return StatusWithSize(Status::RESOURCE_EXHAUSTED, 0);
+ return HandleExhaustedBuffer(buffer);
}
buffer[total_digits] = '\0';
@@ -106,6 +110,22 @@
return StatusWithSize(total_digits);
}
+StatusWithSize IntToHexString(uint64_t value, const span<char>& buffer) {
+ const uint_fast8_t digits = HexDigitCount(value);
+
+ if (digits >= buffer.size()) {
+ return HandleExhaustedBuffer(buffer);
+ }
+
+ for (int i = digits - 1; i >= 0; --i) {
+ buffer[i] = "0123456789abcdef"[value & 0xF];
+ value >>= 4;
+ }
+
+ buffer[digits] = '\0';
+ return StatusWithSize(digits);
+}
+
template <>
StatusWithSize IntToString(int64_t value, const span<char>& buffer) {
if (value >= 0) {
@@ -121,10 +141,7 @@
return StatusWithSize(result.size() + 1);
}
- if (!buffer.empty()) {
- buffer[0] = '\0';
- }
- return StatusWithSize(Status::RESOURCE_EXHAUSTED, 0);
+ return HandleExhaustedBuffer(buffer);
}
// TODO(hepler): Look into using the float overload of std::to_chars when it is
@@ -147,10 +164,7 @@
return StatusWithSize(written);
}
- if (!buffer.empty()) {
- buffer[0] = '\0';
- }
- return StatusWithSize(Status::RESOURCE_EXHAUSTED, 0);
+ return HandleExhaustedBuffer(buffer);
}
StatusWithSize BoolToString(bool value, const span<char>& buffer) {
@@ -161,8 +175,7 @@
if (pointer == nullptr) {
return CopyEntireString("null", buffer);
}
- // TODO(hepler): Add support for hexadecimal output.
- return IntToString(reinterpret_cast<uintptr_t>(pointer), buffer);
+ return IntToHexString(reinterpret_cast<uintptr_t>(pointer), buffer);
}
StatusWithSize CopyString(const std::string_view& value,
@@ -181,10 +194,7 @@
StatusWithSize CopyEntireString(const std::string_view& value,
const span<char>& buffer) {
if (value.size() >= buffer.size()) {
- if (!buffer.empty()) {
- buffer[0] = '\0';
- }
- return StatusWithSize(Status::RESOURCE_EXHAUSTED, 0);
+ return HandleExhaustedBuffer(buffer);
}
std::memcpy(buffer.data(), value.data(), value.size());
diff --git a/pw_string/type_to_string_test.cc b/pw_string/type_to_string_test.cc
index 389f9f6..af1a607 100644
--- a/pw_string/type_to_string_test.cc
+++ b/pw_string/type_to_string_test.cc
@@ -57,6 +57,39 @@
}
}
+TEST(Digits, HexDigits_AllOneDigit) {
+ for (uint64_t i = 0; i < 0x10; ++i) {
+ ASSERT_EQ(1u, HexDigitCount(i));
+ }
+}
+
+TEST(Digits, HexDigits_AllTwoDigit) {
+ for (uint64_t i = 0x10; i < 0x100u; ++i) {
+ ASSERT_EQ(2u, HexDigitCount(i));
+ }
+}
+
+TEST(Digits, HexDigits_1To15Digits) {
+ uint64_t value = 1;
+ for (unsigned digits = 1; digits <= 15u; ++digits) {
+ ASSERT_EQ(digits, HexDigitCount(value));
+ ASSERT_EQ(digits, HexDigitCount(value + 1));
+
+ value *= 0x10;
+ ASSERT_EQ(digits, HexDigitCount(value - 1));
+ }
+}
+
+TEST(Digits, HexDigits_16) {
+ for (uint64_t i : {
+ 0x1000000000000000llu,
+ 0x1000000000000001llu,
+ std::numeric_limits<unsigned long long>::max(),
+ }) {
+ ASSERT_EQ(16u, HexDigitCount(i));
+ }
+}
+
class TestWithBuffer : public ::testing::Test {
protected:
static constexpr char kStartingString[] = "!@#$%^&*()!@#$%^&*()";
@@ -233,6 +266,57 @@
}
}
+class IntToHexStringTest : public TestWithBuffer {};
+
+TEST_F(IntToHexStringTest, Sweep) {
+ for (unsigned i = 0; i < 1030; ++i) {
+ char hex[16];
+ int bytes = std::snprintf(hex, sizeof(hex), "%x", static_cast<unsigned>(i));
+
+ auto result = IntToHexString(i, buffer_);
+ EXPECT_EQ(static_cast<size_t>(bytes), result.size());
+ EXPECT_TRUE(result.ok());
+ EXPECT_STREQ(hex, buffer_);
+ }
+}
+
+TEST_F(IntToHexStringTest, Uint32Max) {
+ EXPECT_EQ(
+ 8u,
+ IntToHexString(std::numeric_limits<uint32_t>::max() - 1, buffer_).size());
+ EXPECT_STREQ("fffffffe", buffer_);
+
+ EXPECT_EQ(
+ 8u, IntToHexString(std::numeric_limits<uint32_t>::max(), buffer_).size());
+ EXPECT_STREQ("ffffffff", buffer_);
+}
+
+TEST_F(IntToHexStringTest, Uint64Max) {
+ EXPECT_EQ(
+ 16u,
+ IntToHexString(std::numeric_limits<uint64_t>::max() - 1, buffer_).size());
+ EXPECT_STREQ("fffffffffffffffe", buffer_);
+
+ EXPECT_EQ(
+ 16u,
+ IntToHexString(std::numeric_limits<uint64_t>::max(), buffer_).size());
+ EXPECT_STREQ("ffffffffffffffff", buffer_);
+}
+
+TEST_F(IntToHexStringTest, EmptyBuffer_WritesNothing) {
+ auto result = IntToHexString(0xbeef, span(buffer_, 0));
+ EXPECT_EQ(0u, result.size());
+ EXPECT_FALSE(result.ok());
+ EXPECT_STREQ(kStartingString, buffer_);
+}
+
+TEST_F(IntToHexStringTest, TooSmall_Truncates) {
+ auto result = IntToHexString(0xbeef, span(buffer_, 3));
+ EXPECT_EQ(0u, result.size());
+ EXPECT_FALSE(result.ok());
+ EXPECT_STREQ("", buffer_);
+}
+
class FloatAsIntToStringTest : public TestWithBuffer {};
TEST_F(FloatAsIntToStringTest, PositiveInfinity) {
@@ -393,9 +477,9 @@
}
TEST_F(PointerToStringTest, WritesAddress) {
- const void* pointer = reinterpret_cast<void*>(321);
- EXPECT_EQ(3u, PointerToString(pointer, buffer_).size());
- EXPECT_STREQ("321", buffer_);
+ const void* pointer = reinterpret_cast<void*>(0xbeef);
+ EXPECT_EQ(4u, PointerToString(pointer, buffer_).size());
+ EXPECT_STREQ("beef", buffer_);
}
class BoolToStringTest : public TestWithBuffer {};