【发布时间】:2015-07-16 09:37:52
【问题描述】:
从时间戳 (TDateTime) 计算 32 位 ID 时,我收到一个奇怪的错误。在某些情况下,不同处理器上的值是不同的。
fTimeStamp 字段是从 SQLite 数据库中的 Double 字段中读取的。
下面的代码从fTimeStamp 计算一个 32 位 ID (lIntStamp),但在某些(罕见的)情况下,即使源数据库文件完全相同(即存储在文件中的 Double 相同),不同计算机上的值也会不同.
...
fTimeStamp: TDateTime
...
var
lIntStamp: Int64;
begin
lIntStamp := Round(fTimeStamp * 864000); //86400=24*60*60*10=steps of 1/10th second
lIntStamp := lIntStamp and $FFFFFFFF;
...
end;
TDateTime(Double)的精度是15位,但是代码中的取整值只用了11位,所以应该有足够的信息来正确取整。
举一个值的例子:在特定的测试运行中,lIntStamp 的值在 Windows 计算机上为 $74AE699B,在 iPad 上为 $74AE699A(= 只有最后一位不同)。
Round函数在各个平台上实现的不同吗?
PS。我们目前的目标平台是 Windows、MacOS 和 iOS。
编辑:
我基于cmets做了一个小测试程序:
var d: Double;
id: int64 absolute d;
lDouble: Double;
begin
id := $40E4863E234B78FC;
lDouble := d*864000;
Label1.text := inttostr(Round(d*864000))+' '+floattostr(lDouble)+' '+inttostr(Round(lDouble));
end;
Windows 上的输出是:
36317325723 36317325722.5 36317325722
在 iPad 上的输出是:
36317325722 36317325722.5 36317325722
不同之处在于第一个数字,它显示了中间计算的舍入,所以出现问题是因为 x86 的内部精度(80 位)比 ARM(64 位)更高。
【问题讨论】:
-
你能捕捉到 fTimeStamp 变量的二进制内容吗?请注意,舍入可能会受到 fpu 的舍入模式的影响。
-
var d: Double; id: int64 absolute d; begin id := $40E4863E234B78FC; WriteLn(Round(d*24*60*60*10)); WriteLn(Round(d*864000)); WriteLn(Trunc(d*24*60*60*10)); WriteLn(Trunc(d*864000)); end.产生36317325723 36317325723 36317325722 36317325722。 x64 编译器为 Round 和 Trunc 提供相同的值(以 22 结尾)。 -
@DavidHeffernan,或者 80 位中间值(由乘以 864000 或 24*60*60*10 引起)被舍入。
-
汉斯,您的最后一次更新意味着在舍入之前将计算存储在 Double 中是一种解决方案。
-
@Hans 我认为这行不通。您需要将舍入精确地复制到 80 位中间值。添加更高的精度并不总是有帮助,因为 80 位精度并不精确。您需要 same 精度,而不是 more 精度。我想你会发现很难在 ARM 上复制 80 位。恐怕你面前有一个相当棘手的任务。你不能摆脱向后兼容的束缚吗?
标签: sqlite delphi firemonkey delphi-xe7