diff --git a/sys/fmt/fmt.c b/sys/fmt/fmt.c index ae47fbf387..1d6a20a8d5 100644 --- a/sys/fmt/fmt.c +++ b/sys/fmt/fmt.c @@ -308,61 +308,62 @@ size_t fmt_s16_dec(char *out, int16_t val) return fmt_s32_dec(out, val); } -size_t fmt_s16_dfp(char *out, int16_t val, int fp_digits) +size_t fmt_s16_dfp(char *out, int16_t val, int scale) { - return fmt_s32_dfp(out, val, fp_digits); + return fmt_s32_dfp(out, val, scale); } -size_t fmt_s32_dfp(char *out, int32_t val, int fp_digits) +size_t fmt_s32_dfp(char *out, int32_t val, int scale) { - assert(fp_digits > -(int)TENMAP_SIZE); + unsigned pos = 0; - unsigned pos = 0; - - if (fp_digits == 0) { + if (scale == 0) { pos = fmt_s32_dec(out, val); } - else if (fp_digits > 0) { + else if (scale > 0) { pos = fmt_s32_dec(out, val); if (out) { - memset(&out[pos], '0', fp_digits); + memset(&out[pos], '0', scale); } - pos += fp_digits; + pos += scale; } else { - fp_digits *= -1; - uint32_t e = _tenmap[fp_digits]; - int32_t abs = (val / (int32_t)e); - int32_t div = val - (abs * e); - - /* the divisor should never be negative */ - if (div < 0) { - div *= -1; - } - /* handle special case for negative number with zero as absolute value */ - if ((abs == 0) && (val < 0)) { + scale = -scale; + char buf[10]; /* "2147483648" */ + int negative = val < 0; + uint32_t uval = negative ? -val : val; + int len = fmt_u32_dec(buf, uval); + if (negative) { if (out) { out[pos] = '-'; } pos++; } + if (len <= scale) { + int zeroes = scale - len + 1; + if (out) { + memset(&out[pos], '0', zeroes); + } + pos += zeroes; + } + if (out) { + memcpy(&out[pos], buf, len); + } - if (!out) { - /* compensate for the decimal point character... */ - pos += fmt_s32_dec(NULL, abs) + 1; + pos += len; + + if (out) { + unsigned dot_pos = pos - scale; + for (unsigned i = pos; i >= dot_pos; i--) { + out[i] = out[i - 1]; + } + out[dot_pos] = '.'; } - else { - pos += fmt_s32_dec(&out[pos], abs); - out[pos++] = '.'; - unsigned div_len = fmt_s32_dec(&out[pos], div); - fmt_lpad(&out[pos], div_len, (size_t)fp_digits, '0'); - } - pos += fp_digits; + pos += 1; } return pos; } - /* this is very probably not the most efficient implementation, as it at least * pulls in floating point math. But it works, and it's always nice to have * low hanging fruits when optimizing. (Kaspar) diff --git a/sys/include/fmt.h b/sys/include/fmt.h index 816791cf92..7fca37399f 100644 --- a/sys/include/fmt.h +++ b/sys/include/fmt.h @@ -18,7 +18,14 @@ * integers, even when the C library was built without support for 64 bit * formatting (newlib-nano). * - * \note The print functions in this library do not buffer any output. + * @note The fmt functions expect their `out` parameter to hold the entire output. + * This *MUST* be ensured by the caller. + * + * @note Each fmt function will not write anything to `out` if it is `NULL`, but + * still return the number of bytes that would have been written. + * This can be used to ensure the `out` buffer is large enough. + * + * @note The print functions in this library do not buffer any output. * Mixing calls to standard @c printf from stdio.h with the @c print_xxx * functions in fmt, especially on the same output line, may cause garbled * output. @@ -287,39 +294,30 @@ size_t fmt_s16_dec(char *out, int16_t val); * * @param[out] out Pointer to the output buffer, or NULL * @param[in] val Fixed point value - * @param[in] fp_digits Number of digits after the decimal point, MUST be - * >= -7 + * @param[in] scale Scale value * * @return Length of the resulting string */ -size_t fmt_s16_dfp(char *out, int16_t val, int fp_digits); +size_t fmt_s16_dfp(char *out, int16_t val, int scale); /** * @brief Convert 32-bit fixed point number to a decimal string * - * The input for this function is a signed 32-bit integer holding the fixed - * point value as well as an integer defining the position of the decimal point. - * This value is used to shift the decimal point to the right (positive value - * of @p fp_digits) or to the left (negative value of @p fp_digits). + * This multiplies a 32bit signed number by 10^(scale) before formatting. * - * Will add a leading "-" if @p val is negative. + * The resulting string will always be padded with zeros after the decimal point. * - * The resulting string will always be patted with zeros after the decimal point. - * - * For example: if @p val is -3548 and @p fp_digits is -2, the resulting string - * will be "-35.48". The same value for @p val with @p fp_digits of 2 will - * result in "-354800". - * - * @pre fp_digits > -8 (TENMAP_SIZE) + * For example: if @p val is -35648 and @p scale is -2, the resulting + * string will be "-352.48"( -35648*10^-2). The same value for @p val with + * @p scale of 2 will result in "-3524800" (-35648*10^2). * * @param[out] out Pointer to the output buffer, or NULL * @param[in] val Fixed point value - * @param[in] fp_digits Number of digits after the decimal point, MUST be - * >= -7 + * @param[in] scale Scale value * * @return Length of the resulting string */ -size_t fmt_s32_dfp(char *out, int32_t val, int fp_digits); +size_t fmt_s32_dfp(char *out, int32_t val, int scale); /** * @brief Format float to string diff --git a/tests/unittests/tests-fmt/tests-fmt.c b/tests/unittests/tests-fmt/tests-fmt.c index 75a647b8c7..a11a48e254 100644 --- a/tests/unittests/tests-fmt/tests-fmt.c +++ b/tests/unittests/tests-fmt/tests-fmt.c @@ -738,6 +738,15 @@ static void test_fmt_s32_dfp(void) out[act_len] = '\0'; TEST_ASSERT_EQUAL_STRING("-0.0000001", (char *)out); + val = -1; + fpp = -24; + len = fmt_s32_dfp(NULL, val, fpp); + TEST_ASSERT_EQUAL_INT(3 - fpp, len); + act_len = fmt_s32_dfp(out, val, fpp); + TEST_ASSERT_EQUAL_INT(3 - fpp, act_len); + out[act_len] = '\0'; + TEST_ASSERT_EQUAL_STRING("-0.000000000000000000000001", (char *)out); + /* check that the buffer was not overflowed */ TEST_ASSERT_EQUAL_STRING("z", &out[28]); }