When you print numbers in a Python program, the raw output often isn't what you want to show to users. Python number format tools give you precise control over how numbers appear — from rounding floats to two decimal places to adding commas as thousands separators. Whether you're building a finance dashboard, a CLI report, or a data summary tool, knowing how to format numbers in Python makes your output readable and professional.
In this tutorial you'll learn every practical technique for python number formatting using f-strings, the built-in format() function, and format specifiers. Each method is explained with working examples and real output so you can follow along in any Python 3 environment.
The foundation of python number format is the format specifier — a mini-language built into Python that tells it exactly how to render a number. You'll encounter it inside f-strings and format() calls, placed after a colon inside curly braces.
The general structure looks like this:
{value:[fill][align][sign][#][0][width][grouping][.precision][type]}
That looks intimidating at first, but in practice you only ever use a few of these pieces at a time. Here's what each key part means:
f for fixed-point float, d for decimal integer, e for scientific notation, g for general format, % for percentage, or underscore _ to visually separate every three digitsUnderstanding this python format specifier is the unlock for every technique in this guide. Once you've seen the pattern once, every format string becomes readable.
F-strings (formatted string literals) arrived in Python 3.6 and quickly became the most popular way to handle python f-string number format. You write them by adding an f prefix before the opening quote, then put your variable name followed by a colon and your format specifier inside the curly braces.
Here's the most common use case — displaying a price with exactly two decimal places:
price = 19.5
print(f"Price: {price:.2f}")
Price: 19.50
The :.2f is the format specifier. The .2 means two digits after the decimal, and f means fixed-point float notation. Python takes 19.5 and renders it as 19.50, adding the trailing zero so the output is always consistent.
You can also control the total width of the field, which is essential for aligning columns in a table. The number before the dot sets the minimum character width:
items = [("Apple", 1.5), ("Banana", 0.75), ("Mango", 3.125)]
for name, cost in items:
print(f"{name:<10} ${cost:8.2f}")
Apple $ 1.50
Banana $ 0.75
Mango $ 3.12
The :<10 left-aligns the item name inside a 10-character field. The :8.2f right-aligns the cost inside an 8-character field with 2 decimal places. This is exactly how you'd format a receipt or invoice in a terminal application — clean, aligned, and consistent regardless of string length.
Controlling decimal places is one of the most frequent python number format tasks you'll encounter — especially for currency, measurements, sensor data, or any user-facing statistics. The .precision portion of the format specifier handles this directly, and you can use it with any numeric value.
Here's a comparison of 1 through 4 decimal places on the same number:
value = 3.14159265358979
print(f"1 decimal: {value:.1f}")
print(f"2 decimals: {value:.2f}")
print(f"3 decimals: {value:.3f}")
print(f"4 decimals: {value:.4f}")
1 decimal: 3.1
2 decimals: 3.14
3 decimals: 3.142
4 decimals: 3.1416
Python rounds correctly at every level — 3.14159 rounded to 4 places gives 3.1416, not 3.1415. Standard rounding rules apply.
There's also the g type specifier, which uses a "general" format that automatically trims trailing zeros. This makes python format float output feel natural when displaying varying values:
a = 3.10000
b = 3.14159
print(f"{a:.4g}")
print(f"{b:.4g}")
3.1
3.142
The g type is useful for scientific measurements or any display where you don't want unnecessary trailing zeros cluttering the output. Python decides whether to use fixed-point or exponential notation based on which is shorter for the given precision.
When you're working with large numbers — population figures, financial totals, file sizes — adding a thousands separator makes them dramatically easier to scan. Python number format supports this natively with a comma inside the format specifier, and you don't need any extra imports or functions.
Adding comma grouping is as simple as including , between the width and precision in your specifier:
population = 8045311447
salary = 125000.75
print(f"World population: {population:,}")
print(f"Annual salary: ${salary:,.2f}")
World population: 8,045,311,447
Annual salary: $125,000.75
Python also supports the underscore _ as an alternative grouping character. This is particularly useful when displaying large binary or hexadecimal values where commas would look out of place:
big_number = 1000000000
hex_value = 16777215
print(f"With underscores: {big_number:_}")
print(f"Hex with underscores: {hex_value:_x}")
With underscores: 1_000_000_000
Hex with underscores: ff_ffff
The _x type formats the integer as lowercase hexadecimal and adds underscore grouping every four hex digits. This is handy when working with color values, memory addresses, or any low-level numeric display.
Python format integer covers more than just printing a number — you can control zero-padding, sign display, and the numeric base: decimal, binary, octal, and hexadecimal. The type letters for integers are d (decimal), b (binary), o (octal), and x / X (hexadecimal lowercase and uppercase).
Zero-padding is a common need when generating sequential file names, IDs, or any output that needs consistent character width:
for i in range(1, 6):
print(f"File: report_{i:04d}.csv")
File: report_0001.csv
File: report_0002.csv
File: report_0003.csv
File: report_0004.csv
File: report_0005.csv
The 04d means: format as a decimal integer, padded with zeros on the left to a minimum width of 4. This is cleaner and more readable than calling str(i).zfill(4).
For displaying the same number in multiple bases — which often comes up in systems programming, networking, and low-level debugging:
n = 255
print(f"Decimal: {n:d}")
print(f"Binary: {n:b}")
print(f"Octal: {n:o}")
print(f"Hex lower: {n:x}")
print(f"Hex upper: {n:X}")
print(f"Hex prefix: {n:#x}")
Decimal: 255
Binary: 11111111
Octal: 377
Hex lower: ff
Hex upper: FF
Hex prefix: 0xff
The # flag adds the 0b, 0o, or 0x prefix automatically, saving you from concatenating those strings manually. This is the cleanest way to render base-prefixed numbers in Python.
When you're dealing with very large or very small numbers — distances in astronomy, probabilities in statistics, physical constants, or machine learning loss values — python scientific notation format keeps things legible. The e type formats a number using standard scientific notation.
light_speed = 299792458 # meters per second
electron_mass = 9.10938e-31 # kilograms
print(f"Speed of light: {light_speed:.3e}")
print(f"Electron mass: {electron_mass:.4e}")
Speed of light: 2.998e+08
Electron mass: 9.1094e-31
The .3e format means three digits after the decimal point in scientific notation. Using uppercase E produces an uppercase exponent marker — a common requirement in scientific publishing:
avogadro = 6.02214076e23
print(f"Avogadro's number: {avogadro:.5E}")
Avogadro's number: 6.02214E+23
Python also handles percentage formatting through the % type, which multiplies the value by 100 and appends the percent sign automatically. This is one of the more elegant features of python number format:
accuracy = 0.9742
discount = 0.15
print(f"Model accuracy: {accuracy:.1%}")
print(f"Discount rate: {discount:.0%}")
Model accuracy: 97.4%
Discount rate: 15%
No manual multiplication, no manual string concatenation — Python handles both in a single format specifier.
Before f-strings became standard, the built-in format() function and the .format() string method were the primary way to handle python format number tasks. These still appear frequently in existing codebases, libraries, and any situation where the format string must be stored in a variable.
The standalone format() function takes a value and a format specifier string and returns the formatted result:
pi = 3.14159
print(format(pi, ".2f"))
print(format(1234567, ","))
print(format(0.0045, ".2e"))
3.14
1,234,567
4.50e-03
The format specifier syntax is identical to f-strings — only the way you write the call is different. The .format() method on strings lets you embed and format multiple values at once using named placeholders:
template = "Name: {name:<15} Score: {score:06.2f} Rank: {rank:>4}"
print(template.format(name="Alice", score=98.5, rank=1))
print(template.format(name="Bob", score=87.333, rank=12))
print(template.format(name="Charlie", score=72.1, rank=100))
Name: Alice Score: 098.50 Rank: 1
Name: Bob Score: 087.33 Rank: 12
Name: Charlie Score: 072.10 Rank: 100
This approach is still valuable when format strings come from configuration files or database templates, since f-strings can only be written as literals in source code. You can find the complete reference for every available option in the Python format specification mini-language documentation.
This example combines every python number format technique from this guide into a single financial report generator — decimal places, thousands separators, scientific notation, integer zero-padding, and percentage formatting all working together.
from datetime import date
def generate_report(records):
today = date.today().strftime("%B %d, %Y")
width = 62
print(f"{'QUARTERLY FINANCIAL REPORT':^{width}}")
print(f"{'Report Date: ' + today:^{width}}")
print("-" * width)
col_header = f"{'Division':<26} {'Revenue':>14} {'Growth':>9} {'Share':>8}"
print(f"\n{col_header}")
print("-" * width)
total = sum(r["revenue"] for r in records)
for r in records:
share = r["revenue"] / total
sign = "+" if r["growth"] >= 0 else ""
print(
f"{r['name']:<26}"
f" ${r['revenue']:>12,.2f}"
f" {sign}{r['growth']:>5.1f}%"
f" {share:>5.1%}"
)
print("-" * width)
print(f"{'TOTAL':<26} ${total:>12,.2f}")
largest = max(r["revenue"] for r in records)
smallest = min(r["revenue"] for r in records)
print(f"\nLargest division revenue: {largest:.4e}")
print(f"Smallest division revenue: {smallest:.4e}")
print(f"Revenue ratio (L/S): {largest / smallest:.2f}x")
print(f"\nDivisions on record: {len(records):04d}")
print(f"Average revenue: ${total / len(records):,.2f}")
quarterly_data = [
{"name": "North America", "revenue": 5_241_830.75, "growth": 14.2},
{"name": "Europe", "revenue": 2_893_412.50, "growth": -2.8},
{"name": "Asia Pacific", "revenue": 4_107_655.00, "growth": 21.3},
{"name": "Latin America", "revenue": 1_032_980.25, "growth": 6.7},
{"name": "Middle East", "revenue": 748_334.60, "growth": 3.1},
{"name": "Africa", "revenue": 415_221.80, "growth": -0.5},
]
generate_report(quarterly_data)
Output
QUARTERLY FINANCIAL REPORT
Report Date: May 15, 2026
--------------------------------------------------------------
Division Revenue Growth Share
--------------------------------------------------------------
North America $5,241,830.75 +14.2% 36.8%
Europe $2,893,412.50 -2.8% 20.3%
Asia Pacific $4,107,655.00 +21.3% 28.8%
Latin America $1,032,980.25 +6.7% 7.2%
Middle East $748,334.60 +3.1% 5.3%
Africa $415,221.80 -0.5% 2.9%
--------------------------------------------------------------
TOTAL $14,439,434.90
Largest division revenue: 5.2418e+06
Smallest division revenue: 4.1522e+05
Revenue ratio (L/S): 12.62x
Divisions on record: 0006
Average revenue: $2,406,572.48