Delphi Roundto和FormatFloat不一致性

Delphi Roundto和FormatFloat不一致性,delphi,rounding-error,Delphi,Rounding Error,我在Delphi 2010中遇到了一个舍入的奇怪现象,有些数字在舍入中向下舍入,但在格式浮点中向上舍入 我完全知道十进制数的二进制表示有时会给出误导性的结果,但在这种情况下,我希望formatfloat和roundto给出相同的结果 我也看到了一些建议,这是“货币”应该用来做的事情,但正如你在下面看到的,货币和双精度给出了相同的结果 program testrounding; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils,Math

我在Delphi 2010中遇到了一个舍入的奇怪现象,有些数字在舍入中向下舍入,但在格式浮点中向上舍入

我完全知道十进制数的二进制表示有时会给出误导性的结果,但在这种情况下,我希望formatfloat和roundto给出相同的结果

我也看到了一些建议,这是“货币”应该用来做的事情,但正如你在下面看到的,货币和双精度给出了相同的结果

program testrounding;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,Math;

var d:Double;
    c:Currency;
begin
  d:=534.50;
  c:=534.50;
  writeln('Format: '  +formatfloat('0',d));
  writeln('Roundto: '+formatfloat('0',roundto(d,0)));
  writeln('C Format: '  +formatfloat('0',c));
  writeln('C Roundto: '+formatfloat('0',roundto(c,0)));
  readln;
end.
结果如下:

Format: 535
Roundto: 534
C Format: 535
C Roundto: 534

我已经看过了,但建议的补救措施似乎并不适用。

在这种情况下,534.5的值正好可以用双精度表示

查看源代码,发现如果最后一个挂起的数字是5或更多,则
FormatFloat
函数向上舍入


使用银行家四舍五入,在本例中四舍五入到最接近的偶数(534)

首先,我们可以从问题中删除
Currency
,因为您使用的两个函数没有
Currency
重载。该值将转换为IEEE754浮点值,然后遵循与
Double
代码相同的路径

首先让我们看一下
RoundTo
。使用调试器或附加的
Writeln
可以快速检查
RoundTo(d,0)=534
。为什么呢

嗯,
RoundTo的

使用“银行家舍入法”将浮点值舍入到指定的数字或十的幂

实际上,在
RoundTo
的实现中,我们看到在恢复到其原始值之前,舍入模式临时切换到
TRoundingMode.rmNearest
。舍入模式仅在值正好位于两个整数的一半时适用。这正是我们这里的情况

因此,银行家四舍五入法适用。这意味着,当值正好位于两个整数之间的一半时,舍入算法会选择相邻的偶数整数

因此,
RoundTo(534.5,0)=534
是有意义的,同样,您可以检查
RoundTo(535.5,0)=536

理解
FormatFloat
是完全不同的事情。坦率地说,它的行为有些不透明。它对不同平台的不同代码执行特殊舍入。例如,它是32位Windows上的汇编程序,但在64位Windows上是Pascal。总体方法似乎是取浮点值的尾数,将其转换为整数,将其转换为文本数字,然后根据这些文本数字执行舍入。执行舍入时不考虑当前舍入模式,算法似乎实现了舍入。然而,对于所有可能的浮点值,即使是这样,也不能可靠地实现。它适用于您的值,但对于尾数中位数较多的值,算法会崩溃

事实上,众所周知,用于在浮点值和文本之间转换的DelphiRTL例程从根本上被设计破坏了。Delphi RTL中没有能够正确地从文本转换为浮点或从浮点转换为文本的例程。事实上,我最近实现了自己的转换例程,这些例程基于其他语言运行时使用的现有开放源代码正确地实现了这一点。总有一天我会发布这段代码供其他人使用


我不确定你的确切需求是什么,但如果你希望对舍入进行一些控制,那么如果你负责舍入,你可以这样做。虽然
RoundTo
始终使用银行取整,但您可以改为使用当前取整模式的
Round
。这将允许您使用您选择的舍入算法执行舍入(通过调用
SetRoundMode
),然后您可以将舍入值转换为文本。这是关键。将值保留在算术类型中,执行舍入,并仅在应用正确舍入后的最后一刻转换为文本。

此问题与您链接的问题之间存在巨大差异。这里的数字完全可以表示。至于为什么会有差异,我们需要深入研究rtl源代码。文档中没有提供任何内容。FormatFloat和RoundFloat都不能与Extended一起使用,因此原始的float类型应该无关紧要。顺便说一句,我想知道Delphi2010编译器是如何找到System.Sysutils的。XE2中引入了单元范围名称。0.5有不同的舍入策略。一种策略是向上舍入(或向下舍入)。另一种方法是四舍五入到最接近的偶数整数。可能一个函数使用一种策略,另一个函数使用另一种策略。统计学家和会计师更喜欢最后一种(取整)选择,但这仅仅是因为统计错误倾向于抵消而不是强化,但这不会使其他策略出错。仔细想想,535和354一样错误(与真实值相差甚远)。在32位模式下,
FormatFloat
数字在汇编代码中计算。调试有点困难。由于上一个Delphi版本在64位模式下给出了相同的结果,因此我跟踪到了function
SysUtils.ExtToDecimal
。归根结底,如果最后一个数字是5或更多,它就会向上舍入。这确实解释了很多。这是在调查SQL Server和Delphi对同一数据的舍入之间的差异时提出的,我被这样一个事实所震惊,即在几百美元的数字中只有一个数字被舍入。。但它可能是唯一一个以0.50结尾的。