Python 在 numpy 中四舍五入到有效数字

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18915378/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 12:15:10  来源:igfitidea点击:

Rounding to significant figures in numpy

pythonnumpy

提问by dmon

I've tried searching this and can't find a satisfactory answer.

我试过搜索这个,但找不到满意的答案。

I want to take a list/array of numbers and round them all to n significant figures. I have written a function to do this, but I was wondering if there is a standard method for this? I've searched but can't find it. Example:

我想获取一个数字列表/数组并将它们全部四舍五入为 n 个有效数字。我已经写了一个函数来做到这一点,但我想知道是否有一个标准的方法?我已经搜索过,但找不到。例子:

In:  [  0.0, -1.2366e22, 1.2544444e-15, 0.001222 ], n=2
Out: [ 0.00, -1.24e22,        1.25e-15,  1.22e-3 ]

Thanks

谢谢

回答by Andrew Walker

Is numpy.set_printoptionswhat you're looking for?

numpy.set_printoptions你在找什么?

import numpy as np
np.set_printoptions(precision=2)
print np.array([  0.0, -1.2366e22, 1.2544444e-15, 0.001222 ])

Gives:

给出:

[  0.00e+00  -1.24e+22   1.25e-15   1.22e-03]

Edit:

编辑:

numpy.aroundappears to solve aspects of this problem if you're trying to transform the data. However, it doesn't do what you want in cases where the exponent is negative.

如果您尝试转换数据,numpy.around似乎可以解决此问题的各个方面。但是,在指数为负的情况下,它不会执行您想要的操作。

回答by Greg

From the example numbers you have I think you mean significant figures rather than decimal places (-1.2366e22to 0 decimal places is still -1.2366e22).

从您拥有的示例数字中,我认为您的意思是有效数字而不是小数位(-1.2366e22仍然是 0 位小数-1.2366e22)。

This piece of code works for me, I've always thought there should be an inbuilt function though:

这段代码对我有用,但我一直认为应该有一个内置函数:

def Round_To_n(x, n):
    return round(x, -int(np.floor(np.sign(x) * np.log10(abs(x)))) + n)

>>> Round_To_n(1.2544444e-15,2)
1.25e-15

>>> Round_To_n(2.128282321e3, 6)
2130.0

回答by dmon

Okay, so reasonably safe to say this is not allowed for in standard functionality. To close this off then, this is my attempt at a robust solution. It's rather ugly/non-pythonic and prob illustrates better then anything why I asked this question, so please feel free to correct or beat :)

好的,可以说这在标准功能中是不允许的。为了关闭它,这是我对强大解决方案的尝试。这是相当丑陋/非pythonic和prob更好地说明了我问这个问题的任何原因,所以请随时纠正或击败:)

import numpy as np

def round2SignifFigs(vals,n):
    """
    (list, int) -> numpy array
    (numpy array, int) -> numpy array

    In: a list/array of values
    Out: array of values rounded to n significant figures

    Does not accept: inf, nan, complex

    >>> m = [0.0, -1.2366e22, 1.2544444e-15, 0.001222]
    >>> round2SignifFigs(m,2)
    array([  0.00e+00,  -1.24e+22,   1.25e-15,   1.22e-03])
    """

    if np.all(np.isfinite(vals)) and np.all(np.isreal((vals))):
        eset = np.seterr(all='ignore')
        mags = 10.0**np.floor(np.log10(np.abs(vals)))  # omag's
        vals = np.around(vals/mags,n)*mags             # round(val/omag)*omag
        np.seterr(**eset)
        vals[np.where(np.isnan(vals))] = 0.0           # 0.0 -> nan -> 0.0
    else:
        raise IOError('Input must be real and finite')
    return vals

Nearest I get to neat does not account for 0.0, nan, inf or complex:

最近我得到的整洁不占 0.0、nan、inf 或 complex:

>>> omag      = lambda x: 10**np.floor(np.log10(np.abs(x)))
>>> signifFig = lambda x, n: (np.around(x/omag(x),n)*omag(x))

giving:

给予:

>>> m = [0.0, -1.2366e22, 1.2544444e-15, 0.001222]
>>> signifFig(m,2)
array([ nan, -1.24e+22,   1.25e-15,   1.22e-03])

回答by Sean Lake

First a criticism: you're counting the number of significant figures wrong. In your example you want n=3, not 2.

首先批评:你计算的有效数字是错误的。在您的示例中,您需要 n=3,而不是 2。

It is possible to get around most of the edge cases by letting numpy library functions handle them if you use the function that makes the binary version of this algorithm simple: frexp. As a bonus, this algorithm will also run much faster because it never calls the log function.

如果您使用使该算法的二进制版本变得简单的函数:frexp,则可以通过让 numpy 库函数处理它们来绕过大多数边缘情况。作为奖励,该算法也将运行得更快,因为它从不调用 log 函数。

#The following constant was computed in maxima 5.35.1 using 64 bigfloat digits of precision
__logBase10of2 = 3.010299956639811952137388947244930267681898814621085413104274611e-1

import numpy as np

def RoundToSigFigs_fp( x, sigfigs ):
    """
    Rounds the value(s) in x to the number of significant figures in sigfigs.
    Return value has the same type as x.

    Restrictions:
    sigfigs must be an integer type and store a positive value.
    x must be a real value.
    """
    if not ( type(sigfigs) is int or type(sigfigs) is long or
             isinstance(sigfigs, np.integer) ):
        raise TypeError( "RoundToSigFigs_fp: sigfigs must be an integer." )

    if sigfigs <= 0:
        raise ValueError( "RoundToSigFigs_fp: sigfigs must be positive." )

    if not np.isreal( x ):
        raise TypeError( "RoundToSigFigs_fp: x must be real." )

    xsgn = np.sign(x)
    absx = xsgn * x
    mantissa, binaryExponent = np.frexp( absx )

    decimalExponent = __logBase10of2 * binaryExponent
    omag = np.floor(decimalExponent)

    mantissa *= 10.0**(decimalExponent - omag)

    if mantissa < 1.0:
        mantissa *= 10.0
        omag -= 1.0

    return xsgn * np.around( mantissa, decimals=sigfigs - 1 ) * 10.0**omag

And it handles all of your cases correctly, including infinite, nan, 0.0, and a subnormal number:

它可以正确处理所有情况,包括无穷大、nan、0.0 和次正规数:

>>> eglist = [  0.0, -1.2366e22, 1.2544444e-15, 0.001222, 0.0, 
...        float("nan"), float("inf"), float.fromhex("0x4.23p-1028"), 
...        0.5555, 1.5444, 1.72340, 1.256e-15, 10.555555  ]
>>> eglist
[0.0, -1.2366e+22, 1.2544444e-15, 0.001222, 0.0, 
nan, inf, 1.438203867284623e-309, 
0.5555, 1.5444, 1.7234, 1.256e-15, 10.555555]
>>> RoundToSigFigs(eglist, 3)
array([  0.00000000e+000,  -1.24000000e+022,   1.25000000e-015,
         1.22000000e-003,   0.00000000e+000,               nan,
                     inf,   1.44000000e-309,   5.56000000e-001,
         1.54000000e+000,   1.72000000e+000,   1.26000000e-015,
         1.06000000e+001])
>>> RoundToSigFigs(eglist, 1)
array([  0.00000000e+000,  -1.00000000e+022,   1.00000000e-015,
         1.00000000e-003,   0.00000000e+000,               nan,
                     inf,   1.00000000e-309,   6.00000000e-001,
         2.00000000e+000,   2.00000000e+000,   1.00000000e-015,
         1.00000000e+001])

Edit: 2016/10/12 I found an edge case that the original code handled wrong. I have placed a fuller version of the codein a GitHub repository.

编辑:2016/10/12 我发现原始代码处理错误的边缘情况。我已经在 GitHub 存储库中放置了更完整的代码版本。

Edit: 2019/03/01 Replace with recoded version.

编辑:2019/03/01 替换为重新编码的版本。

回答by denizb

There is a simple solution that uses the logic built into pythons string formatting system:

有一个简单的解决方案,它使用 python 字符串格式化系统中内置的逻辑:

def round_sig(f, p):
    return float(('%.' + str(p) + 'e') % f)

Test with the following example:

使用以下示例进行测试:

for f in [0.01, 0.1, 1, 10, 100, 1000, 1000]:
    f *= 1.23456789
    print('%e --> %f' % (f, round_sig(f,3)))

which yields:

产生:

1.234568e-02 --> 0.012350
1.234568e-01 --> 0.123500
1.234568e+00 --> 1.235000
1.234568e+01 --> 12.350000
1.234568e+02 --> 123.500000
1.234568e+03 --> 1235.000000
1.234568e+03 --> 1235.000000

Best of luck!

祝你好运!

(If you like lambdas use:

(如果你喜欢 lambdas 使用:

round_sig = lambda f,p: float(('%.' + str(p) + 'e') % f)

)

)

回答by user11336338

I like Greg's very short effective routine above. However, it suffers from two drawbacks. One is that it doesn't work for x<0, not for me anyway. (That np.sign(x)should be removed.) Another is that it does not work if xis an array. I've fixed both of those problems with the routine below. Notice that I've changed the definition of n.

我喜欢上面 Greg 非常简短的有效例程。然而,它有两个缺点。一是它对 不起作用x<0,反正对我不起作用。(np.sign(x)应该删除。)另一个是如果x是数组则不起作用。我已经用下面的例程解决了这两个问题。请注意,我更改了n.

import numpy as np

def Round_n_sig_dig(x, n):

    xr = (np.floor(np.log10(np.abs(x)))).astype(int)
    xr=10.**xr*np.around(x/10.**xr,n-1)   
    return xr    

回答by A. West

For Scalars

对于标量

sround = lambda x,p: float(f'%.{p-1}e'%x)

Example

例子

>>> print( sround(123.45, 2) )
120.0

For Arrays

对于数组

Use Scott Gigante's signif(x, p)fig1fig2

使用Scott Gigantesignif(x, p)fig1 fig2

回答by Autumn

Most of the solutions given here either (a) don't give correct significant figures, or (b) are unnecessarily complex.

这里给出的大多数解决方案要么 (a) 没有给出正确的有效数字,要么 (b) 不必要地复杂。

If your goal is displayformatting, then numpy.format_float_positionalsupports the desired behaviour directly. The following fragment returns the float xformatted to 4 significant figures, with scientific notation suppressed.

如果您的目标是显示格式,则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.

回答by Scott Gigante

Testing all of the already proposed solutions, I find they either

测试所有已经提出的解决方案,我发现它们要么

  1. convert to and from strings, which is inefficient
  2. can't handle negative numbers
  3. can't handle arrays
  4. have some numerical errors.
  1. 与字符串相互转换,效率低下
  2. 无法处理负数
  3. 无法处理数组
  4. 有一些数字错误。

Here's my attempt at a solution which should handle all of these things. (Edit 2020-03-18: added np.asarrayas suggested by A. West.)

这是我尝试解决所有这些问题的解决方案。(编辑 2020-03-18:np.asarray按照 A. West 的建议添加。)

def signif(x, p):
    x = np.asarray(x)
    x_positive = np.where(np.isfinite(x) & (x != 0), np.abs(x), 10**(p-1))
    mags = 10 ** (p - 1 - np.floor(np.log10(x_positive)))
    return np.round(x * mags) / mags

Testing:

测试:

def scottgigante(x, p):
    x_positive = np.where(np.isfinite(x) & (x != 0), np.abs(x), 10**(p-1))
    mags = 10 ** (p - 1 - np.floor(np.log10(x_positive)))
    return np.round(x * mags) / mags

def awest(x,p):
    return float(f'%.{p-1}e'%x)

def denizb(x,p):
    return float(('%.' + str(p-1) + 'e') % x)

def autumn(x, p):
    return np.format_float_positional(x, precision=p, unique=False, fractional=False, trim='k')

def greg(x, p):
    return round(x, -int(np.floor(np.sign(x) * np.log10(abs(x)))) + p-1)

def user11336338(x, p):         
    xr = (np.floor(np.log10(np.abs(x)))).astype(int)
    xr=10.**xr*np.around(x/10.**xr,p-1)   
    return xr

def dmon(x, p):
    if np.all(np.isfinite(x)):
        eset = np.seterr(all='ignore')
        mags = 10.0**np.floor(np.log10(np.abs(x)))  # omag's
        x = np.around(x/mags,p-1)*mags             # round(val/omag)*omag
        np.seterr(**eset)
        x = np.where(np.isnan(x), 0.0, x)           # 0.0 -> nan -> 0.0
    return x

def seanlake(x, p):
    __logBase10of2 = 3.010299956639811952137388947244930267681898814621085413104274611e-1
    xsgn = np.sign(x)
    absx = xsgn * x
    mantissa, binaryExponent = np.frexp( absx )

    decimalExponent = __logBase10of2 * binaryExponent
    omag = np.floor(decimalExponent)

    mantissa *= 10.0**(decimalExponent - omag)

    if mantissa < 1.0:
        mantissa *= 10.0
        omag -= 1.0

    return xsgn * np.around( mantissa, decimals=p - 1 ) * 10.0**omag

solns = [scottgigante, awest, denizb, autumn, greg, user11336338, dmon, seanlake]

xs = [
    1.114, # positive, round down
    1.115, # positive, round up
    -1.114, # negative
    1.114e-30, # extremely small
    1.114e30, # extremely large
    0, # zero
    float('inf'), # infinite
    [1.114, 1.115e-30], # array input
]
p = 3

print('input:', xs)
for soln in solns:
    print(f'{soln.__name__}', end=': ')
    for x in xs:
        try:
            print(soln(x, p), end=', ')
        except Exception as e:
            print(type(e).__name__, end=', ')
    print()

Results:

结果:

input: [1.114, 1.115, -1.114, 1.114e-30, 1.114e+30, 0, inf, [1.114, 1.115e-30]]
scottgigante: 1.11, 1.12, -1.11, 1.11e-30, 1.11e+30, 0.0, inf, [1.11e+00 1.12e-30], 
awest: 1.11, 1.11, -1.11, 1.11e-30, 1.11e+30, 0.0, inf, TypeError, 
denizb: 1.11, 1.11, -1.11, 1.11e-30, 1.11e+30, 0.0, inf, TypeError, 
autumn: 1.11, 1.11, -1.11, 0.00000000000000000000000000000111, 1110000000000000000000000000000., 0.00, inf, TypeError, 
greg: 1.11, 1.11, -1.114, 1.11e-30, 1.11e+30, ValueError, OverflowError, TypeError, 
user11336338: 1.11, 1.12, -1.11, 1.1100000000000002e-30, 1.1100000000000001e+30, nan, nan, [1.11e+00 1.12e-30], 
dmon: 1.11, 1.12, -1.11, 1.1100000000000002e-30, 1.1100000000000001e+30, 0.0, inf, [1.11e+00 1.12e-30], 
seanlake: 1.11, 1.12, -1.11, 1.1100000000000002e-30, 1.1100000000000001e+30, 0.0, inf, ValueError, 

Timing:

定时:

def test_soln(soln):
    try:
        soln(np.linspace(1, 100, 1000), 3)
    except Exception:
        [soln(x, 3) for x in np.linspace(1, 100, 1000)]

for soln in solns:
    print(soln.__name__)
    %timeit test_soln(soln)

Results:

结果:

scottgigante
135 μs ± 15.3 μs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
awest
2.23 ms ± 430 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
denizb
2.18 ms ± 352 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
autumn
2.92 ms ± 206 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
greg
14.1 ms ± 1.21 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
user11336338
157 μs ± 50.1 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
dmon
142 μs ± 8.52 μs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
seanlake
20.7 ms ± 994 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)

回答by Aditya Sinha

I got quite frustrated after scouring the internet and not finding an answer for this, so I wrote my own piece of code. Hope this is what you're looking for

在搜索互联网并没有找到答案后,我感到非常沮丧,所以我编写了自己的一段代码。希望这是你要找的

import numpy as np
from numpy import ma

exp = np.floor(ma.log10(abs(X)).filled(0))
ans = np.round(X*10**-exp, sigfigs-1) * 10**exp

Just plug in your np array X and the required number of significant figures. Cheers!

只需插入您的 np 数组 X 和所需的有效数字数。干杯!