Short Answer
Python is a first-class tool for scientific computing, but its default numeric types can surprise anyone who needs standards-compliant significant figures. The question is not merely which function rounds, but which one follows ASTM E29, the standard practice for using significant digits in test data. The short answer: Python’s round(), format(), and Decimal all use round-half-to-even for exact ties, which aligns with ASTM E29. However, binary floating-point representation means round() and format() on floats can fail decimal ties. For ASTM-compliant decimal rounding, use Decimal.quantize() with an explicit rounding mode.
Rule Statement: What ASTM E29 Actually Requires
ASTM E29-13, Standard Practice for Using Significant Digits in Test Data to Determine Conformance with Specifications, specifies a rounding method in Section 6.1. When the digit immediately beyond the last place to be retained is less than 5, retain unchanged. When it is greater than 5, increase by one. When it is exactly 5, round to the nearest even digit. This is the banker’s rounding rule, also called round-half-even.
ASTM E29 Section 6.1: If the digit next beyond the last place to be retained is exactly 5, round to the nearest even digit.
Python’s round() for floats and Decimal in its default context both use round-half-even. The format() function for floats also resolves exact ties to even. The catch is that most decimal fractions are not exact in binary, so a value like 2.675 is stored as something slightly less than 2.675. That makes round(2.675, 2) return 2.67, not 2.68. Decimal constructed from a string avoids this trap.
Standards Citation
- ASTM E29-13, Section 6.1 — Rounding off to significant digits; ties to even.
- ISO 80000-1:2009, Quantities and units — Part 1: General, Clause 6.4.3 — Recommends rounding to nearest, ties to even.
- NIST SP 811, Guide for the Use of the International System of Units (SI), Section 7.4 — Discusses rounding and significant figures.
- JCGM 100:2008 (GUM), Clause 7.2.6 — Reporting uncertainty and rounding measurement results.
- Python 3 Documentation —
decimal,round(), and format specification mini-language.
Software Behavior Note: round(), format(), and Decimal
round()
round(number, ndigits) returns a numeric value. For exact ties, Python 3 uses round-half-even. For example, round(2.5) returns 2 and round(3.5) returns 4. But round() on a binary float is limited by the float’s actual stored value. It rounds to decimal places, not significant figures.
format()
format(value, '.2f') returns a string for display. It uses correctly rounded decimal conversion and resolves exact ties to even. For example, format(2.5, '.0f') returns '2'. The '.3g' format rounds to three significant figures, but may switch to scientific notation. format() does not change the original value.
Decimal
Decimal provides exact decimal arithmetic. Its default context uses ROUND_HALF_EVEN, matching ASTM E29. The most explicit method is Decimal('2.5').quantize(Decimal('1'), rounding=ROUND_HALF_EVEN), which returns Decimal('2'). For significant figures, compute the correct exponent and quantize.
Convention Comparison Table
| Convention | Rule for exact tie | ASTM E29 alignment | Python implementation |
|---|---|---|---|
| Round half up | Always round away from zero | No | Not default in Python |
| Round half even | Round to nearest even digit | Yes | round(), format(), Decimal default |
| Round half down | Always round toward zero | No | Available in Decimal as ROUND_HALF_DOWN |
| Truncation | Drop extra digits | No | math.trunc(), Decimal quantize with ROUND_DOWN |
Worked Examples
Example 1: Decimal tie 2.5
Round 2.5 to an integer under ASTM E29. The digit to drop is 5, so round to the nearest even digit. The preceding digit is 2, which is even, so retain 2. Python: round(2.5) → 2. Decimal('2.5').quantize(Decimal('1'), rounding=ROUND_HALF_EVEN) → Decimal('2'). This matches ASTM E29.
Example 2: Binary float trap 2.675
Round 2.675 to two decimal places. ASTM E29 would examine the third decimal, a 5, and round to even: 2.68. But round(2.675, 2) returns 2.67 because the binary float is slightly less than 2.675. Decimal('2.675').quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN) returns Decimal('2.68'). Use Decimal when decimal exactness matters.
Example 3: Significant figures 12345 to 3 sig figs
To round 12345 to three significant figures, the retained digits are 1, 2, and 3. The next digit is 4, so the result is 12300. Python’s round(12345, -2) returns 12300, but this only works because the decimal-place offset happens to match. A general function uses Decimal:
from decimal import Decimal, ROUND_HALF_EVEN
def round_sig(x, sig):
d = Decimal(str(x))
if d.is_zero():
return d
exponent = d.adjusted() - sig + 1
return d.quantize(Decimal('1E{}'.format(exponent)), rounding=ROUND_HALF_EVEN)
round_sig(12345, 3) # Decimal('1.23E+4')
round_sig(0.012345, 3) # Decimal('0.0123')
This approach follows ASTM E29 for decimal data and avoids binary float artifacts.
Counter-Examples
- Assuming half-up:
round(2.5)gives 2, not 3. If your process requires half-up, Python’s default is wrong for you. - Using round() for sig figs:
round(12345, 3)rounds to three decimal places, returning 12345. It does not round to three significant figures. - Double rounding:
round(1.2349, 2)gives 1.23. If you first round to three decimals,round(1.2349, 3)gives 1.235, thenround(1.235, 2)gives 1.24. The intermediate rounding changed the final result. - Formatting as rounding:
format(2.5, '.0f')returns'2', but the variable still holds 2.5. Formatting is display, not mutation.
Common Mistakes
- Confusing decimal places with significant figures.
- Using binary floats for decimal rounding and expecting exact ties.
- Rounding intermediate values before the final calculation.
- Assuming Python’s
round()is half-up because that is taught in some schools. - Ignoring ASTM E29’s tie-to-even rule when checking conformance to specifications.
- Not using
Decimalwhen input values are decimal strings from instruments or databases.
Quick Reference Table
| Task | Recommended Python | ASTM E29 alignment |
|---|---|---|
| Round exact decimal to decimal places | Decimal.quantize(..., ROUND_HALF_EVEN) |
Yes |
| Round float to decimal places | round() or format() |
Usually, but binary caveat |
| Round to significant figures | Decimal with computed exponent |
Yes |
| Format for display | format() or f-string |
Yes for ties, display only |
| Round to nearest integer | round() |
Yes for ties to even |
Sources & Further Reading
- ASTM E29-13, Standard Practice for Using Significant Digits in Test Data to Determine Conformance with Specifications, ASTM International.
- ISO 80000-1:2009, Quantities and units — Part 1: General, Clause 6.4.3.
- NIST Special Publication 811, Guide for the Use of the International System of Units (SI), Section 7.4.
- JCGM 100:2008, Evaluation of measurement data — Guide to the expression of uncertainty in measurement (GUM), Clause 7.2.6.
- Python Software Foundation, Python 3 Documentation: decimal — Decimal fixed point and floating point arithmetic.
FAQ
Does Python's round() follow ASTM E29?
For exact ties, yes: Python 3 uses round-half-even, which matches ASTM E29. But binary floats can make decimal ties inexact, so use Decimal for ASTM-compliant decimal rounding.
What is the difference between round() and format()?
round() returns a numeric value; format() returns a string for display. Both use half-even for exact ties, but format() is for presentation, not further calculation.
How do I round to significant figures in Python?
Use Decimal and quantize() with an exponent computed from the adjusted exponent. Do not use round() directly for significant figures.
Does Decimal follow ASTM E29 by default?
Decimal's default context uses ROUND_HALF_EVEN, which matches ASTM E29. Explicitly passing rounding=ROUND_HALF_EVEN to quantize() is clearer and safer.

Leave a Reply