Explained clearly 5 min read

Sig Figs in Python: round(), format(), and Decimal — Which One Follows ASTM E29?

Python's round() and format() use half-even ties, matching ASTM E29 for exact ties, but binary floats can break decimal rounding. Decimal.quantize() is the reliable ASTM-compliant choice.

Short Answer

Python's round() and format() use half-even ties, matching ASTM E29 for exact ties, but binary floats can break decimal rounding. Decimal.quantize() is the reliable ASTM-compliant choice.

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, then round(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 Decimal when 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.

Verified sources

References

  1. ASTM E29-13, Standard Practice for Using Significant Digits in Test Data to Determine Conformance with Specifications, ASTM International.
  2. ISO 80000-1:2009, Quantities and units — Part 1: General, Clause 6.4.3.
  3. NIST Special Publication 811, Guide for the Use of the International System of Units (SI), Section 7.4.
  4. JCGM 100:2008, Evaluation of measurement data — Guide to the expression of uncertainty in measurement (GUM), Clause 7.2.6.
  5. Python Software Foundation, Python 3 Documentation: decimal — Decimal fixed point and floating point arithmetic.

Leave a Reply

Your email address will not be published. Required fields are marked *