首页 > 解决方案 > 为什么从红宝石中的浮点数(以毫秒精度)转换为时间会导致红宝石中的精度损失不一致?

问题描述

当使用该Time.at函数转换表示自纪元以来的秒数的浮点数(以毫秒精度)然后提取微秒值时,结果是精度损失不一致。

例如,使用自 1970 年以来每年的 1 月 1 日:

ruby -e 'require("time");p (1..50).map {|offset| Time.at(Time.parse("#{1970+offset}-01-01T00:00:0.123}").to_r.numerator/1000.0).usec}'
[122999, 123000, 122999, 122999, 122999, 122999, 122999, 122999, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 123000, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999, 122999]

基于https://bugs.ruby-lang.org/issues/7829中的讨论- 我预计亚秒值总是会损失精度(即总是是122999)。

标签: ruby

解决方案


TL;DR(来自Time#to_f

请注意,IEEE 754 双精度不足以表示自纪元以来的确切纳秒数。


关于您的期望:

我本来预计亚秒值总是会损失精度(即始终是 122999)。

损失≠少。“精度损失”并不意味着结果总是小于实际值。例如,由于精度损失,添加0.10.2会导致略高于 的值 0.3

0.1 + 0.2 #=> 0.30000000000000004

回到你的时间价值。让我们看看前两年,1971 年和 1972 年:

Time.parse('1971-01-01T00:00:0.123').to_f #=> 31532400.123
Time.parse('1972-01-01T00:00:0.123').to_f #=> 63068400.123

Time.at(31532400.123).usec #=> 122999
Time.at(63068400.123).usec #=> 123000

发生这种情况是因为浮点不准确。浮点数的实际值为:

31532400.1229999996721744537353515625
63068400.123000003397464752197265625
#        ^^^^^^
#         usec

调用nsec显示以上两个实际上都不准确:(一个略低于,另一个略高于)

Time.at(31532400.123).nsec #=> 122999999
Time.at(63068400.123).nsec #=> 123000003

要获得精确的值,您必须提供精确的参数:

Time.at(31532400123.quo(1000)).usec #=> 12300

# or

Time.at(31532400, 123000).usec #=> 12300

推荐阅读