diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 30903e58ca..244d307f8f 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -534,39 +534,15 @@ static size_t value_accuracy_to_buf_fast(char *buf, float value, int8_t accuracy *p++ = '-'; value = -value; } - uint32_t mult = 1; - if (accuracy_decimals == 1) { - mult = 10; - } else if (accuracy_decimals == 2) { - mult = 100; - } else if (accuracy_decimals == 3) { - mult = 1000; - } - // Cast to double for the multiply to match snprintf's precision. + uint32_t mult = small_pow10(accuracy_decimals); + // Cast to double for the multiply to match snprintf's rounding precision. // 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)); - uint32_t int_part = scaled / mult; - // Write integer part in reverse, then flip - char *start = p; - if (int_part == 0) { - *p++ = '0'; - } else { - while (int_part > 0) { - *p++ = '0' + (int_part % 10); - int_part /= 10; - } - std::reverse(start, p); - } + p = uint32_to_str(p, scaled / mult); if (accuracy_decimals > 0) { *p++ = '.'; - uint32_t frac = scaled % mult; - uint32_t d = mult / 10; - while (d > 0) { - *p++ = '0' + static_cast(frac / d); - frac %= d; - d /= 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 c26bbe17b7..ee6f76a09d 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1295,6 +1295,46 @@ inline char *int8_to_str(char *buf, int8_t val) { return buf; } +/// 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; +} + +/// 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) { + if (val == 0) { + *buf++ = '0'; + return buf; + } + char *start = buf; + while (val > 0) { + *buf++ = '0' + (val % 10); + val /= 10; + } + std::reverse(start, buf); + return buf; +} + +/// Write fractional digits with leading zeros to buffer. +/// 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) { + while (divisor > 0) { + *buf++ = '0' + static_cast(frac / divisor); + frac %= divisor; + divisor /= 10; + } + return buf; +} + /// Format byte array as lowercase hex to buffer (base implementation). char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length);