matplotlib 由于IEEE 754浮点精度,刻度标签以不同的精度显示[重复]

t1qtbnec  于 2023-11-22  发布在  其他
关注(0)|答案(2)|浏览(97)

此问题在此处已有答案

Different precision on matplotlib axis(2个答案)
Specify format of floats for tick labels(5个答案)
Is floating point math broken?(34个回答)
10天前关闭。
此帖子已于8天前编辑并提交审核,未能重新打开帖子:
原始关闭原因未解决
举例来说:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(0.0,1.2,0.2)
y = np.arange(0.0,1.2,0.2)

labels = np.arange(0.0,1.2,0.2)

plt.plot(x, y)
plt.xticks(x, labels)
plt.show()

字符串


的数据
我不得不使用np.around(np.arange(0.0, 1.2, 0.2),1)来避免它,但是如果我只运行np.arange(0.0,1.2,0.2),它会得到:array([0. , 0.2, 0.4, 0.6, 0.8, 1. ]),为什么它不同呢?
另外,y轴没有使用0.60...01作为标签,这也很奇怪。
这个问题是由于IEEE 754浮点精度,我认为它应该有一个很好的解决方案,以四舍五入小数。

fcy6dtqo

fcy6dtqo1#

在浮点运算中添加额外的尾随零会导致某些小数不能被精确表示。请阅读this answer沿着其引用。
对于您手头的问题,您可以像这样重新格式化标签

plt.xticks(x, [f"{l:.1f}" for l in labels])

字符串
其中.1表示一个有效的十进制数字。

3zwjbxry

3zwjbxry2#

表示0.6的相同浮点数表示匹配整个区间的真实的数。因此,从0.599999999993到0.600000000000003的所有真实的数共享相同的float 64表示。
试试看:

import struct
struct.pack('d', 0.59999999999999992)
struct.pack('d', 0.59999999999999993)
struct.pack('d', 0.59999999999999994)
struct.pack('d', 0.59999999999999995)
struct.pack('d', 0.59999999999999996)
struct.pack('d', 0.59999999999999997)
struct.pack('d', 0.59999999999999998)
struct.pack('d', 0.59999999999999999)
struct.pack('d', 0.60000000000000000)
struct.pack('d', 0.60000000000000001)
struct.pack('d', 0.60000000000000002)
struct.pack('d', 0.60000000000000003)
struct.pack('d', 0.60000000000000004)

字符串
正如你所看到的,除了第一个和最后一个数字,所有的数字都有相同的表示法。
但这并不是唯一的问题。因为,表示0.59993和0.6003之间的任何真实的的float 64对象,由python表示为该区间的“最圆”数。即,0.6。这就是为什么当您在python解释器中键入0.6时,它不会回复0.599999999999993或0.5999999999999999。(或者,这是测试struct的最简单方法-但我想介绍struct-,为什么当你输入0.59999999999994时,python回复0.6,但是当你输入0.5999999999999992时,它会显示0.5999999999999999)
问题是0.2都没有精确的表示。
所有从0.1999999999999998到0.20000000000000000002的真实的数都有相同的表示法,而这个表示法只是0.2000000000000001110223024625156540423631668090820的精确表示法.
我知道这是因为:

import struct
b=struct.pack('d', 0.2)
x=struct.unpack('l', b)[0]
exponent=(x>>52)&(2**11-1) # 1020 aka -3
mantissa=x&(2**52-1) # 2702159776422298
mantissa+=2**52 # Add the implicit 1 of float64
# Check, mantissa/2**52*2**-3 should be ~0.2
mantissa/2**52*2**(exponent-1023)  # 0.2
# To know the rest of the digits that python float64 can't show, 
# I take advantage of the infinite range of integers of python, and compute
# that times 10**50
# using exact integer operations
10**50*mantissa//(2**(52+1023-exponent))
# 20000000000000001110223024625156540423631668090820



现在,我把这个数字乘以3,你得到0.600000000000000003330669073875469621270895004272460.
大于0.6000000000000003
换句话说,0.2*3和0.6并不具有相同的float 64表示。
现在,当打印一个numpy数组时,它有点舍入。

np.array([1.234567890123])


array([1.23456789])


这只是numpy的一个显示选项(顺便说一句,可以用set_printoptions调整)。__repr__方法的工作方式。
你可以查一下

np.array([1.234567890123])[0]


1.234567890123


所以你在打印范围时没有看到数值错误。
所有的数字都在那里。只是numpy数组的__repr__没有打印出来。
0.6也一样

np.arange(0,1.2,0.2)
#array([0. , 0.2, 0.4, 0.6, 0.8, 1. ])
np.arange(0,1.2,0.2)[3]
#0.6000000000000001


至于如何避免:

  • 你可以做你所做的。将数字四舍五入一点。这将把它变成0.6(同样,不是一个精确的数字。但至少一个数字的float 64表示与0.6相同,因此将被打印为"0.6"
  • 什么都不做。然后删除xticks规范。您期望的行为已经是默认行为了
  • 如果问题是,由于某种原因,您的机器上的默认行为不同(某些内容的分辨率不同),并且不是所有的刻度都被打印出来,请使用xticks强制使用刻度位置,但不要设置标签,并让默认格式器选择如何打印刻度(因此选择打印哪些刻度,而不是如何打印)plt.xticks(x)
  • 相反,如果不是打印哪些刻度,而是打印方式使您对默认行为感到困扰,则可以设置您喜欢的格式化程序

import matplotlib.ticker as tkplt.gca().xaxis.set_major_formatter(tk.FormatStrFormatter('%.2f'))
我自愿选择使用2位小数来查看与默认值的差异。

  • 当然,你可以同时做这两件事:用单参数xticks选择在哪里打印标签,用formatter选择如何打印标签。
  • 最后,正如我在输入这个答案时已经建议的那样,如果您需要使用2-arguments xticks来修复tick及其标签,(但我认为应该避免这样做,因为这是重做格式化程序的工作。我只在需要一些特殊标签时才这样做。例如xticks(x, ['zero', '1/5', '40%', '3/5', '80%', 'full'])),然后传递显式字符串作为标签(如果仍然不能自己选择如何打印传递的非字符串对象,那么重做格式化程序的工作有什么意义呢?)

plt.xticks(x, [f'{t:.2f}' for t in x])

相关问题