Round to significant figures in python

Question:

I want to round like this 42949672 -> 42949700, 2147483647 -> 2147480000, 4252017622 -> 4252020000 etc.

I tried to use following , but only works for the first one. How can I make a more general one? thanks

round(42949672, -2)
Asked By: golu

||

Answers:

To round to any digits that you are targeting, you can simply put the digits as a negative number in your round function and then convert it to integer.

digits = 4
num = 2147483647
rounded_num = int(round(num, -digits))

If you are trying to keep 6 significant figures always, that’s the pattern that I saw in your question you would want to do this:

def sf_round(num, sig_fig):
    return int(round(num, -(len(str(num))-sig_fig)))
Answered By: pilatipus

I assume you want to round to 6 significant figures. If you want to round ints, you can use

def round_sf(number, significant):
    return round(number, significant - len(str(number)))

print(round_sf(4252017622, 6))

Result:

4252020000
Answered By: Reinstate Monica

The accepted answer has limitations and does not produce technically correct significant figures in the general case.

numpy.format_float_positional supports the desired behaviour directly. The following fragment returns the float x formatted to 4 significant figures, with scientific notation suppressed.

import numpy as np
x=12345.6
np.format_float_positional(x, precision=4, unique=False, fractional=False, trim='k')
> 12340.
Answered By: Autumn

To expand upon Autumn‘s answer, the reason that the accepted answer fails in the general case is that round is rounding to decimal places and not (necessarily) to significant figures.

The following function addresses the case when there are significant figures both before and after the decimal place:

import math
def round_to_nsf(number, nsf=6):
    integer_part = math.floor(number)
    return round(number, nsf - len(str(integer_part)))

although it doesn’t address the case for numbers like 0.0000012345678, which rounded to 6 s.f. should be 0.00000123457.

The following code almost works – but is very inefficient, risks stack overflow through its use of recursion and won’t always return the correct answer due to the fact that computers use binary arithmetic instead of decimal:

def round_to_nsf(number, nsf=6):
    integer_part = math.floor(number)
    if integer_part > 0:
        return round(number, nsf - len(str(integer_part)))
    else:
        return round_to_nsf(10 * number, nsf) / 10

But I think that the else clause could be modified by transforming the number into a string, using the main function in a single recursive step, then constructing the output from strings in order to obtain a non-recurring decimal.

Answered By: David Joy

Expanded on @david-joy’s function. Indeed, it did not work properly in the else clause, and actually not all cases were covered in the if clause.
So here is my function.

def round_to_nsf(value, nsf=2):
    """
    Rounds the number to the provided number of significant figures.
    """

    integer_part = math.floor(value)
    if integer_part > 0:
        integer_part_len = len(str(integer_part))
        return round(value, nsf-integer_part_len)
    else:
        str_value = str(value)
        #if of the form "8e-05"
        if '-' in str_value:
            index = int(str_value[str_value.find('-')+1:]) - 1
        else:
            st = {'1', '2', '3', '4', '5', '6', '7', '8', '9'}
            index = next((i for i, ch in enumerate(str(value)) if ch in st), None) - 2
        return round(value, index+nsf)
Answered By: Uladzimir Treihis

These work for me, for ints and floats, both positive and negative. If desired they may be modified to preserve the original int or float type by applying isinstance() to x and then return int(float()) or float().

Note that format beyond around 14 digits can give unexpected results.

# Option 1: use math to find number of digits left of decimal
import math
def round_sig(x, sigdig=14):
    """Round x to sigdig significant digits"""
    if x == 0: return 0   # can't log10(0)
    # n = digits left of decimal, can be negative
    n = math.floor(math.log10(abs(x))) + 1
    return round(x, sigdig - n)

# Option 2: use format to round
def round_sig2(x, sigdig=14):
    """Round x to sigdig significant digits"""
    f = f'{{:.{sigdig - 1}E}}'
    s = f.format(x)
    return float(s)

# Option 3: Use format to find number of digits left of decimal
def round_sig3(x, sigdig=14):
    """Round x to sigdig significant digits"""
    s = '{:.0E}'.format(x)
    # n = digits left of decimal, can be negative
    n = int( s[s.index('E') + 1 :] ) + 1
    return round(x, sigdig - n)

# Bonus: use math to truncate
import math
def trunc_sig(x, sigdig=14):
    """Truncate x to sigdig significant digits"""
    if x == 0: return 0   # can't log10(0)
    # n = digits left of decimal, can be negative
    n = math.floor(math.log10(abs(x))) + 1
    shift = 10**(sigdig - n)
    return int(x * shift) / shift

# Unexpected format results with large number of significant digits
>>> '{:.20E}'.format(10**-1)
'1.00000000000000005551E-01'

>>> '{:.20E}'.format(10**-10)
'1.00000000000000003643E-10'

>>> '{:.20E}'.format(10**-20)
'9.99999999999999945153E-21'

>>> n = .123456789012345678901234567890
>>> '{:.20E}'.format(n)
'1.23456789012345677370E-01'
Answered By: Don H
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.