diff --git a/py/objstr.c b/py/objstr.c
index 593c39cb0953be758493382f604b6a5af677d8af..4e70b00812b72b263427df5334a014fb9e027691 100644
--- a/py/objstr.c
+++ b/py/objstr.c
@@ -981,7 +981,7 @@ mp_obj_t mp_obj_str_format(uint n_args, const mp_obj_t *args) {
         if (arg_looks_integer(arg)) {
             switch (type) {
                 case 'b':
-                    pfenv_print_mp_int(&pfenv_vstr, arg, 1, 2, 'a', flags, fill, width);
+                    pfenv_print_mp_int(&pfenv_vstr, arg, 1, 2, 'a', flags, fill, width, 0);
                     continue;
 
                 case 'c':
@@ -994,7 +994,7 @@ mp_obj_t mp_obj_str_format(uint n_args, const mp_obj_t *args) {
                 case '\0':  // No explicit format type implies 'd'
                 case 'n':   // I don't think we support locales in uPy so use 'd'
                 case 'd':
-                    pfenv_print_mp_int(&pfenv_vstr, arg, 1, 10, 'a', flags, fill, width);
+                    pfenv_print_mp_int(&pfenv_vstr, arg, 1, 10, 'a', flags, fill, width, 0);
                     continue;
 
                 case 'o':
@@ -1002,12 +1002,12 @@ mp_obj_t mp_obj_str_format(uint n_args, const mp_obj_t *args) {
                         flags |= PF_FLAG_SHOW_OCTAL_LETTER;
                     }
 
-                    pfenv_print_mp_int(&pfenv_vstr, arg, 1, 8, 'a', flags, fill, width);
+                    pfenv_print_mp_int(&pfenv_vstr, arg, 1, 8, 'a', flags, fill, width, 0);
                     continue;
 
                 case 'X':
                 case 'x':
-                    pfenv_print_mp_int(&pfenv_vstr, arg, 1, 16, type - ('X' - 'A'), flags, fill, width);
+                    pfenv_print_mp_int(&pfenv_vstr, arg, 1, 16, type - ('X' - 'A'), flags, fill, width, 0);
                     continue;
 
                 case 'e':
@@ -1255,7 +1255,7 @@ not_enough_args:
             case 'd':
             case 'i':
             case 'u':
-                pfenv_print_mp_int(&pfenv_vstr, arg_as_int(arg), 1, 10, 'a', flags, fill, width);
+                pfenv_print_mp_int(&pfenv_vstr, arg_as_int(arg), 1, 10, 'a', flags, fill, width, prec);
                 break;
 
 #if MICROPY_PY_BUILTINS_FLOAT
@@ -1273,7 +1273,7 @@ not_enough_args:
                 if (alt) {
                     flags |= (PF_FLAG_SHOW_PREFIX | PF_FLAG_SHOW_OCTAL_LETTER);
                 }
-                pfenv_print_mp_int(&pfenv_vstr, arg, 1, 8, 'a', flags, fill, width);
+                pfenv_print_mp_int(&pfenv_vstr, arg, 1, 8, 'a', flags, fill, width, prec);
                 break;
 
             case 'r':
@@ -1296,7 +1296,7 @@ not_enough_args:
 
             case 'X':
             case 'x':
-                pfenv_print_mp_int(&pfenv_vstr, arg, 1, 16, *str - ('X' - 'A'), flags | alt, fill, width);
+                pfenv_print_mp_int(&pfenv_vstr, arg, 1, 16, *str - ('X' - 'A'), flags | alt, fill, width, prec);
                 break;
 
             default:
diff --git a/py/pfenv.c b/py/pfenv.c
index e4db4da0460b5098616e4fc213e1483ce62d1dd8..e631f8654a30a728201d91c9bb03bd57fee54ebd 100644
--- a/py/pfenv.c
+++ b/py/pfenv.c
@@ -91,8 +91,10 @@ int pfenv_print_strn(const pfenv_t *pfenv, const char *str, unsigned int len, in
             left_pad -= p;
         }
     }
-    pfenv->print_strn(pfenv->data, str, len);
-    total_chars_printed += len;
+    if (len) {
+        pfenv->print_strn(pfenv->data, str, len);
+        total_chars_printed += len;
+    }
     if (right_pad > 0) {
         total_chars_printed += right_pad;
         while (right_pad > 0) {
@@ -181,13 +183,19 @@ int pfenv_print_int(const pfenv_t *pfenv, machine_uint_t x, int sgn, int base, i
     return len;
 }
 
-int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int sgn, int base, int base_char, int flags, char fill, int width) {
+int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int sgn, int base, int base_char, int flags, char fill, int width, int prec) {
     if (!MP_OBJ_IS_INT(x)) {
         // This will convert booleans to int, or raise an error for
         // non-integer types.
         x = MP_OBJ_NEW_SMALL_INT(mp_obj_get_int(x));
     }
 
+    if ((flags & (PF_FLAG_LEFT_ADJUST | PF_FLAG_CENTER_ADJUST)) == 0 && fill == '0') {
+        if (prec > width) {
+            width = prec;
+        }
+        prec = 0;
+    }
     char prefix_buf[4];
     char *prefix = prefix_buf;
 
@@ -230,6 +238,9 @@ int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int sgn, int base, int
     int fmt_size = 0;
     char *str;
 
+    if (prec > 1) {
+        flags |= PF_FLAG_PAD_AFTER_SIGN;
+    }
     char sign = '\0';
     if (flags & PF_FLAG_PAD_AFTER_SIGN) {
         // We add the pad in this function, so since the pad goes after
@@ -245,7 +256,39 @@ int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int sgn, int base, int
                                    x, base, prefix, base_char, comma);
     }
 
+    int spaces_before = 0;
+    int spaces_after = 0;
+
+    if (prec > 1) {
+        // If prec was specified, then prec specifies the width to zero-pad the
+        // the number to. This zero-padded number then gets left or right
+        // aligned in width characters.
+
+        int prec_width = fmt_size;  // The digits
+        if (prec_width < prec) {
+            prec_width = prec;
+        }
+        if (flags & PF_FLAG_PAD_AFTER_SIGN) {
+            if (sign) {
+                prec_width++;
+            }
+            prec_width += prefix_len;
+        }
+        if (prec_width < width) {
+            if (flags & PF_FLAG_LEFT_ADJUST) {
+                spaces_after = width - prec_width;
+            } else {
+                spaces_before = width - prec_width;
+            }
+        }
+        fill = '0';
+        flags &= ~PF_FLAG_LEFT_ADJUST;
+    }
+
     int len = 0;
+    if (spaces_before) {
+        len += pfenv_print_strn(pfenv, "", 0, 0, ' ', spaces_before);
+    }
     if (flags & PF_FLAG_PAD_AFTER_SIGN) {
         // pad after sign implies pad after prefix as well.
         if (sign) {
@@ -257,9 +300,16 @@ int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int sgn, int base, int
             width -= prefix_len;
         }
     }
+    if (prec > 1) {
+        width = prec;
+    }
 
     len += pfenv_print_strn(pfenv, str, fmt_size, flags, fill, width);
 
+    if (spaces_after) {
+        len += pfenv_print_strn(pfenv, "", 0, 0, ' ', spaces_after);
+    }
+
     if (buf != stack_buf) {
         m_free(buf, buf_size);
     }
diff --git a/py/pfenv.h b/py/pfenv.h
index 2a3de6c32ddccf6e28377b7899c34384257a89d6..55eca6fed1887e59320222ca3f0b7eb012720d57 100644
--- a/py/pfenv.h
+++ b/py/pfenv.h
@@ -45,7 +45,7 @@ void pfenv_vstr_add_strn(void *data, const char *str, unsigned int len);
 
 int pfenv_print_strn(const pfenv_t *pfenv, const char *str, unsigned int len, int flags, char fill, int width);
 int pfenv_print_int(const pfenv_t *pfenv, machine_uint_t x, int sgn, int base, int base_char, int flags, char fill, int width);
-int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int sgn, int base, int base_char, int flags, char fill, int width);
+int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int sgn, int base, int base_char, int flags, char fill, int width, int prec);
 #if MICROPY_PY_BUILTINS_FLOAT
 int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, char fill, int width, int prec);
 #endif
diff --git a/tests/basics/string-format-modulo.py b/tests/basics/string-format-modulo.py
index c8fdc06f68e9fe57ea93e46c16d7c59399a51f72..f3f57b45adacc19fe536c8eca09990adf8d300ac 100644
--- a/tests/basics/string-format-modulo.py
+++ b/tests/basics/string-format-modulo.py
@@ -51,8 +51,18 @@ print("%#06x" % 18)
 
 print("%*d" % (5, 10))
 print("%*.*d" % (2, 2, 20))
-# TODO: Formatted incorrectly
-#print("%*.*d" % (5, 8, 20))
+print("%*.*d" % (5, 8, 20))
+
+print(">%8.4d<" % -12)
+print(">% 8.4d<" % -12)
+print(">%+8.4d<" %  12)
+print(">%+8.4d<" % -12)
+print(">%08.4d<" % -12)
+print(">%08.4d<" % 12)
+print(">%-8.4d<" % -12)
+print(">%-08.4d<" % -12)
+print(">%-+08.4d<" % -12)
+print(">%-+08.4d<" %  12)
 
 # Cases when "*" used and there's not enough values total
 try: