将Python浮点数转换为字符串而不失精度

31 投票
5 回答
21233 浏览
提问于 2025-04-16 02:46

我正在维护一个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 个回答

1

编辑:我错了。我把这个回答留在这里是为了让其他人的讨论更有意义,但这并不正确。请查看上面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,看看会发生什么。

4

你可以使用 repr() 这个函数把数字转换成字符串,这样就不会丢失精度。然后再把这个字符串转换成 Decimal 类型:

>>> from decimal import Decimal
>>> f = 0.38288746115497402
>>> d = Decimal(repr(f))
>>> print d
0.38288746115497402
56

我是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模块。

撰写回答