分别指定“小”和“德尔塔”是否有意义? 此外,为什么我的编译器拒绝for foo'Small use foo'Delta?

问题描述

编译器选择的“小”的演示程序

作为Ada中的定点类型的新手,让我惊讶的是,认值'Small是2的幂,小于或等于指定的增量。这是介绍问题的简短代码段:

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   type foo is delta 0.1 range 0.0..1.0;
   x : foo := foo'delta;
begin
   Put (x'Image);
   while true loop
      x := x + foo'delta;
      Put (x'Image);
   end loop;
end Main;

输出显示,“ Small实际上是小于0.1的2的最大幂,因为某些打印值出现两次:

 0.1 0.1 0.2 0.3 0.3 0.4 0.4 0.5 0.6 0.6 0.7 0.8 0.8 0.9 0.9 1.0

raised CONSTRAINT_ERROR : main.adb:9 range check Failed

解决方案:将它们指定为相同的值

如果我们真的想要0.1作为增量,我们可以这样说:

   real_delta : constant := 0.1;
   type foo is delta real_delta range 0.0..1.0
      with Small => real_delta;

问题;将两者都指定为不同的值是否有用?

如果优化是解决此差异的唯一用例,则可能是布尔属性,甚至只是警告“选择的增量不是2的幂(建议2 **-4)。是否有任何理由将两者都指定为单独的值,例如:

   type foo is delta 0.1 range 0.0..1.0
      with Small => 0.07;
   x : foo := 0.4 + 0.4;  -- equals 0.7 rather than 0.8

这似乎只会使以后遇到此问题的可怜的读者感到困惑。以下示例摘自John Barnes在Ada 2012上的《编程》,第434页的第17.5节。他没有解释为什么增量值要大得多而不是实际“小”值的倍数。

π : constant := Ada.numerics.π;
type angle is delta 0.1 range -4*π .. 4 *π;
for Angle'Small use π * 2.0**(-13);

我看到的唯一区别是,“图像现在只打印一位数的精度。那是唯一的区别吗?

此外,为什么我的编译器拒绝for foo'Small use foo'Delta

我遇到了直接在没有常量的情况下执行上述操作的代码

type foo is delta 0.1 range 0.0..1.0;
for foo'Small use foo'Delta;

但是GNAT抱怨foo在声明之后立即被冻结:

main.adb:6:04: representation item appears too late

在某些版本的Ada中该更改了吗?它应该是有效的Ada2012吗?

解决方法

免责声明:因此定点算法是一个非常特殊的主题。这是我对本主题的了解,但是我必须在这里发出警告:我在下面写的内容可能不正确。因此,对于每个阅读它的人:如果我写错了,请纠正我。

在Ada中,实型由其准确性定义。这与大多数其他通过实型实现定义实型(即硬件表示)的语言相反。选择使用精度属性代替实型定义中的表示形式与语言哲学一致:作为一个概念,精度与正确性密切相关;语言的目标。在准确性方面定义实类型也更为自然,因为您可以让编译器根据对准确性的要求来选择最佳类型(在计算机上,所有值无论如何都是近似值,因此您必须一一处理这一事实,方式)

Delta属性定义了对与基础定点类型相关的绝对误差范围(准确性)的要求(另请参见Ada 83 Rationale,section 5.1.3)。优点有两个:

  • 程序员使用要求指定数字类型,并将硬件上表示的最佳选择委托给编译器。

  • 绝对误差范围通常在数值分析中用于分析和预测算术运算对精度的影响,直接在类型定义中说明。在实现计算算法时,尤其是在使用定点类型时,数值分析(准确性和范围分析)是一个重要方面。

更新24-10-2020 :应在原始语言规范Ada 83的上下文中阅读这些前几段。此外,Ada 83语言还有第二个重要目标,似乎已经对它产生了影响使用精度定义数字实型的选择:分离原理。有关其含义的明确说明,请参见Ada 83基本原理chapter 15。但是,当开发Ada 95时,(至少对于定点类型)逻辑类型属性(如准确性)和机器表示之间的分离受到了审查,发现在实践中没有期望的有用(请参阅Ada)。 95基本原理,第G.4.2节)。因此,自Ada 95起,Delta属性的作用已被削弱,Small属性已被用来代替定点类型和操作的工作方式(例如,参见,RM G.2.3)。

作为示例,请考虑下面的示例程序。该程序定义了一个数字类型,并指定“真”值与基础表示形式之间的绝对差不得超过0.07:

type Fix is delta 0.07 range 0.0 .. 10.0;     --  0.07 is just a random value here

换句话说,当将给定的“ true”值强制转换为类型Fix时,它将获得+/- 0.07的不确定性。因此,以下程序中的三个命名常量XYZ在转换为类型Fix时变为:

X : constant := 5.6;       --  Becomes 5.6 +/- 0.07 when casted to type Fix.
Y : constant := 0.3;       --  Becomes 0.3 +/- 0.07 when casted to type Fix.
Z : constant := 2.5;       --  Becomes 2.5 +/- 0.07 when casted to type Fix.

鉴于这些不确定性,人们可以计算出一系列算术运算结果的不确定性(另请参见this关于SO的极好的答案)。程序中实际上已经证明了这一点。

更新2020年10月24日:回想起来,这似乎是不正确的,并且存在一些复杂性。程序中不确定性的计算未考虑在计算和最终分配过程中可能发生的中间和最终转换(量化)。因此,计算出的不确定性不正确且过于乐观(即应更大)。我不会删除示例程序,因为它确实提供了Delta属性的初衷。

使用Long_FloatFlt)和自定义定点类型Fix进行了三个计算。当然,使用Long_Float的计算结果也是一个近似值,但是为了演示起见,我们可以假定它是精确的。但是,由于我们为类型Fix指定了相当大的误差范围,定点计算的结果的精度非常有限。另一方面,定点值需要较少的空间(这里:每个值仅8位),算术运算不需要专用的浮点硬件。

您可以调整Small属性的事实仅是允许程序员控制由定点类型定义的集合中可用的模型编号。始终使Small表示方面等于Delta属性可能很诱人,但是使它们相等(通常)并不会改变在以下情况下需要执行一些数值(错误)分析的要求:结合使用定点数和算术运算。

更新24-10-2020 :我认为这句话仅部分正确。 Small属性的确允许程序员控制模型编号(即可以由数据类型精确表示的编号),但不仅限于此。自Ada 95起,Small属性在定点算法的工作原理(RM G.2.3)中起着重要作用,此外,关于定点算法和分析定点算法的软件的大多数文档点算法(例如,参见here)假定该类型在硬件中的实际表示是已知的;它们对对象的处理不会偏离绝对误差范围,而总是偏离定点值的表示形式。

最后,所有内容都与数值精度的交易资源(内存,浮点硬件)有关。

更新24-10-2020 :此语句还需要说明:在上下文中,不需要浮点运算来执行Ada中的定点运算。如果操作数的类型和操作的结果具有Small的特定值,则只能使用整数运算来完成定点运算,尤其是乘法和除法。这里有太多细节,但是实际上可以在GNAT本身有大量文献记录的源代码中找到一些有趣的信息,例如,exp_fixd.adb

更新24-10-2020:因此,总而言之,鉴于Ada 95的变化以及当前用于执行定点分析的最新工具,似乎没有强有力的论据为DeltaSmall选择不同的值。 Delta属性仍然表示绝对错误界限,但是它的值没有最初想象的有用。正如您已经提到的,它只是在定点数据类型(RM 3.5.10 (5)Ada.Text_IO.Fixed_IO)的I / O中用作主要用途。

main.adb

pragma Warnings (Off,"static fixed-point value is not a multiple of Small");
pragma Warnings (Off,"high bound adjusted down by delta (RM 3.5.9(13))");


with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

   type Flt is new Long_Float;
   type Fix is delta 0.07 range 0.0 .. 10.0;

   ---------
   -- Put --
   ---------

   procedure Put (Nominal,Uncertainty : Flt; Result : Fix) is

      package Fix_IO is new Fixed_IO (Fix);
      use Fix_IO;

      package Flt_IO is new Float_IO (Flt);
      use Flt_IO;

   begin
      Put ("   Result will be within     : ");
      Put (Nominal,Fore => 2,Aft => 4,Exp => 0);
      Put (" +/-");
      Put (Uncertainty,Exp => 0);
      New_Line;

      Put ("   Actual fixed-point result : ");
      Put (Result,Fore => 2);
      New_Line (2);

   end Put;

   X : constant := 5.6;
   Y : constant := 0.3;
   Z : constant := 2.5;

   D : constant Flt := Fix'Delta;

begin

   Put_Line ("Size  of fixed-point type : " & Fix'Size'Image);
   Put_Line ("Small of fixed-point type : " & Fix'Small'Image);
   New_Line;

   --  Update 24-10-2020: Uncertainty computation is too optimistic. It omits
   --                     the effect of quantization in intermediate and final
   --                     variable assignments.

   Put_Line ("X + Y = ");
   Put (Nominal     => Flt (X) + Flt (Y),Uncertainty => D + D,Result      => Fix (X) + Fix (Y));

   Put_Line ("X * Y = ");
   Put (Nominal     => Flt (X) * Flt (Y),Uncertainty => (D / X + D / Y) * X * Y,Result      => Fix (X) * Fix (Y));

   Put_Line ("X * Y + Z = ");
   Put (Nominal     => Flt (X) * Flt (Y) + Flt (Z),Uncertainty => (D / X + D / Y) * X * Y + D,Result      => Fix (X) * Fix (Y) + Fix (Z));

end Main;

输出

Size  of fixed-point type :  8
Small of fixed-point type :  6.25000000000000000E-02

X + Y = 
   Result will be within     :  5.9000 +/- 0.1400
   Actual fixed-point result :  5.81

X * Y = 
   Result will be within     :  1.6800 +/- 0.4130
   Actual fixed-point result :  1.38

X * Y + Z = 
   Result will be within     :  4.1800 +/- 0.4830
   Actual fixed-point result :  3.88