如何在Python中将数字四舍五入为有效数字


148

我需要四舍五入才能在UI中显示。例如,一个重要的数字:

1234-> 1000

0.12-> 0.1

0.012-> 0.01

0.062-> 0.06

6253-> 6000

1999-> 2000

是否有使用Python库执行此操作的好方法,还是我必须自己编写它?



您期望0.062和6253的输出是什么?
lamirap 2010年

封装精度现在可以执行此操作。我发布的答案详细说明了如何应用。
威廉·鲁斯纳克

Answers:


146

您可以使用负数舍入整数:

>>> round(1234, -3)
1000.0

因此,如果您只需要最高有效数字:

>>> from math import log10, floor
>>> def round_to_1(x):
...   return round(x, -int(floor(log10(abs(x)))))
... 
>>> round_to_1(0.0232)
0.02
>>> round_to_1(1234243)
1000000.0
>>> round_to_1(13)
10.0
>>> round_to_1(4)
4.0
>>> round_to_1(19)
20.0

如果大于1,则可能需要将float转换为整数。


3
这是正确的解决方案。使用log10是确定如何舍入的唯一正确方法。
Wolph

73
round_to_n = lambda x,n:round(x,-int(floor(log10(x)))+(n-1))
Roy Hyunjin Han

28
您应该使用log10(abs(x)),否则负数将失败(并且x == 0当然要分开对待)
Tobias Kienzler 2013年

2
我创建了一个可以立即执行此操作的程序包,它可能比此程序包更容易且更可靠。发布链接回购链接。希望这可以帮助!
William Rusnack

2
round_to_n = lambda x, n: x if x == 0 else round(x, -int(math.floor(math.log10(abs(x)))) + (n - 1))防范x==0x<0感谢@RoyHyunjinHan和@TobiasKienzler。不能防止未定义的内容(例如math.inf)或垃圾(例如“无”等)
AJP

98

字符串格式的%g将格式化浮点数,并四舍五入到有效数字。有时会使用科学的“ e”表示法,因此将舍入的字符串转换回浮点数,然后通过%s字符串格式进行格式化。

>>> '%s' % float('%.1g' % 1234)
'1000'
>>> '%s' % float('%.1g' % 0.12)
'0.1'
>>> '%s' % float('%.1g' % 0.012)
'0.01'
>>> '%s' % float('%.1g' % 0.062)
'0.06'
>>> '%s' % float('%.1g' % 6253)
'6000.0'
>>> '%s' % float('%.1g' % 1999)
'2000.0'

7
OP的要求是将1999年的格式设置为“ 2000”,而不是“ 2000.0”。我看不到一种简单的方法来更改您的方法来实现这一目标。
蒂姆·马丁

1
这就是我一直想要的!您在哪里找到的?
djhaskin987

12
请注意,%g的行为并不总是正确的。特别是,它始终会修剪尾随零,即使它们很重要。数字1.23400具有6个有效数字,但是“%.6g”%(1.23400)将导致错误的“ 1.234”。此博客文章中的更多详细信息:randlet.com/blog/python-significant-figures-format
randlet

3
就像Evgeny的答案中的方法一样,此方法无法正确舍入0.0750.08。它返回0.07
加百利

1
round_sig = lambda f,p: float(('%.' + str(p) + 'e') % f)允许您调整有效位数!
denizb

49

如果要使用除1个有效小数之外的其他数字(否则与Evgeny相同):

>>> from math import log10, floor
>>> def round_sig(x, sig=2):
...   return round(x, sig-int(floor(log10(abs(x))))-1)
... 
>>> round_sig(0.0232)
0.023
>>> round_sig(0.0232, 1)
0.02
>>> round_sig(1234243, 3)
1230000.0

8
round_sig(-0.0232)->数学域错误,您可能想在其中添加abs();)
dgorissen 2011年

2
就像叶夫根尼(Evgeny)和彼得·格雷厄姆(Peter Graham)的答案中的方法一样,这无法正确舍入0.0750.08。它返回0.07
加百利

3
同样,对于round_sig(0)也失败。
Yuval Atzmon

2
@Gabriel这是在计算机上运行的python的内置“功能”,并通过函数的行为体现出来rounddocs.python.org/2/tutorial/floatingpoint.html#tut-fp-issues
新手C

1
@Gabriel我添加了一个答案,解释了为什么您应该期望从四舍五入到“ 0.075”可以得到0.7!参见stackoverflow.com/a/56974893/1358308
Sam Mason

30
f'{float(f"{i:.1g}"):g}'
# Or with Python <3.6,
'{:g}'.format(float('{:.1g}'.format(i)))

该解决方案与所有其他解决方案不同,因为:

  1. 恰好解决了OP问题
  2. 它并没有需要任何额外的包
  3. 需要任何用户定义的辅助功能数学运算

对于任意数量n的有效数字,可以使用:

print('{:g}'.format(float('{:.{p}g}'.format(i, p=n))))

测试:

a = [1234, 0.12, 0.012, 0.062, 6253, 1999, -3.14, 0., -48.01, 0.75]
b = ['{:g}'.format(float('{:.1g}'.format(i))) for i in a]
# b == ['1000', '0.1', '0.01', '0.06', '6000', '2000', '-3', '0', '-50', '0.8']

注意:使用此解决方案,不可能从输入动态地调整有效数字的数量,因为没有标准的方法来区分具有不同尾随零的数字(3.14 == 3.1400)。如果需要这样做,则需要非标准功能,例如精确度软件包中提供的功能。


仅供参考:在我尝试用我的代码之一解决相同问题时,我独立于eddygeek找到了该解决方案。现在,我意识到我的解决方案显然与他的解决方案完全相同(我只是注意到错误的输出,并且不费心去读代码,这是我的错误)。可能在他的答案下短的注释就足够了,而不是新的答案。。。唯一(关键)的区别是:g保留整数的格式化程序的双重用途。
法肯

哇,您的答案需要从上到下真正阅读;)这个双重技巧很脏,但是很整洁。(请注意,1999年被格式化为2000.0 显示 5个显著数字,所以它必须要经过{:g}一次。)一般来说,尾随零整数是含糊地对待显著的数字,除非是一些技术(如划线高于去年显著)被使用。
Tomasz Gandor

8

我已经创建了可以满足您需求的高精度软件包。它使您可以为数字赋予或多或少的有效数字。

它还输出带有指定数量有效数字的标准,科学和工程符号。

在接受的答案中有一行

>>> round_to_1(1234243)
1000000.0

实际上指定了8个无花果。对于数字1234243,我的图书馆仅显示一个有效数字:

>>> from to_precision import to_precision
>>> to_precision(1234243, 1, 'std')
'1000000'
>>> to_precision(1234243, 1, 'sci')
'1e6'
>>> to_precision(1234243, 1, 'eng')
'1e6'

还将舍入最后一个有效数字,如果未指定符号,则可以自动选择要使用的符号:

>>> to_precision(599, 2)
'600'
>>> to_precision(1164, 2)
'1.2e3'

现在我正在寻找相同的东西,但应用于熊猫df
mhoff

@mhoff您可能可以将lambda与pandas地图一起使用。lambda x: to_precision(x, 2)
威廉·鲁斯纳克

将此添加到(PyPI)[ pypi.org/]。据我所知,那里没有这样的东西。
Morgoth

这是一个很棒的软件包,但我认为现在大多数功能都在sigfig模块中
HyperActive

1
它有一个错误:std_notation(9.999999999999999e-05,3)给出: '0.00010',这是只有2显著数字
鲍里斯穆德

5

将整数四舍五入到1位有效数字的基本思想是将其转换为浮点数,该浮点数在该点之前1位数并四舍五入,然后将其转换回其原始整数大小。

为此,我们需要知道小于整数10的最大幂。为此,我们可以使用log 10功能的下限。

from math import log10, floor
def round_int(i,places):
    if i == 0:
        return 0
    isign = i/abs(i)
    i = abs(i)
    if i < 1:
        return 0
    max10exp = floor(log10(i))
    if max10exp+1 < places:
        return i
    sig10pow = 10**(max10exp-places+1)
    floated = i*1.0/sig10pow
    defloated = round(floated)*sig10pow
    return int(defloated*isign)

1
再加上一个解决方案,该解决方案无需python的round(..,digits)并且不附加任何字符串!
史蒂夫·罗杰斯

5

为了直接回答这个问题,这是我使用R函数命名的版本:

import math

def signif(x, digits=6):
    if x == 0 or not math.isfinite(x):
        return x
    digits -= math.ceil(math.log10(abs(x)))
    return round(x, digits)

我发布此答案的主要原因是评论抱怨“ 0.075”舍入为0.07而不是0.08。如“新手C”所指出的,这是由于具有有限精度和以2为底的表示形式的浮点运算的组合。实际上可以表示的最接近0.075的数字要小一些,因此舍入的结果与您可能天真地期望的不同。

还要注意,这适用于任何非十进制浮点算法的使用,例如C和Java都有相同的问题。

为了更详细地显示,我们要求Python将数字格式化为“十六进制”格式:

0.075.hex()

这给了我们:0x1.3333333333333p-4。这样做的原因是正常的十进制表示形式通常涉及舍入,因此不是计算机实际“看到”数字的方式。如果您不习惯这种格式,那么Python docsC standard是两个有用的参考。

为了说明这些数字是如何工作的,我们可以通过以下操作回到起点:

0x13333333333333 / 16**13 * 2**-4

应该打印出来0.07516**13是因为小数点后有13个十六进制数字,并且2**-4是因为十六进制指数是以2为底的。

现在我们有了关于浮点数表示方式的一些想法,可以使用decimal模块为我们提供更多的精度,向我们展示发生了什么事情:

from decimal import Decimal

Decimal(0x13333333333333) / 16**13 / 2**4

给:0.07499999999999999722444243844并希望解释为什么round(0.075, 2)评估0.07


1
这很好地解释了为什么在代码级将0.075向下舍入为0.07 ,但是我们(在物理科学中)被教导始终不舍入。因此,尽管有浮点精度问题,但预期的结果实际上是0.08。
加百利

1
我不确定您的困惑在哪里:当您输入0.075时,您实际上是在输入〜0.07499(如上所述),该值根据正常的数学规则取整。如果您使用的数据类型(如十进制浮点数)可能表示0.075,则它的确应取整为0.08
Sam Mason

我不困惑。当我输入0.075时,我实际上是在输入0.075。我不在乎代码中的浮点数学中发生的任何事情。
加百利

@Gabriel:如果你故意进入0.074999999999999999,你会想到在这种情况下获得?
Mark Dickinson

@MarkDickinson取决于。一个有效数字:0.07,两个:0.075。
加百利

4
def round_to_n(x, n):
    if not x: return 0
    power = -int(math.floor(math.log10(abs(x)))) + (n - 1)
    factor = (10 ** power)
    return round(x * factor) / factor

round_to_n(0.075, 1)      # 0.08
round_to_n(0, 1)          # 0
round_to_n(-1e15 - 1, 16) # 1000000000000001.0

希望能充分利用以上所有答案中的最佳答案(减去能够将其表示为一行lambda的意思;)。尚未探索,请随时编辑此答案:

round_to_n(1e15 + 1, 11)  # 999999999999999.9

4

我修改了indgar的解决方案,以处理负数和小数(包括零)。

from math import log10, floor
def round_sig(x, sig=6, small_value=1.0e-9):
    return round(x, sig - int(floor(log10(max(abs(x), abs(small_value))))) - 1)

为什么不只是测试是否x == 0呢?如果您喜欢单线飞机,那就return 0 if x==0 else round(...)
pjvandehaar '16

2
@pjvandehaar,对于一般情况,您是正确的,我应该把它放进去。此外,对于数值计算,我需要执行,我们偶尔会得到类似1e-15的数字。在我们的应用程序中,我们希望将两个小数字(其中一个可能为零)的比较视为相等。还有一些人想将小数(可能是1e-9、1e-15甚至1e-300)四舍五入为零。
ryan281 '16

1
有趣。感谢您的解释。在那种情况下,我真的很喜欢这种解决方案。
pjvandehaar '16

@Morgoth这是一个有趣且困难的问题。正如您所指出的,打印的值未显示3个有效数字,但该值是正确的(例如0.970 == 0.97)。我认为您可以使用其他一些打印解决方案,例如f'{round_sig(0.9701, sig=3):0.3f}'是否要打印零。
ryan281

3

如果您想不涉及字符串而四舍五入,我发现上面的链接中隐藏了该链接:

http://code.activestate.com/lists/python-tutor/70739/

给我最好的印象 然后,当您使用任何字符串格式的描述符进行打印时,您将获得一个合理的输出,并且可以将数字表示形式用于其他计算目的。

链接上的代码由三部分组成:def,doc和return。它有一个错误:您需要检查对数是否爆炸。那很容易。将输入与进行比较sys.float_info.min。完整的解决方案是:

import sys,math

def tidy(x, n):
"""Return 'x' rounded to 'n' significant digits."""
y=abs(x)
if y <= sys.float_info.min: return 0.0
return round( x, int( n-math.ceil(math.log10(y)) ) )

它适用于任何标量数值,float如果由于某种原因需要移动响应,n可以为a 。您实际上可以将限制提高到:

sys.float_info.min*sys.float_info.epsilon

如果您出于某些原因正在使用极小的值,则不会引发错误。


2

我想不出任何能够立即解决的问题。但是对于浮点数来说,它处理得很好。

>>> round(1.2322, 2)
1.23

整数比较棘手。它们不会以10为基数存储在内存中,因此重要的地方并不是自然而然的事情。但是,一旦成为字符串,实现起来就很简单了。

或对于整数:

>>> def intround(n, sigfigs):
...   n = str(n)
...   return n[:sigfigs] + ('0' * (len(n)-(sigfigs)))

>>> intround(1234, 1)
'1000'
>>> intround(1234, 2)

如果您想创建一个可以处理任何数字的函数,我的偏好是将它们都转换为字符串,并寻找一个小数位来决定要做什么:

>>> def roundall1(n, sigfigs):
...   n = str(n)
...   try:
...     sigfigs = n.index('.')
...   except ValueError:
...     pass
...   return intround(n, sigfigs)

另一种选择是检查类型。这将不太灵活,并且可能无法与其他数字(例如Decimal对象)很好地配合使用:

>>> def roundall2(n, sigfigs):
...   if type(n) is int: return intround(n, sigfigs)
...   else: return round(n, sigfigs)

仅仅弄乱字符串就不会四舍五入。1999年四舍五入到1个显著数字是2000年,不是1000
彼得·格雷厄姆


2

给出的答案是给出的最好的答案,但是它有很多限制,并且在技术上没有正确的有效数字。

numpy.format_float_positional直接支持所需的行为。以下片段将浮点数x格式化为4个有效数字,并取消了科学计数法。

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

文档(移至numpy.org/doc/stable/reference/generated/…)指出该函数实现了Dragon4算法(Steele&White 1990,dl.acm.org / doi / pdf / 10.1145 / 93542.93559)。它产生令人讨厌的结果,例如print(*[''.join([np.format_float_positional(.01*a*n,precision=2,unique=False,fractional=False,trim='k',pad_right=5) for a in [.99, .999, 1.001]]) for n in [8,9,10,11,12,19,20,21]],sep='\n')。我没有检查Dragon4本身。
Rainald62

0

我也遇到了这个问题,但是我需要控制舍入类型。因此,我编写了一个快速函数(请参见下面的代码),该函数可以将值,舍入类型和所需的有效数字考虑在内。

import decimal
from math import log10, floor

def myrounding(value , roundstyle='ROUND_HALF_UP',sig = 3):
    roundstyles = [ 'ROUND_05UP','ROUND_DOWN','ROUND_HALF_DOWN','ROUND_HALF_UP','ROUND_CEILING','ROUND_FLOOR','ROUND_HALF_EVEN','ROUND_UP']

    power =  -1 * floor(log10(abs(value)))
    value = '{0:f}'.format(value) #format value to string to prevent float conversion issues
    divided = Decimal(value) * (Decimal('10.0')**power) 
    roundto = Decimal('10.0')**(-sig+1)
    if roundstyle not in roundstyles:
        print('roundstyle must be in list:', roundstyles) ## Could thrown an exception here if you want.
    return_val = decimal.Decimal(divided).quantize(roundto,rounding=roundstyle)*(decimal.Decimal(10.0)**-power)
    nozero = ('{0:f}'.format(return_val)).rstrip('0').rstrip('.') # strips out trailing 0 and .
    return decimal.Decimal(nozero)


for x in list(map(float, '-1.234 1.2345 0.03 -90.25 90.34543 9123.3 111'.split())):
    print (x, 'rounded UP: ',myrounding(x,'ROUND_UP',3))
    print (x, 'rounded normal: ',myrounding(x,sig=3))

0

使用python 2.6+ 新样式格式(不建议使用%-style):

>>> "{0}".format(float("{0:.1g}".format(1216)))
'1000.0'
>>> "{0}".format(float("{0:.1g}".format(0.00356)))
'0.004'

在python 2.7+中,您可以省略前导0s。


使用什么版本的python?Python 3.6.3 | Anaconda,Inc. | (默认值,2017年10月13日,12:02:49)具有相同的旧取整问题。“{0}”格式(浮( “{0:0.1克}”。格式(0.075)))。产率'0.07',而不是'0.08'
唐克兰


0

如果数字大于10 **(-decimal_positions),则此函数进行正常的回合,否则增加更多的小数,直到达到有意义的小数位数为止:

def smart_round(x, decimal_positions):
    dp = - int(math.log10(abs(x))) if x != 0.0 else int(0)
    return round(float(x), decimal_positions + dp if dp > 0 else decimal_positions)

希望能帮助到你。


0

https://stackoverflow.com/users/1391441/gabriel,以下内容是否解决了您对rnd(.075,1)的担忧?警告:以浮点数形式返回值

def round_to_n(x, n):
    fmt = '{:1.' + str(n) + 'e}'    # gives 1.n figures
    p = fmt.format(x).split('e')    # get mantissa and exponent
                                    # round "extra" figure off mantissa
    p[0] = str(round(float(p[0]) * 10**(n-1)) / 10**(n-1))
    return float(p[0] + 'e' + p[1]) # convert str to float

>>> round_to_n(750, 2)
750.0
>>> round_to_n(750, 1)
800.0
>>> round_to_n(.0750, 2)
0.075
>>> round_to_n(.0750, 1)
0.08
>>> math.pi
3.141592653589793
>>> round_to_n(math.pi, 7)
3.141593

0

这将返回一个字符串,以便结果不包含小数部分,并且正确显示了E表示法中否则会出现的较小值:

def sigfig(x, num_sigfig):
    num_decplace = num_sigfig - int(math.floor(math.log10(abs(x)))) - 1
    return '%.*f' % (num_decplace, round(x, num_decplace))

0

给定一个如此彻底回答的问题,为什么不添加另一个

尽管上面的许多内容是可比的,但这更适合我的审美观

import numpy as np

number=-456.789
significantFigures=4

roundingFactor=significantFigures - int(np.floor(np.log10(np.abs(number)))) - 1
rounded=np.round(number, roundingFactor)

string=rounded.astype(str)

print(string)

这适用于单个数字和numpy数组,对于负数应该可以正常工作。

我们可能还要增加一个附加步骤-即使四舍五入为整数,np.round()也会返回一个十进制数(即,对于ificantFigures = 2,我们可能期望返回-460,但相反会得到-460.0)。我们可以添加此步骤以更正此问题:

if roundingFactor<=0:
    rounded=rounded.astype(int)

不幸的是,最后一步不适用于数字数组-亲爱的读者,我会把这个留给您看看是否需要。


0

sigfig包/库盖这一点。后安装,你可以做到以下几点:

>>> from sigfig import round
>>> round(1234, 1)
1000
>>> round(0.12, 1)
0.1
>>> round(0.012, 1)
0.01
>>> round(0.062, 1)
0.06
>>> round(6253, 1)
6000
>>> round(1999, 1)
2000

0
import math

  def sig_dig(x, n_sig_dig):
      num_of_digits = len(str(x).replace(".", ""))
      if n_sig_dig >= num_of_digits:
          return x
      n = math.floor(math.log10(x) + 1 - n_sig_dig)
      result = round(10 ** -n * x) * 10 ** n
      return float(str(result)[: n_sig_dig + 1])


    >>> sig_dig(1234243, 3)
    >>> sig_dig(243.3576, 5)

        1230.0
        243.36
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.