将Python浮点数转换为字符串而不失精度
我正在维护一个Python脚本,这个脚本用xlrd
从Excel表格中获取数据,然后对这些数据进行各种处理。表格中的一些单元格包含高精度的数字,这些数字必须保持精确。当我从这些单元格中获取值时,xlrd
会给我一个像0.38288746115497402这样的float
类型的数字。
不过,后来我需要把这个值转成字符串。在代码中使用str(value)
或者unicode(value)
时,得到的结果是“0.382887461155”。根据要求,这样是不可以的;我需要保持数字的精度。
我尝试过几种方法,但都没有成功。第一种是使用字符串格式化的方式:
data = "%.40s" % (value)
data2 = "%.40r" % (value)
但是这两种方法都产生了相同的四舍五入结果,“0.382887461155”。
在网上和StackOverflow上搜索类似问题时,很多人建议使用Decimal
类。但我不能改变数据的格式(除非有人知道如何让xlrd
返回Decimal类型)。当我尝试这样做时:
data = Decimal(value)
我得到了一个TypeError: Cannot convert float to Decimal. First convert the float to a string.
的错误。但显然我不能把它转换成字符串,否则就会失去精度。
所以,我欢迎任何建议——即使是一些很奇怪或者不太优雅的解决方案也可以。我对Python不是很熟悉(我更擅长Java/C#),所以如果我有任何基本的误解,请随时纠正我。
补充说明:我使用的是Python 2.6.4。我认为没有任何正式的要求阻止我更改版本;只要不影响其他代码就可以。
5 个回答
编辑:我错了。我把这个回答留在这里是为了让其他人的讨论更有意义,但这并不正确。请查看上面John Machin的回答。谢谢大家 =)。
如果上面的回答有效,那就太好了——这可以帮你省去很多麻烦的操作。不过,至少在我的系统上,它们并不奏效。你可以用以下方法检查一下:
import sys
print( "%.30f" % sys.float_info.epsilon )
那个数字是你系统能区分的最小浮点数。任何比它小的数在你进行运算时可能会随机加到或减去任何浮点数。 这意味着,至少在我的Python环境中,精度在xlrd
的内部处理过程中丢失了,似乎没有办法不修改它就能解决这个问题。这很奇怪;我本以为这种情况之前就会出现,但显然没有!
你可能可以修改你本地的xlrd
安装,来改变float
的转换方式。打开site-packages\xlrd\sheet.py
,找到第1099行:
...
elif rc == XL_INTEGER:
rowx, colx, cell_attr, d = local_unpack('<HH3sH', data)
self_put_number_cell(rowx, colx, float(d), self.fixed_BIFF2_xfindex(cell_attr, rowx, colx))
...
注意那个float
的转换——你可以尝试把它改成decimal.Decimal
,看看会发生什么。
你可以使用 repr()
这个函数把数字转换成字符串,这样就不会丢失精度。然后再把这个字符串转换成 Decimal 类型:
>>> from decimal import Decimal
>>> f = 0.38288746115497402
>>> d = Decimal(repr(f))
>>> print d
0.38288746115497402
我是xlrd的作者。其他回答和评论中有很多混淆的地方,我在这里做个澄清。
@katriealex: “xlrd内部丢失了精度”——这完全没有根据,也不是真的。xlrd会准确地再现存储在XLS文件中的64位浮点数。
@katriealex: “可能可以修改你本地的xlrd安装来改变浮点数的转换”——我不知道你为什么想这么做;把一个16位整数转换成浮点数不会丢失任何精度!!!而且那段代码只在读取Excel 2.X文件时使用(那些文件有一个整数类型的单元格记录)。提问者并没有说明他在读取这种古老的文件。
@jloubert: 你一定搞错了。"%.40r" % a_float
只是获取和repr(a_float)
相同结果的一种复杂方式。
@大家: 你不需要把浮点数转换成十进制数来保持精度。repr()
函数的主要目的就是保证以下内容:
float(repr(a_float)) == a_float
Python 2.X(X <= 6)中的repr会提供17位小数的精度,这是保证能再现原始值的。而后来的Python版本(2.7, 3.1)会给出能再现原始值的最少小数位数。
Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on win32
>>> f = 0.38288746115497402
>>> repr(f)
'0.38288746115497402'
>>> float(repr(f)) == f
True
Python 2.7 (r27:82525, Jul 4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32
>>> f = 0.38288746115497402
>>> repr(f)
'0.382887461154974'
>>> float(repr(f)) == f
True
所以,总结一下,如果你想要一个能保持浮点对象所有精度的字符串,使用preserved = repr(the_float_object)
... 然后可以通过float(preserved)
来恢复这个值。就是这么简单。没有必要使用decimal
模块。