diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index 4c7dbbc65a6d4c95a99c4eb8a3b825cedd36cc77_Q3l0aG9uL0NvbXBpbGVyL0V4cHJOb2Rlcy5weQ==..0f6af780d201b1cef4e1ad0697a8bb75777e7914_Q3l0aG9uL0NvbXBpbGVyL0V4cHJOb2Rlcy5weQ== 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -3293,7 +3293,7 @@
     # {}-delimited portions of an f-string
     #
     # value           ExprNode                The expression itself
-    # conversion_char str or None             Type conversion (!s, !r, !a, or none)
+    # conversion_char str or None             Type conversion (!s, !r, !a, or none, or 'd' for integer conversion)
     # format_spec     JoinedStrNode or None   Format string passed to __format__
     # c_format_spec   str or None             If not None, formatting can be done at the C level
 
@@ -3308,6 +3308,7 @@
         's': 'PyObject_Unicode',
         'r': 'PyObject_Repr',
         'a': 'PyObject_ASCII',  # NOTE: mapped to PyObject_Repr() in Py2
+        'd': '__Pyx_PyNumber_IntOrLong',  # NOTE: internal mapping for '%d' formatting
     }.get
 
     def may_be_none(self):
diff --git a/Cython/Compiler/Optimize.py b/Cython/Compiler/Optimize.py
index 4c7dbbc65a6d4c95a99c4eb8a3b825cedd36cc77_Q3l0aG9uL0NvbXBpbGVyL09wdGltaXplLnB5..0f6af780d201b1cef4e1ad0697a8bb75777e7914_Q3l0aG9uL0NvbXBpbGVyL09wdGltaXplLnB5 100644
--- a/Cython/Compiler/Optimize.py
+++ b/Cython/Compiler/Optimize.py
@@ -4382,8 +4382,9 @@
                 break
             if format_type in u'asrfdoxX':
                 format_spec = s[1:]
+                conversion_char = None
                 if format_type in u'doxX' and u'.' in format_spec:
                     # Precision is not allowed for integers in format(), but ok in %-formatting.
                     can_be_optimised = False
                 elif format_type in u'ars':
                     format_spec = format_spec[:-1]
@@ -4385,7 +4386,11 @@
                 if format_type in u'doxX' and u'.' in format_spec:
                     # Precision is not allowed for integers in format(), but ok in %-formatting.
                     can_be_optimised = False
                 elif format_type in u'ars':
                     format_spec = format_spec[:-1]
+                    conversion_char = format_type
+                elif format_type == u'd':
+                    # '%d' formatting supports float, but '{obj:d}' does not => convert to int first.
+                    conversion_char = 'd'
                 substrings.append(ExprNodes.FormattedValueNode(
                     arg.pos, value=arg,
@@ -4390,6 +4395,6 @@
                 substrings.append(ExprNodes.FormattedValueNode(
                     arg.pos, value=arg,
-                    conversion_char=format_type if format_type in u'ars' else None,
+                    conversion_char=conversion_char,
                     format_spec=ExprNodes.UnicodeNode(
                         pos, value=EncodedString(format_spec), constant_result=format_spec)
                         if format_spec else None,
diff --git a/tests/run/fstring.pyx b/tests/run/fstring.pyx
index 4c7dbbc65a6d4c95a99c4eb8a3b825cedd36cc77_dGVzdHMvcnVuL2ZzdHJpbmcucHl4..0f6af780d201b1cef4e1ad0697a8bb75777e7914_dGVzdHMvcnVuL2ZzdHJpbmcucHl4 100644
--- a/tests/run/fstring.pyx
+++ b/tests/run/fstring.pyx
@@ -533,5 +533,5 @@
     "//FormattedValueNode",
     "//JoinedStrNode",
 )
-def generated_fstring(int i, unicode u not None, o):
+def generated_fstring(int i, float f, unicode u not None, o):
     """
@@ -537,5 +537,5 @@
     """
-    >>> i, u, o = 11, u'xyz', [1]
+    >>> i, f, u, o = 11, 1.3125, u'xyz', [1]
     >>> print(((
     ...     u"(i) %s-%.3s-%r-%.3r-%d-%3d-%o-%04o-%x-%4x-%X-%03X-%.1f-%04.2f %% "
     ...     u"(u) %s-%.2s-%r-%.7r %% "
@@ -539,8 +539,9 @@
     >>> print(((
     ...     u"(i) %s-%.3s-%r-%.3r-%d-%3d-%o-%04o-%x-%4x-%X-%03X-%.1f-%04.2f %% "
     ...     u"(u) %s-%.2s-%r-%.7r %% "
-    ...     u"(o) %s-%.2s-%r-%.2r"
+    ...     u"(o) %s-%.2s-%r-%.2r %% "
+    ...     u"(f) %.2f-%d"
     ... ) % (
     ...     i, i, i, i, i, i, i, i, i, i, i, i, i, i,
     ...     u, u, u, u,
     ...     o, o, o, o,
@@ -543,5 +544,6 @@
     ... ) % (
     ...     i, i, i, i, i, i, i, i, i, i, i, i, i, i,
     ...     u, u, u, u,
     ...     o, o, o, o,
+    ...     f, f,
     ... )).replace("-u'xyz'", "-'xyz'"))
@@ -547,3 +549,3 @@
     ... )).replace("-u'xyz'", "-'xyz'"))
-    (i) 11-11-11-11-11- 11-13-0013-b-   b-B-00B-11.0-11.00 % (u) xyz-xy-'xyz'-'xyz' % (o) [1]-[1-[1]-[1
+    (i) 11-11-11-11-11- 11-13-0013-b-   b-B-00B-11.0-11.00 % (u) xyz-xy-'xyz'-'xyz' % (o) [1]-[1-[1]-[1 % (f) 1.31-1
 
@@ -549,7 +551,7 @@
 
-    >>> print(generated_fstring(i, u, o).replace("-u'xyz'", "-'xyz'"))
-    (i) 11-11-11-11-11- 11-13-0013-b-   b-B-00B-11.0-11.00 % (u) xyz-xy-'xyz'-'xyz' % (o) [1]-[1-[1]-[1
+    >>> print(generated_fstring(i, f, u, o).replace("-u'xyz'", "-'xyz'"))
+    (i) 11-11-11-11-11- 11-13-0013-b-   b-B-00B-11.0-11.00 % (u) xyz-xy-'xyz'-'xyz' % (o) [1]-[1-[1]-[1 % (f) 1.31-1
     """
     return (
         u"(i) %s-%.3s-%r-%.3r-%d-%3d-%o-%04o-%x-%4x-%X-%03X-%.1f-%04.2f %% "
         u"(u) %s-%.2s-%r-%.7r %% "
@@ -552,9 +554,10 @@
     """
     return (
         u"(i) %s-%.3s-%r-%.3r-%d-%3d-%o-%04o-%x-%4x-%X-%03X-%.1f-%04.2f %% "
         u"(u) %s-%.2s-%r-%.7r %% "
-        u"(o) %s-%.2s-%r-%.2r"
+        u"(o) %s-%.2s-%r-%.2r %% "
+        u"(f) %.2f-%d"
     ) % (
         i, i, i, i, i, i, i, i, i, i, i, i, i, i,
         u, u, u, u,
         o, o, o, o,
@@ -557,7 +560,8 @@
     ) % (
         i, i, i, i, i, i, i, i, i, i, i, i, i, i,
         u, u, u, u,
         o, o, o, o,
+        f, f,
     )