diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index b09e8ccbdf6..dc4560b5ef1 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -539,10 +539,10 @@ static size_t value_accuracy_to_buf_fast(char *buf, float value, int8_t accuracy // float*int loses bits at exact-half boundaries (e.g. 23.45f*10 = 234.5 in float, // but snprintf sees 234.500007... via double promotion and rounds differently). uint32_t scaled = static_cast(lrint(static_cast(value) * mult)); - p = uint32_to_str(p, scaled / mult); + p = uint32_to_str_(p, scaled / mult); if (accuracy_decimals > 0) { *p++ = '.'; - p = frac_to_str(p, scaled % mult, mult / 10); + p = frac_to_str_(p, scaled % mult, mult / 10); } *p = '\0'; return static_cast(p - buf); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 3b93a2c4763..11696772bea 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1311,20 +1311,14 @@ inline char *buf_append_sep_str(char *buf, size_t remaining, char separator, con } /// Return 10^n for small non-negative n (0-3) as uint32_t, avoiding float. -inline uint32_t small_pow10(int8_t n) { - if (n == 1) { - return 10; - } else if (n == 2) { - return 100; - } else if (n == 3) { - return 1000; - } - return 1; -} +inline uint32_t small_pow10(int8_t n) { return n == 3 ? 1000 : n == 2 ? 100 : n == 1 ? 10 : 1; } -/// Write unsigned 32-bit integer to buffer. Returns pointer past last char written. -/// Buffer must have at least 10 bytes free (max uint32 is 4294967295). -inline char *uint32_to_str(char *buf, uint32_t val) { +/// Minimum buffer size for uint32_to_str: 10 digits + null terminator. +static constexpr size_t UINT32_MAX_STR_SIZE = 11; + +/// Write unsigned 32-bit integer to buffer (internal, no size check). +/// Buffer must have at least 10 bytes free. Returns pointer past last char written. +inline char *uint32_to_str_(char *buf, uint32_t val) { if (val == 0) { *buf++ = '0'; return buf; @@ -1338,10 +1332,19 @@ inline char *uint32_to_str(char *buf, uint32_t val) { return buf; } -/// Write fractional digits with leading zeros to buffer. +/// Write unsigned 32-bit integer to buffer with compile-time size check. +/// Null-terminates the output. Returns number of chars written (excluding null). +template inline size_t uint32_to_str(char (&buf)[N], uint32_t val) { + static_assert(N >= UINT32_MAX_STR_SIZE, "Buffer too small for uint32 (need 11 bytes)"); + char *end = uint32_to_str_(buf, val); + *end = '\0'; + return static_cast(end - buf); +} + +/// Write fractional digits with leading zeros to buffer (internal, no size check). /// frac is the fractional value, divisor is the highest place value (e.g. 100 for 3 digits). /// Returns pointer past last char written. -inline char *frac_to_str(char *buf, uint32_t frac, uint32_t divisor) { +inline char *frac_to_str_(char *buf, uint32_t frac, uint32_t divisor) { while (divisor > 0) { *buf++ = '0' + static_cast(frac / divisor); frac %= divisor; diff --git a/tests/components/core/test_helpers.cpp b/tests/components/core/test_helpers.cpp index 261a4111b16..6c0490b6c60 100644 --- a/tests/components/core/test_helpers.cpp +++ b/tests/components/core/test_helpers.cpp @@ -16,7 +16,7 @@ TEST(SmallPow10, Three) { EXPECT_EQ(small_pow10(3), 1000u); } TEST(Uint32ToStr, Zero) { char buf[12]; - char *end = uint32_to_str(buf, 0); + char *end = uint32_to_str_(buf, 0); *end = '\0'; EXPECT_STREQ(buf, "0"); EXPECT_EQ(end - buf, 1); @@ -24,14 +24,14 @@ TEST(Uint32ToStr, Zero) { TEST(Uint32ToStr, SingleDigit) { char buf[12]; - char *end = uint32_to_str(buf, 7); + char *end = uint32_to_str_(buf, 7); *end = '\0'; EXPECT_STREQ(buf, "7"); } TEST(Uint32ToStr, MultiDigit) { char buf[12]; - char *end = uint32_to_str(buf, 12345); + char *end = uint32_to_str_(buf, 12345); *end = '\0'; EXPECT_STREQ(buf, "12345"); EXPECT_EQ(end - buf, 5); @@ -39,7 +39,7 @@ TEST(Uint32ToStr, MultiDigit) { TEST(Uint32ToStr, Large) { char buf[12]; - char *end = uint32_to_str(buf, 4294967295u); + char *end = uint32_to_str_(buf, 4294967295u); *end = '\0'; EXPECT_STREQ(buf, "4294967295"); EXPECT_EQ(end - buf, 10); @@ -49,24 +49,36 @@ TEST(Uint32ToStr, PowersOfTen) { char buf[12]; char *end; - end = uint32_to_str(buf, 10); + end = uint32_to_str_(buf, 10); *end = '\0'; EXPECT_STREQ(buf, "10"); - end = uint32_to_str(buf, 100); + end = uint32_to_str_(buf, 100); *end = '\0'; EXPECT_STREQ(buf, "100"); - end = uint32_to_str(buf, 1000); + end = uint32_to_str_(buf, 1000); *end = '\0'; EXPECT_STREQ(buf, "1000"); } -// --- frac_to_str() --- +// --- uint32_to_str() (public, template with size check) --- + +TEST(Uint32ToStr, PublicApi) { + char buf[UINT32_MAX_STR_SIZE]; + EXPECT_EQ(uint32_to_str(buf, 0), 1u); + EXPECT_STREQ(buf, "0"); + EXPECT_EQ(uint32_to_str(buf, 12345), 5u); + EXPECT_STREQ(buf, "12345"); + EXPECT_EQ(uint32_to_str(buf, 4294967295u), 10u); + EXPECT_STREQ(buf, "4294967295"); +} + +// --- frac_to_str_() --- TEST(FracToStr, OneDigit) { char buf[8]; - char *end = frac_to_str(buf, 5, 1); + char *end = frac_to_str_(buf, 5, 1); *end = '\0'; EXPECT_STREQ(buf, "5"); EXPECT_EQ(end - buf, 1); @@ -74,14 +86,14 @@ TEST(FracToStr, OneDigit) { TEST(FracToStr, TwoDigits) { char buf[8]; - char *end = frac_to_str(buf, 46, 10); + char *end = frac_to_str_(buf, 46, 10); *end = '\0'; EXPECT_STREQ(buf, "46"); } TEST(FracToStr, ThreeDigits) { char buf[8]; - char *end = frac_to_str(buf, 456, 100); + char *end = frac_to_str_(buf, 456, 100); *end = '\0'; EXPECT_STREQ(buf, "456"); EXPECT_EQ(end - buf, 3); @@ -89,22 +101,22 @@ TEST(FracToStr, ThreeDigits) { TEST(FracToStr, LeadingZeros) { char buf[8]; - char *end = frac_to_str(buf, 1, 100); + char *end = frac_to_str_(buf, 1, 100); *end = '\0'; EXPECT_STREQ(buf, "001"); - end = frac_to_str(buf, 5, 10); + end = frac_to_str_(buf, 5, 10); *end = '\0'; EXPECT_STREQ(buf, "05"); } TEST(FracToStr, AllZeros) { char buf[8]; - char *end = frac_to_str(buf, 0, 100); + char *end = frac_to_str_(buf, 0, 100); *end = '\0'; EXPECT_STREQ(buf, "000"); - end = frac_to_str(buf, 0, 1); + end = frac_to_str_(buf, 0, 1); *end = '\0'; EXPECT_STREQ(buf, "0"); } @@ -112,7 +124,7 @@ TEST(FracToStr, AllZeros) { TEST(FracToStr, ZeroDivisor) { char buf[8]; buf[0] = 'X'; - char *end = frac_to_str(buf, 0, 0); + char *end = frac_to_str_(buf, 0, 0); EXPECT_EQ(end, buf); // writes nothing }