如何防止csv.DictWriter()或writerow()四舍五入我的浮点数?

12 投票
3 回答
10396 浏览
提问于 2025-04-17 08:06

我有一个字典,想把它写入一个csv文件,但字典里的浮点数在写入文件时被四舍五入了。我希望能保留最大的精度。

请问四舍五入是在哪里发生的,我该怎么防止它?

我做了什么

我参考了这里的DictWriter示例,并且我在Mac上运行的是Python 2.6.1(10.6 - 雪豹)。


# my import statements
import sys
import csv

这是我的字典(d)包含的内容:

>>> d = runtime.__dict__
>>> d
{'time_final': 1323494016.8556759,
'time_init': 1323493818.0042379,
'time_lapsed': 198.85143804550171}

这些值确实是浮点数:

>>> type(runtime.time_init)
<type 'float'>

然后我设置了写入器,并写入了表头和数值:

f = open(log_filename,'w')
fieldnames = ('time_init', 'time_final', 'time_lapsed')
myWriter = csv.DictWriter(f, fieldnames=fieldnames)
headers = dict( (n,n) for n in fieldnames )
myWriter.writerow(headers)
myWriter.writerow(d)
f.close()

但是当我查看输出文件时,发现数字被四舍五入了(也就是浮点数):

time_init,time_final,time_lapsed
1323493818.0,1323494016.86,198.851438046

< EOF >

3 个回答

1

这个方法可以用,但可能不是最好的或者最有效的方式:

>>> f = StringIO()
>>> w = csv.DictWriter(f,fieldnames=headers)
>>> w.writerow(dict((k,"%f"%d[k]) for k in d.keys()))
>>> f.getvalue()
'1323493818.004238,1323494016.855676,198.851438\r\n'
2

这是一个众所周知的bug^H^H^H特性。根据文档

"""... 值为None会被写成空字符串。[省略] 所有其他非字符串数据在写入之前都会用str()转换成字符串。"""

不要依赖默认的转换方式。对于浮点数,使用repr()unicode对象需要特别处理;请查看手册。检查一下文件的接收方是否能接受datetime.x对象的默认格式,其中x可以是(datetime, date, time, timedelta)。

更新

对于浮点对象,"%f" % value并不是repr(value)的好替代品。判断标准是文件的接收方是否能重现原始的浮点对象。repr(value)可以保证这一点,而"%f" % value则不能。

# Python 2.6.6
>>> nums = [1323494016.855676, 1323493818.004238, 198.8514380455017, 1.0 / 3]
>>> for v in nums:
...     rv = repr(v)
...     fv = "%f" % v
...     sv = str(v)
...     print rv, float(rv) == v, fv, float(fv) == v, sv, float(sv) == v
...
1323494016.8556759 True 1323494016.855676 True 1323494016.86 False
1323493818.0042379 True 1323493818.004238 True 1323493818.0 False
198.85143804550171 True 198.851438 False 198.851438046 False
0.33333333333333331 True 0.333333 False 0.333333333333 False

注意,在上面的内容中,通过检查生成的字符串,似乎没有一个%f的情况是有效的。在2.7之前,Python的repr总是使用17位有效数字。在2.7中,这个规则改为使用最少的数字,以确保float(repr(v)) == v。这个差异并不是一个舍入错误。

# Python 2.7 output
1323494016.855676 True 1323494016.855676 True 1323494016.86 False
1323493818.004238 True 1323493818.004238 True 1323493818.0 False
198.8514380455017 True 198.851438 False 198.851438046 False
0.3333333333333333 True 0.333333 False 0.333333333333 False

请注意,上面第一列中改进后的repr()结果。

更新2,回应评论:“谢谢你提供的关于Python 2.7的信息。不幸的是,我只能使用2.6.2(在无法升级的目标机器上运行)。但我会记住这些信息,以便将来使用。”

这没关系。float('0.3333333333333333') == float('0.33333333333333331')在所有版本的Python中都会返回True。这意味着你可以在2.7上写文件,而在2.6上读取,反之亦然。repr(a_float_object)生成的内容在准确性上没有变化。

8

看起来 csv 模块使用的是 float.__str__ 而不是 float.__repr__

>>> print repr(1323494016.855676)
1323494016.855676
>>> print str(1323494016.855676)
1323494016.86

csv 的源代码 来看,这似乎是一个固定的行为。解决这个问题的方法是,在 csv 处理之前,把所有的浮点数值转换成它们的表示形式。可以使用类似这样的代码:d = dict((k, repr(v)) for k, v in d.items())

这里有一个详细的例子:

import sys, csv

d = {'time_final': 1323494016.8556759,
     'time_init': 1323493818.0042379,
     'time_lapsed': 198.85143804550171
}

d = dict((k, repr(v)) for k, v in d.items())

fieldnames = ('time_init', 'time_final', 'time_lapsed')
myWriter = csv.DictWriter(sys.stdout, fieldnames=fieldnames)
headers = dict( (n,n) for n in fieldnames )
myWriter.writerow(headers)
myWriter.writerow(d)

这段代码会产生以下输出:

time_init,time_final,time_lapsed
1323493818.0042379,1323494016.8556759,198.85143804550171

一种更精细的方法是只替换浮点数:

d = dict((k, (repr(v) if isinstance(v, float) else str(v))) for k, v in d.items())

注意,我刚刚为 Py2.7.3 修复了这个问题,所以将来应该不会再有这个问题了。请查看 http://hg.python.org/cpython/rev/bf7329190ca6

撰写回答