关于浮点运算

来自RAD Studio
跳转到:航行,搜索

转到使用浮点例程

使用浮点数需要了解内部代表数据的。程序员必须意识到有限精度问题:

  • 对于整数值(整数类型),必须考虑溢出的可能性。
  • 对于浮点值(单精度、双精度或扩展精度),必须考虑精度损失的可能性。

有限精度含义

浮点精度损失可能是多种原因造成的:

  • 如果将浮点文字(例如:0.1)赋给浮点变量,则浮点变量可能没有足够的位来保存所需的值,而不会引入一些错误。浮点文字可能需要非常大的数字,甚至是无限数量的位才能实现无限精度表示。
使用
  系统.系统实用程序;
变量
  X(X): 单个;
  Y(Y): 双精度;
开始
  X(X) := 0.1;
  Y(Y) := 0.1;
  Writeln公司(“X=”, X(X));
  Writeln公司(“Y=”, Y(Y));
  Writeln公司(格式('指数X:%X', [X(X).实验]));
  Writeln公司(格式('曼蒂萨Y:%x', [Y(Y).曼蒂萨]));
  读取Ln;
结束.
控制台输出:
X=1.000000001490116E-0001
Y=10000000000000E-0001
指数Y:3FB
曼蒂萨Y:19999999999A
从上面的代码中,您可以看到单个精度表示。这个双精度精度表示也有误差。为了证明这个错误,让我们使用原始指数曼蒂萨双精度精度数字。十六进制3FB美元 指数1019十进制表示。自从内部代表属于双精度数字具有偏差等于1023,则指数=1019-1023=-4。十六进制19999999999999A美元 曼蒂萨11001100110011001100110011001100110011001100110011010二进制表示。因此,Y的二进制表示为1.1001100110011001100110011001100110011001100110011010*2-40.00011001100110011001100110011001100110011001100110011010*2-4。请注意,此数字是双精度精度近似。确切地说0.1数字表示为无限循环分数0.0(0011).
然而,值得注意的是,以这种方式产生的最大误差为0.5ulps公司.
  • 如果执行浮点运算,则每个步骤(操作)都可能引入其特定错误。发生这种情况的原因是,在某些操作中,计算的结果不能完全精确地存储。例如,如果将两个数字S1位与S2位相乘(对于整型和浮点型都是如此),则结果需要S1+S2位才能达到完全精度。
操作引入的错误“数量”取决于处理器型号和操作类型。相加运算引入的误差相对较低。乘法运算会引入相对较高的误差。

重要的是要理解浮点精度损失(错误)是通过计算传播的,然而,程序员的职责是设计正确的算法。

浮点变量可以被看作是一个具有两次幂的整数变量。如果将浮点变量“强制”为极值,则会自动调整刻度。这就是为什么您可能会觉得浮点变量不能溢出。这确实是真的,但另一方面,还有其他威胁:浮点变量可能累积严重错误和/或成为规格化.

使用较大的数据类型

解决整数溢出或浮点精度下降(通常是有限精度影响)问题的最简单方法是使用同一类的数据类型(整数或浮点),但容量有所增加。也就是说,如果短Int溢出,然后您可以轻松切换到LongInt公司,固定Int国际64类似地,如果单精度浮点不能提供足够的精度,则可以切换到双精度浮点。但有两件事需要考虑:

  • 存储容量更大的数据类型可能仍然不足。
  • 具有更多存储容量的数据类型需要更多内存,并且操作中可能需要更多CPU周期。

控件设置

在32位平台上,x87 FPU控制字(CW)分配了两个位,用于指定舍入模式。请参见英特尔®64与IA-32体系结构软件开发人员手册第1卷:基本体系结构>8.1.5.3舍入控制字段对于64位程序,SSE控制寄存器MSXCSR指定舍入模式。您可以在系统。数学。设置圆形模式.

一些使用浮点变量操作的RTL函数可能会受到FPU舍入模式。基于FPU控制字的RTL例程结果更改的确切性质取决于所实现的算法。舍入将影响需要舍入以将结果适应目标类型的每个操作,例如,浮点乘法几乎总是涉及舍入。如果一个函数包含大量浮点乘法,它将受到舍入模式的强烈影响。

舍入模式有时用于实现区间算术:大致来说,使用向上舍入模式执行相同的算法,然后使用向下舍入模式重复该算法,然后查看两个结果之间的差异。这就说明了舍入和不精确带来的潜在误差。

用例

财务计算

IEEE浮点可能不适用于财务计算。这是因为精度要求通常非常严格。您应该考虑使用整数类型(基元整数或货币)或BCD类型。

这个数据。FmtBcd公司该单位为BCD操作提供支持。BCD格式有以下重要特征:每个十进制数字(基数为10位)用4位内存(半字节)编码。

以下代码显示了如何使用待定值作为变量,以便:

德尔福:

变量
  X(X): 变体;

开始
  X(X) := VarFMTBcd创建('0.1', 16 {精度}, 2 {缩放});
  Writeln公司(VarToStr变量(X(X)));

  // ...

C++语言:

#包括<Variants.hpp>
#包括<FMTBcd.hpp>

整数 _特曼(整数 argc公司, _TCHAR公司* 自动变速箱[]) {
  变体 x个 = VarFMTBcd创建("0.1", 16 /*精密度*/, 2 /*比例*/);
  打印(“%ls”, VarToStr变量(x个).c_str码());

 // ...

控制台输出:

0.1

从上面的代码中可以看出,在BCD变量的帮助下,从文本格式到数字格式的转换是完美的。

您可以使用货币类型。这个货币类型本质上是一个按10000缩放的整数(该值允许精确除以10)。您可以将四个十进制数字存储在货币变量,超出此限制的任何内容都将四舍五入。

德尔福:

变量
  X(X), Y(Y): 货币;

开始
  X(X) := 0.1;
  Y(Y) := 0.00001;

  Writeln公司(X(X));
  Writeln公司(Y(Y));

  // ...

C++语言:

#包括<System.hpp>

整数 _特曼(整数 argc公司, _TCHAR公司* 自动变速箱[]) {
  货币 x个, ;
  x个 = 0.1;
   = 0.00001;

  打印(“%2.14E”\n个", (双重的)x个);
  打印(“%2.14E”\n个", (双重的));

  // ...

控制台输出:

1.0000000000000E-0001
0.0000000000000 E+0000

您可以看到的C++实现货币在里面$BDS\include\rtl\syscurr。小时.

这个货币区间为[-92233720365477.5807;922337203685477.5807]。

物理(科学)计算/模拟

通常,科学计算需要大量计算,提高浮点精度可能并不可取。扩展不太支持精确操作(请参阅Delphi for x64)。

如果使用SSE,则必须记住,SSE寄存器可以保存两个双精度变量或四个单精度变量。因此,您可以并行执行比双精度操作更多的单精度操作。

一种非常有趣且有用的方法如下:使用单精度浮点,但定期减少累积误差。许多应用程序可以容忍较小的精度损失;以某种方式消除偏差是非常重要的。这种实现方式的一个示例是使用四元数的3D空间旋转。请参见基于物理的建模>刚体动力学(ONLINE SIGGRAPH 2001课程注释).

数字信号处理

通常,所有DSP变量都被错误“污染”了。您应该考虑数据精度和计算工作量之间的最佳权衡。

一般结论

“你看到的不是你得到的”

用源代码编写的浮点数,十进制数字和浮点数显示在屏幕上可能与内存中的内容不同。不要假设您在控制台上看到的内容正好代表内存中的内容。十进制到二进制的转换(以及反向转换)不可能在任何情况下都能完美完成。

使用积分,业务连续性数据,或货币变量以避免IEEE浮点表示错误。

了解数据流

在Delphi中单个精度浮点表达式始终隐式存储为扩展在x86上。

默认情况下,所有x64算术运算和表达式只涉及单个精度浮点值通过将中间结果存储为双精度精度值。因此,这些操作比显式双精度操作数(编译的代码转换单个值到双精度每个操作)。如果执行速度是主要关注点,则可以使用{$EXCESSPRECISION关闭}指令禁止使用中间双精度值。否则为默认指令({$EXCESSPRECISION开})建议提高结果值的精度。这个{$EXCESSPRECISION关闭}指令仅对x64目标CPU有效。

在C++中,浮点文字可以表示单精度浮点或双精度浮点:它取决于(f)后缀。如果(f)在C++中附加到浮点文字,然后编译器选择单精度。要了解中间值的精度,请参阅ISO发布的标准。

浮点运算可能不具有关联性

由于每个操作符都会产生错误,因此执行计算的顺序非常重要。

请参见CERT C安全编码标准,建议FLP01-C.

浮点异常

浮点运算可能会导致以下几种错误情况浮点溢出,除以零,非规范化值,生成NaN(纳米),并执行其他无效的浮点操作通常,这种情况会导致浮点异常. The系统。数学该装置提供:

不同的浮点硬件提供了不同的方法来控制浮点异常:

  • 在英特尔32位处理器上,这是FPU控制字。
  • 在英特尔64位处理器上,这是SSE控制字。
  • 我们不支持ARM体系结构上的浮点异常。因此,我们总是屏蔽ARM体系结构上的所有浮点异常。

另请参见