精度溢出問題
背景
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)的相關說明:
核心說明
核心的說明有以下幾點:
- 浮點數在計算機硬件中表示為以 2 為底的(二進制)分數。
- 不幸的是,大多數十進制分數不能完全表示為二進制分數。結果是,通常,您輸入的十進制浮點數僅與實際存儲在機器中的二進制浮點數近似。
- Python 只打印機器存儲的二進制近似值的真實十進制值的十進制近似值。
- 有趣的是,有許多不同的十進制數共享相同的最接近的近似二進制分數。
- 請注意,這是二進制浮點的本質:這不是 Python 中的錯誤,也不是您的代碼中的錯誤。您將在所有支持硬件浮點運算的語言中看到相同的內容(盡管某些語言默認或在所有輸出模式下可能不會顯示差異)。
- 表示錯誤問題:指某些(實際上是大多數)十進制分數不能完全表示為二進制(以 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

浙公網安備 33010602011771號