5

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)
golu
  • 629
  • 2
  • 7
  • 18
  • `round(2147483647, -4)` gives `2147480000` as desired. You need to tell it how many digits places to round to. – Hugh Bothwell Jul 18 '17 at 23:14
  • yes, but round(42949672, -4) doesn't give 42949700. I guess I need to digit places math myself – golu Jul 18 '17 at 23:36

6 Answers6

9

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.
Autumn
  • 3,214
  • 1
  • 20
  • 35
3

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.

David Joy
  • 31
  • 3
1

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
Reinstate Monica
  • 4,568
  • 1
  • 24
  • 35
0

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)))
pilatipus
  • 91
  • 1
  • 4
0

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)
0

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'
Don H
  • 41
  • 4