diff --git a/sys/fmt/fmt.c b/sys/fmt/fmt.c
index 9c951be387a18f2b518f1d56f3a01de01da7e131..e75c7487a34d36c1a27879e7dc19f944a0f0ff23 100644
--- a/sys/fmt/fmt.c
+++ b/sys/fmt/fmt.c
@@ -263,48 +263,56 @@ 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, unsigned fp_digits)
+size_t fmt_s16_dfp(char *out, int16_t val, int fp_digits)
 {
     return fmt_s32_dfp(out, val, fp_digits);
 }
 
-size_t fmt_s32_dfp(char *out, int32_t val, unsigned fp_digits)
+size_t fmt_s32_dfp(char *out, int32_t val, int fp_digits)
 {
-    assert(fp_digits < TENMAP_SIZE);
+    assert(fp_digits > -(int)TENMAP_SIZE);
 
-    int32_t absolute, divider;
-    unsigned div_len, len, pos = 0;
-    char tmp[9];
+    unsigned  pos = 0;
 
     if (fp_digits == 0) {
-        return fmt_s32_dec(out, val);
+        pos = fmt_s32_dec(out, val);
     }
-    if (val < 0) {
+    else if (fp_digits > 0) {
+        pos = fmt_s32_dec(out, val);
         if (out) {
-            out[pos++] = '-';
+            memset(&out[pos], '0', fp_digits);
         }
-        val = -val;
-    }
-
-    uint32_t e = _tenmap[fp_digits];
-    absolute = (val / e);
-    divider = val - (absolute * e);
-
-    pos += fmt_s32_dec(&out[pos], absolute);
-
-    if (!out) {
-        return pos + 1 + fp_digits;     /* abs len + decimal point + divider */
+        pos += fp_digits;
     }
+    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)) {
+            if (out) {
+                out[pos] = '-';
+            }
+            pos++;
+        }
 
-    out[pos++] = '.';
-    len = pos + fp_digits;
-    div_len = fmt_s32_dec(tmp, divider);
-
-    while (pos < (len - div_len)) {
-        out[pos++] = '0';
-    }
-    for (size_t i = 0; i < div_len; i++) {
-        out[pos++] = tmp[i];
+        if (!out) {
+            /* compensate for the decimal point character... */
+            pos += fmt_s32_dec(NULL, abs) + 1;
+        }
+        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;
     }
 
     return pos;
diff --git a/sys/include/fmt.h b/sys/include/fmt.h
index 3f9e1ea00e829bac43da76c0d5dc58de09229bbb..d88608b2c9266593fbddb0236c781b725ee14d04 100644
--- a/sys/include/fmt.h
+++ b/sys/include/fmt.h
@@ -225,60 +225,43 @@ size_t fmt_s16_dec(char *out, int16_t val);
 /**
  * @brief Convert 16-bit fixed point number to a decimal string
  *
- * The input for this function is a signed 16-bit integer holding the fixed
- * point value as well as an unsigned integer defining the position of the
- * decimal point, so this value defines the number of decimal digits 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". For @p val := 12010 and @p fp_digits := 3 the result will
- * be "12.010".
- *
- * Will add a leading "-" if @p val is negative.
- *
- * If @p out is NULL, will only return the number of bytes that would have
- * been written.
- *
- * @pre fp_digits < 8 (TENMAP_SIZE)
+ * See fmt_s32_dfp() for more details
  *
  * @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
+ * @param[in]  fp_digits    Number of digits after the decimal point, MUST be
+ *                          >= -7
  *
  * @return      Length of the resulting string
  */
-size_t fmt_s16_dfp(char *out, int16_t val, unsigned fp_digits);
+size_t fmt_s16_dfp(char *out, int16_t val, int fp_digits);
 
 /**
  * @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 unsigned integer defining the position of the
- * decimal point, so this value defines the number of decimal digits after the
- * decimal point.
+ * 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).
  *
  * Will add a leading "-" if @p val is negative.
  *
  * The resulting string will always be patted with zeros after the decimal point.
  *
- * For example: if @p val is -314159 and @p fp_digits is 5, the resulting string
- * will be "-3.14159". For @p val := 16777215 and @p fp_digits := 6 the result
- * will be "16.777215".
- *
- * If @p out is NULL, will only return the number of bytes that would have
- * been written.
+ * 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)
+ * @pre fp_digits > -8 (TENMAP_SIZE)
  *
  * @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
+ * @param[in]  fp_digits    Number of digits after the decimal point, MUST be
+ *                          >= -7
  *
  * @return      Length of the resulting string
  */
-size_t fmt_s32_dfp(char *out, int32_t val, unsigned fp_digits);
+size_t fmt_s32_dfp(char *out, int32_t val, int fp_digits);
 
 /**
  * @brief Format float to string