Explained clearly 5 min read

How to Round to Significant Figures in Python Using the decimal Module

Use Decimal.quantize with an exponent derived from the adjusted exponent to round to n significant figures, choosing ROUND_HALF_EVEN for standards-aligned ties.

Short Answer

Use Decimal.quantize with an exponent derived from the adjusted exponent to round to n significant figures, choosing ROUND_HALF_EVEN for standards-aligned ties.

This reference explains how to round to significant figures in Python using the decimal module. It is part of a precision-and-rounding reference site, not merely a calculator. The goal is to give students, engineers, scientists, and educators a reliable, standards-aware guide to significant figures, rounding rules, and the exact behavior of Python’s decimal type.

Rule Statement

Rounding to n significant figures means keeping the first n meaningful digits, starting from the first nonzero digit, and replacing the rest with zeros or dropping them. In Python, the decimal module does not provide a direct round_sig() function. Instead, use Decimal.quantize() with an exponent that corresponds to the desired place value.

For a nonzero Decimal x, the target exponent is:

exponent = x.adjusted() – n + 1

Here adjusted() returns the exponent of the most significant digit. For example, Decimal('1234.567').adjusted() is 3, and Decimal('0.0012345').adjusted() is -3. Then quantize(Decimal('1e{exponent}')) rounds to that place value.

from decimal import Decimal, localcontext, ROUND_HALF_EVEN

def round_sig(x, n, rounding=ROUND_HALF_EVEN):
    x = Decimal(x) if not isinstance(x, Decimal) else x
    if x.is_zero():
        return x
    exp = x.adjusted() - n + 1
    q = Decimal('1e{}'.format(exp))
    with localcontext() as ctx:
        ctx.prec = max(28, len(x.as_tuple().digits) + abs(exp) + 5)
        return x.quantize(q, rounding=rounding)

Always construct Decimal values from strings, such as Decimal('2.675'), not from binary floats. This avoids hidden binary artifacts that corrupt rounding.

Software Behavior Note

The Python decimal module implements the IBM General Decimal Arithmetic Specification. Its default context rounding mode is ROUND_HALF_EVEN, also called banker’s rounding. This aligns with several metrology and standards conventions for tie-breaking.

quantize() rounds to a fixed exponent, not directly to significant figures. It also respects the current context precision. If the result would have more digits than the context allows, quantize() may raise InvalidOperation. Set ctx.prec high enough for large coefficients, especially when rounding very large or very small numbers.

Zero is special: Decimal('0').adjusted() is 0, but the number of significant figures in zero is ambiguous. Standards such as ambiguous trailing zeros treat zeros as significant only when explicitly indicated. The helper above returns zero unchanged.

Worked Examples

Example 1: 1234.567 to 3 significant figures

  1. Decimal('1234.567').adjusted() = 3.
  2. Target exponent = 3 - 3 + 1 = 1.
  3. Quantize to Decimal('1e1'), which is 10.
  4. With ROUND_HALF_UP, 1234.567 becomes 1230.
Decimal('1234.567').quantize(Decimal('1e1'), rounding=ROUND_HALF_UP)
# Decimal('1.23E+3')

Result: 1.23 × 103, or 1230 in positional notation.

Example 2: 0.0012345 to 3 significant figures

  1. Decimal('0.0012345').adjusted() = -3.
  2. Target exponent = -3 - 3 + 1 = -5.
  3. Quantize to Decimal('1e-5').
  4. Result: 0.00123.
Decimal('0.0012345').quantize(Decimal('1e-5'), rounding=ROUND_HALF_UP)
# Decimal('0.00123')

Example 3: 2.5 to 1 significant figure

Under ROUND_HALF_EVEN, 2.5 rounds to 2 because the retained digit 2 is even. Under ROUND_HALF_UP, 2.5 rounds to 3. This is the classic tie-breaking difference between banker’s rounding and half-up rounding.

Counter-Examples

  • Float contamination: Decimal(2.675) is actually 2.674999999999999822.... Quantizing to two decimal places with ROUND_HALF_UP gives 2.67, not 2.68. Use Decimal('2.675').
  • Double rounding: Rounding 1.245 to 2 significant figures directly gives 1.2. But rounding first to 3 significant figures gives 1.25, then to 2 gives 1.3. That is a double rounding error.
  • Decimal places vs significant figures: f'{1234.567:.2f}' gives 1234.57, which has two decimal places, not two significant figures. The correct 3-significant-figure result is 1230.
  • Insufficient context precision: quantize() can fail for very large coefficients if ctx.prec is too low. Always raise precision before quantizing.

Convention Comparison Table

Convention Tie-breaking Typical use Python constant
Half-even (banker’s) Round to nearest even last digit ASTM E29, ISO 80000, Python default ROUND_HALF_EVEN
Half-up Always round 5 up Everyday math, some finance ROUND_HALF_UP
Half-down Always round 5 down Specialized fields ROUND_HALF_DOWN
Truncation Drop extra digits Conservative limits ROUND_DOWN

Standards Citation

ASTM E29-13a, Section 6.2 recommends rounding ties to the nearest even digit for test data conformance. ISO 80000-1:2009, Clause 7.3.3 gives the same rule for general quantities and units. JCGM 100:2008 (GUM), Clause 7.2.6 advises reporting uncertainty to at most two significant digits. NIST SP 811, Section 7.9 provides rounding guidance for SI values. Python’s default ROUND_HALF_EVEN is therefore a defensible choice for standards-aligned work.

GUM 7.2.6: The numerical values of the estimate and its uncertainty should not be given with an excessive number of digits. In practice, uncertainty is often reported to two significant digits.

Common Mistakes

  • Using Decimal(float) instead of Decimal('string').
  • Confusing decimal places with significant figures.
  • Forgetting to increase context precision before quantize().
  • Using built-in round() for significant figures; it rounds to decimal places and uses binary floats.
  • Double rounding: rounding twice instead of once to the final precision.
  • Assuming trailing zeros are never significant; in measurement they can be.

Quick Reference Table

Task Code pattern
Import tools from decimal import Decimal, localcontext, ROUND_HALF_EVEN
Find target exponent exp = x.adjusted() - n + 1
Build quantizer q = Decimal('1e{}'.format(exp))
Round to n sig figs x.quantize(q, rounding=ROUND_HALF_EVEN)
Use high precision with localcontext() as ctx: ctx.prec = 50

FAQ

Does Python's decimal module round to significant figures automatically?

No. Use Decimal.quantize() with an exponent computed from x.adjusted() - n + 1.

Which rounding mode should I use for significant figures?

ROUND_HALF_EVEN is the Python default and aligns with ASTM E29 and ISO 80000 for tie-breaking. Some fields prefer ROUND_HALF_UP.

Why does Decimal(2.675) round incorrectly?

Decimal(2.675) converts a binary float, which is not exactly 2.675. Use Decimal('2.675') instead.

How many significant figures should uncertainty have?

GUM JCGM 100:2008, Clause 7.2.6, suggests uncertainty is often reported to two significant digits.

Verified sources

References

  1. Python Software Foundation. decimal — Decimal fixed point and floating point arithmetic. https://docs.python.org/3/library/decimal.html
  2. ASTM E29-13a, Standard Practice for Using Significant Digits in Test Data to Determine Conformance with Specifications. ASTM International, West Conshohocken, PA.
  3. ISO 80000-1:2009, Quantities and units — Part 1: General. ISO, Geneva, Clause 7.3.3.
  4. JCGM 100:2008, Evaluation of measurement data — Guide to the expression of uncertainty in measurement (GUM), Clause 7.2.6.
  5. NIST SP 811 (2008), Guide for the Use of the International System of Units (SI), Section 7.9.

Leave a Reply

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