---
title: "Python Float Formatting — Equality Trap with Percentages"
tags: [python, float, formatting, gotcha]
created: 2026-05-18
sources: daily/2026-05-18.md
---

# Python Float Formatting — Equality Trap with Percentages

## The Problem

Binary floating-point arithmetic breaks integer-equality checks on values that look whole:

```python
1.1 * 10          # → 11.000000000000002
(1.1 * 10) % 1    # → 2e-15 (not 0.0!)
(1.1 * 10) % 1 == 0  # → False  ← BUG
```

A common context where this bites: formatting percentage strings.

```python
def fmt_pct(value):
    """Format 1.1 → '1.1%', 2.0 → '2%'"""
    pct = value * 100
    if pct % 1 == 0:            # WRONG — floats lie here
        return f"{int(pct)}%"   # never reached for 1.1
    return f"{pct:.2f}%"        # outputs "1.10%" instead of "1.1%"
```

**Symptom:** Values like `1.1`, `3.3`, `6.6` produce `"1.10%"`, `"3.30%"`, `"6.60%"` instead of `"1.1%"`, `"3.3%"`, `"6.6%"`.

## Root Cause

IEEE 754 double precision cannot represent most decimal fractions exactly. Multiplying by 100 amplifies the error. The `% 1 == 0` check compares against exact zero — which the fractional residue never is.

## Fix

Format to a fixed number of decimal places first, then strip trailing zeros:

```python
def fmt_pct(value):
    pct = value * 100
    formatted = f"{pct:.10f}".rstrip("0").rstrip(".")
    return f"{formatted}%"
```

Or use `Decimal` for exact arithmetic when the source is user-entered text:

```python
from decimal import Decimal
def fmt_pct(value: str) -> str:
    pct = Decimal(value) * 100
    return f"{pct.normalize():f}%"
```

## General Rule

> Never use `float == integer` or `float % 1 == 0` for "is this whole?" checks.
> Always format to fixed dp first, then apply string operations.

## See Also

- `wiki/concepts/python-iso-z-suffix` — another float/string representation trap
