精度溢出问题

背景

python中定义好的浮点型数据,在实际业务系统传输过程中,出现了精度溢出的问题。具体实例如下:

加载数据

import numpy as np
import pandas as  pd


#加载本地的测试数据
data_path=r'D:\desktop\data_b6da1bdd4fa54677a03994e0db9fb508.csv'
data=pd.read_csv(data_path)
data.head()
"""
	time	366cf056-0f9b-11ed-9353-99e44f95bc32
0	0.0	0.066
1	0.2	0.052
2	0.5	0.023
3	0.8	0.006
4	1.0	0.004

"""

查看数据基础属性


data.info()#查看数据的基础信息
"""
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2049 entries, 0 to 2048
Data columns (total 2 columns):
 #   Column                                Non-Null Count  Dtype  
---  ------                                --------------  -----  
 0   time                                  2049 non-null   float64
 1   366cf056-0f9b-11ed-9353-99e44f95bc32  2049 non-null   float64
dtypes: float64(2)
memory usage: 32.1 KB

"""

业务应用:将dataframe转换成json

def dataframe_to_json(raw_data):
    """将dataframe转换成json的形式"""
    data = raw_data.values.tolist()
    cols = raw_data.columns.tolist()
    output = list(map(lambda x: dict(zip(cols, x)), data))
    return output
dataframe_to_json(data)[:10]#查看前10个
"""
[{'time': 0.0, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.066},
 {'time': 0.2, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.052},
 {'time': 0.5, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.023},
 {'time': 0.8, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.006},
 {'time': 1.0, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.004},
 {'time': 1.2, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.009},
 {'time': 1.5, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.003},
 {'time': 1.8, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.002},
 {'time': 2.0, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.002},
 {'time': 2.2, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.003}]
"""

在jupyter或pycharm本地测试中显示没有任何问题,但是嵌入到开发的软件中,以exe的方式运行时,过程中偶尔传输的结果是:

#相同的python虚拟环境,相同的数据,在实际项目过程中运行的结果:
[{'time': 0.0, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.06599999964237213}, {'time': 0.2, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.052000001072883606}, {'time': 0.5, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.023000000044703484}, {'time': 0.8, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.006000000052154064}, {'time': 1.0, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.004000000189989805}, {'time': 1.2, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.008999999612569809}, {'time': 1.5, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.003000000026077032}, {'time': 1.8, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.0020000000949949026}, {'time': 2.0, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.0020000000949949026}, {'time': 2.2, '366cf056-0f9b-11ed-9353-99e44f95bc32': 0.003000000026077032}]

问题定位

与python中浮点数据的表示机制有关

相关文档说明

在stackflow(https://stackoverflow.com/questions/31977319/floating-point-precision-affected-when-converting-dataframe-to-list)上 我也查到了类似的问题,大家给到核心建议是使用"round"方法。

查看python官方文档关于《浮点算术:问题和限制》(https://docs.python.org/3.8/tutorial/floatingpoint.html)的相关说明:

核心说明

核心的说明有以下几点:

  1. 浮点数在计算机硬件中表示为以 2 为底的(二进制)分数。
  2. 不幸的是,大多数十进制分数不能完全表示为二进制分数。结果是,通常,您输入的十进制浮点数仅与实际存储在机器中的二进制浮点数近似。
  3. Python 只打印机器存储的二进制近似值的真实十进制值的十进制近似值。
  4. 有趣的是,有许多不同的十进制数共享相同的最接近的近似二进制分数。
  5. 请注意,这是二进制浮点的本质:这不是 Python 中的错误,也不是您的代码中的错误。您将在所有支持硬件浮点运算的语言中看到相同的内容(尽管某些语言默认或在所有输出模式下可能不会显示差异)。
  6. 表示错误问题:指某些(实际上是大多数)十进制分数不能完全表示为二进制(以 2 为底)分数的事实。这就是为什么 Python(或 Perl、C、C++、Java、Fortran 和许多其他语言)通常不会显示您期望的确切十进制数的主要原因。 这是为什么?1/10 不能完全表示为二进制分数。今天(2000 年 11 月)几乎所有机器都使用 IEEE-754 浮点运算,并且几乎所有平台都将 Python 浮点数映射到 IEEE-754 “双精度”。754 个双精度数包含 53 位精度,因此在输入时,计算机会努力将 0.1 转换为最接近的小数,其形式为J /2** N,其中J是正好包含 53 位的整数。

解决方案

  • 使用format格式化输出(或f-string )
  • 保存成分数的形式
#应用示意
format(math.pi, '.12g') #'3.14159265359'

#转化成分数
x=3.14159
x.as_integer_ratio()#转换成对应的分数表示
"""
(3537115888337719, 1125899906842624)#分子/分母
"""
x=0.25
x.as_integer_ratio()
"""
(1, 4)##分子/分母
"""


from decimal import Decimal
Decimal.from_float(0.1)
"""
Decimal('0.1000000000000000055511151231257827021181583404541015625')
"""
format(Decimal.from_float(0.1),".17")#'0.10000000000000001'

from fractions import Fraction
Fraction.from_float(0.1)
(0.1).as_integer_ratio()
"""
(3602879701896397, 36028797018963968)
"""

回归上述问题

但针对背景中提到的问题,我们可以看到输出的数据都是数值型,通过上述格式化方案或分数不足以解决问题。最红通过定位发现dataframe.values.tolist()中才会出现该问题。

在dataframe结果时我们我们已经明确指定小数位数,但是通过dataframe.values.tolist()方法或者dataframe.values时会出现精度溢出的问题。 分析: 基于python中float型数据的保存机制,精度溢出是随机的,完全依赖于当前运行环境。 精度溢出以外的数据对我们的影响有两种情况:

第一种:通过保留小数(round),我们可以达到目标预期;

第二种:通过保留小数后的数值与我们的实际预期差异较大,这种可能性非常小。

解决方案: 本文给出一种结合业务场景的"round"方法,假定我们保存的结果数据可能有3位小数也可能包含6位小数,此时我们通过使用np.allclose()函数 比较误差,以此确定保留几位小数。以此减少精度溢出问题对我们的影响。

#代码优化形式
def dataframe_to_json(raw_data):
    """将dataframe转换成json的形式"""
    data = raw_data.values  # .tolist()
    cols = raw_data.columns  # .tolist()
    try:
        data1 = np.round(data, 3)
        data2 = np.round(data, 6)
        data = data1 if np.allclose(data1, data) else data2
    except Exception:
        pass
    output = list(map(lambda x: dict(zip(cols, x)), data))
    return output
posted @ 2023-04-11 09:38  LgRun  阅读(171)  评论(0编辑  收藏  举报