ide*_*n42 5 python floating-point rounding python-3.x
我遇到了一个问题,显示floatPython中的值,从外部数据源加载
(它们是32位浮点数,但这也适用于较低精度的浮点数).
(如果它很重要 - 这些值是由C/C++中的人类输入的,所以与任意计算值不同,可能无法忽略与圆数的偏差,但不能忽略,因为这些值可能是常数,例如或乘以常量).M_PI
由于CPython使用更高的精度(通常为64位),因此作为较低精度浮点输入的值可能会repr()显示32位浮点数的精度损失,其中64位浮点数将显示舍入值.
例如:
# Examples of 32bit float's displayed as 64bit floats in CPython.
0.0005 -> 0.0005000000237487257
0.025 -> 0.02500000037252903
0.04 -> 0.03999999910593033
0.05 -> 0.05000000074505806
0.3 -> 0.30000001192092896
0.98 -> 0.9800000190734863
1.2 -> 1.2000000476837158
4096.3 -> 4096.2998046875
Run Code Online (Sandbox Code Playgroud)
在大多数情况下,简单地将值舍入为某些任意精度都可以工作,但可能不正确,因为它可能会丢失重要值,例如:0.00000001.
可以通过打印转换为32位浮点的浮点来显示此示例.
def as_float_32(f):
from struct import pack, unpack
return unpack("f", pack("f", f))[0]
print(0.025) # --> 0.025
print(as_float_32(0.025)) # --> 0.02500000037252903
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:
什么是最有效和直接的方式来获得32位浮点的原始表示,而不做假设或失去精度?
换句话说,如果我有一个包含32位浮点数据的数据源,那么这些数据源最初是由人类作为圆值输入的(上面的示例),但是将它们表示为更高的精度值会暴露出该值为32位浮点数是一个近似的原始值.
我想颠倒这个过程,从32位浮点数据中获取回数,但不会失去32位浮点数给我们的精度.(这就是为什么简单的舍入不是一个好选择).
您可能想要执行此操作的示例:
在这两种情况下,重要的是不要失去显着的精确度,或者显示人类一眼就看不到的值.
更新,我已经提出了一个解决方案,我将其作为答案(供参考并展示其可能),但高度怀疑它是一个高效或优雅的解决方案.
当然,你可以不知道所用的符号:0.1f,0.1F或1e-1f其中进入,这不是这个问题的目的.
Mar*_*son 10
您正在寻找解决Python解决的基本相同的问题repr,即找到舍入到给定浮点数的最短十进制字符串.除了在您的情况下,浮点数不是IEEE 754二进制64("双精度")浮点数,而是IEEE 754二进制32("单精度")浮点数.
只是为了记录在案,我当然应该指出的是,获取原始字符串表示是不可能的,因为比如字符串'0.10','0.1','1e-1'和'10e-2'所有被转换到相同的浮动(或在这种情况下float32).但是在合适的条件下,我们仍然可以希望生成一个与原始字符串具有相同十进制值的字符串,这就是我将在下面做的.
您在答案中概述的方法或多或少有效,但可以简化一些.
首先,一些界限:当谈到单精度浮点数的十进制表示时,有两个幻数:6和9.重要的6是任何(不太大,不太小)具有6个或更少有效十进制数字的十进制数字串将通过单精度IEEE 754浮点数正确往返:即将该字符串转换为最近的float32,然后将该值转换回最近的6-digit十进制字符串,将生成一个与原始值相同的字符串.例如:
>>> x = "634278e13"
>>> y = float(np.float32(x))
>>> y
6.342780214942106e+18
>>> "{:.6g}".format(y)
'6.34278e+18'
Run Code Online (Sandbox Code Playgroud)
(这里,"不太大,不太小"我只是意味着float32应该避免下溢和溢出范围.上面的属性适用于所有正常值.)
这意味着对于您的问题,如果原始字符串有6个或更少的数字,我们可以通过简单地将值格式化为6位有效数字来恢复它.因此,如果你只关心恢复首先有6个或更少有效小数位的字符串,你可以在这里停止阅读:简单'{:.6g}'.format(x)就足够了.如果您想更一般地解决问题,请继续阅读.
对于另一个方向的往返,我们有相反的属性:给定任何单精度浮点数x,将该浮点数转换为9位十进制字符串(舍入到最接近,一如既往),然后将该字符串转换回单精度float,将始终完全恢复该float的值.
>>> x = np.float32(3.14159265358979)
>>> x
3.1415927
>>> np.float32('{:.9g}'.format(x)) == x
True
Run Code Online (Sandbox Code Playgroud)
与您的问题的相关性始终是至少有一个9位数的字符串x,所以我们永远不必超过9位数.
现在我们可以按照您在答案中使用的相同方法:首先尝试6位数字符串,然后是7位数字,然后是8位数字.如果这些都不起作用,那么9位数的字符串肯定会由上面的字符串组成.这是一些代码.
def original_string(x):
for places in range(6, 10): # try 6, 7, 8, 9
s = '{:.{}g}'.format(x, places)
y = np.float32(s)
if x == y:
return s
# If x was genuinely a float32, we should never get here.
raise RuntimeError("We should never get here")
Run Code Online (Sandbox Code Playgroud)
示例输出:
>>> original_string(0.02500000037252903)
'0.025'
>>> original_string(0.03999999910593033)
'0.04'
>>> original_string(0.05000000074505806)
'0.05'
>>> original_string(0.30000001192092896)
'0.3'
>>> original_string(0.9800000190734863)
'0.98'
Run Code Online (Sandbox Code Playgroud)
但是,上面提到了一些警告.
首先,对于我们使用的关键属性,我们必须假设np.float32始终进行正确的舍入.这可能是也可能不是,取决于操作系统.(即使在相关操作系统调用声称被正确舍入的情况下,仍可能存在声称无法实现的极端情况.)在实践中,它可能np.float32足够接近正确舍入而不会导致问题,但对于你完全有信心,你想知道它是正确的圆形.
其次,上述不适用于低于正常范围的值(因此float32,小于的值2**-126).在低于正常范围内,6位十进制数字字符串将通过单精度浮点数正确往返是不正确的.如果你关心次正规,你需要在那里做一些更复杂的事情.
第三,上面有一个非常微妙(和有趣!)的错误几乎无关紧要.我们使用的字符串格式总是舍x入到最接近的 places -digit十进制字符串到真值x.但是,我们想知道是否有任何 - places数字十进制字符串回滚x.我们隐含地假设(看似显而易见的)事实,即如果有任何 - places数字十进制字符串x,则最接近的 places数字十进制字符串舍入到x.这几乎是正确的:从属性得出的结果是,所有实数的间隔x是对称的x.但是,这种对称属性在一种特定情况下失败,即何时x是一种权力2.
因此,如果x是精确的幂2,则可能(但不太可能)(例如)最接近的8位十进制字符串x 不会舍入到x,但是仍有一个8位十进制字符串可以舍入到x.你可以做的情况下,一个详尽的搜索中,发生这种情况的范围内float32,而且事实证明,恰好有三个值,x发生这种情况,即x = 2**-96,x = 2**87和x = 2**90.对于7位数字,没有这样的值.(对于6位和9位数字,这种情况永远不会发生.)让我们仔细看看这个案例x = 2**87:
>>> x = 2.0**87
>>> x
1.5474250491067253e+26
Run Code Online (Sandbox Code Playgroud)
我们将最接近的8位十进制值取为x:
>>> s = '{:.8g}'.format(x)
>>> s
'1.547425e+26'
Run Code Online (Sandbox Code Playgroud)
事实证明,这个值不会回到x:
>>> np.float32(s) == x
False
Run Code Online (Sandbox Code Playgroud)
但是它的下一个8位十进制数字符号确实如下:
>>> np.float32('1.5474251e+26') == x
True
Run Code Online (Sandbox Code Playgroud)
同样,情况x = 2**-96如下:
>>> x = 2**-96.
>>> x
1.262177448353619e-29
>>> s = '{:.8g}'.format(x)
>>> s
'1.2621774e-29'
>>> np.float32(s) == x
False
>>> np.float32('1.2621775e-29') == x
True
Run Code Online (Sandbox Code Playgroud)
因此,在所有20亿左右的正常单精度值中忽略次正规和溢出,恰好有三个值x,上述代码不起作用.(注意:我原本以为只有一个;感谢@RickRegan指出评论中的错误.)所以这是我们的(略带舌头)固定代码:
def original_string(x):
"""
Given a single-precision positive normal value x,
return the shortest decimal numeric string which produces x.
"""
# Deal with the three awkward cases.
if x == 2**-96.:
return '1.2621775e-29'
elif x == 2**87:
return '1.5474251e+26'
elif x == 2**90:
return '1.2379401e+27'
for places in range(6, 10): # try 6, 7, 8, 9
s = '{:.{}g}'.format(x, places)
y = np.float32(s)
if x == y:
return s
# If x was genuinely a float32, we should never get here.
raise RuntimeError("We should never get here")
Run Code Online (Sandbox Code Playgroud)
至少在 python3 中你可以使用.as_integer_ratio. 这不完全是一个字符串,但浮点定义本身并不适合在“有限”字符串中给出精确的表示。
a = 0.1
a.as_integer_ratio()
(3602879701896397, 36028797018963968)
Run Code Online (Sandbox Code Playgroud)
因此,通过保存这两个数字,您永远不会失去精度,因为这两个数字准确地代表了保存的浮点数。(只需将第一个除以第二个即可得到值)。
作为使用 numpy dtypes 的示例(与 c dtypes 非常相似):
# A value in python floating point precision
a = 0.1
# The value as ratio of integers
b = a.as_integer_ratio()
import numpy as np
# Force the result to have some precision:
res = np.array([0], dtype=np.float16)
np.true_divide(b[0], b[1], res)
print(res)
# Compare that two the wanted result when inputting 0.01
np.true_divide(1, 10, res)
print(res)
# Other precisions:
res = np.array([0], dtype=np.float32)
np.true_divide(b[0], b[1], res)
print(res)
res = np.array([0], dtype=np.float64)
np.true_divide(b[0], b[1], res)
print(res)
Run Code Online (Sandbox Code Playgroud)
所有这些计算的结果是:
[ 0.09997559] # Float16 with integer-ratio
[ 0.09997559] # Float16 reference
[ 0.1] # Float32
[ 0.1] # Float64
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
537 次 |
| 最近记录: |