Delphi中EncodeDateTime与EncodeTime的时间部分数值不一致问题
复现代码
var fdt, fndt : Double; dt, dt2 : TDateTime; begin dt := EncodeDateTime(2023, 12, 31, // 20, 10, 5, 100); dt2 := EncodeTime(20, 10, 5, 100); fdt := Frac(dt); fndt := Frac(dt2); Memo1.Lines.Add('Equal: ' + BoolToStr(fdt = fndt, True) + CRLF + // FloatToStr(fdt) + CRLF + // FloatToStr(fndt) + CRLF + // FormatDateTime('HH:NN:SS ZZZ', fdt) + CRLF + // FormatDateTime('HH:NN:SS ZZZ', fndt) + CRLF + // '' ); Exit;
运行结果
Equal: False 0,840336805558763 0,840336805555556 20:10:05 100 20:10:05 100
疑问
二者的时间部分作为Double值不相等,但格式化后的字符串一致。按理解EncodeDateTime是EncodeDate + EncodeTime,日期部分是Trunc(Date),小数部分应该完全一致,为何出现差异?是否与时区有关?
环境:Win 11,Delphi S10,32位程序,时区:欧洲/布达佩斯(+2/+1)。
这个差异和时区无关,核心原因是Double浮点精度的累积误差:
TDateTime的存储特性:TDateTime用Double存储,整数部分是日期天数(从1899-12-30起),小数部分是当日时间占比。理论上
EncodeDateTime确实是EncodeDate + EncodeTime,但EncodeDate返回的并非绝对精确的整数——由于Double的浮点表示限制,部分日期对应的整数值会带有极微小的小数偏移(比如EncodeDate(2023,12,31)的实际值可能是45292.0加上一个极小的误差)。误差传递到时间部分:当
EncodeDate的带误差值和EncodeTime的结果相加后,这个微小误差会被保留在最终的Double值中。直接调用EncodeTime得到的是纯粹的时间占比,无额外误差。取Frac(dt)时,日期部分的误差会混入时间小数部分,导致和Frac(dt2)出现差异。FormatDateTime的一致性:
FormatDateTime处理时会按指定格式(此处精确到毫秒)进行截断或四舍五入,两个差异值在毫秒级精度下完全一致,所以格式化后的字符串相同。
你可以直接输出EncodeDate(2023,12,31)的完整浮点值,就能看到它并非严格整数,这个极小的小数尾巴就是差异的根源。
内容的提问来源于stack exchange,提问作者durumdara

