【问题标题】:Does it ever make sense to specify 'Small and 'Delta separately?分别指定“Small”和“Delta”是否有意义?
【发布时间】:2020-10-22 10:07:16
【问题描述】:

编译器选择的“Small”演示器

作为 Ada 中的定点类型的新手,听到默认值 'Small 是 2 的幂,小于或等于指定的增量,我感到很惊讶。这里有一个简短的sn-p来介绍这个问题:

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 中的 Programming,第 17.5 节第 434 页。他没有解释为什么 delta 是一个大得多的值,而不是实际使用的“Small”的倍数。

π : 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 吗?

【问题讨论】:

  • 表示子句被拒绝,因为您请求编译器将值foo'Delta“分配”给foo'Small。结果,编译器将冻结类型foo(包括类型的小)以计算(固定)foo'Delta 的值,但是一旦确定了foo'Delta 的值,就不能再分配因为foo 已被冻结。
  • 我想不出任何理由有不同的 small 和 delta。需要同时指定两者都很烦人,Barnes 的例子很奇怪

标签: ada fixed-point


【解决方案1】:

免责声明:所以定点算术是一个相当特殊的话题。这是我对这个话题的理解,但我必须在这里发出警告:我在下面写的内容可能不正确。所以对于所有阅读它的人:如果我错了,请纠正我。

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

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

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

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

2020 年 10 月 24 日更新:这些前几段应在原始语言规范 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

换句话说,当给定的“真”值转换为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_Float (Flt) 和自定义定点类型Fix 进行了三个计算。当然,使用Long_Float 的计算结果也是一个近似值,但为了演示,我们可以假设它是精确的。然而,定点计算的结果具有(非常)有限的准确性,因为我们为类型Fix 指定了一个相当大的误差范围。另一方面,定点值需要更少的空间(这里:每个值只需 8 位),并且算术运算不需要专门的浮点硬件。

您可以调整Small 属性的事实只是为了允许程序员控制定点类型定义的集合中可用的型号。总是使Small 表示方面等于Delta 属性可能很诱人,但使它们相等不会(通常)改变使用定点时需要执行一些数值(误差)分析的要求数字和算术运算。

2020 年 10 月 24 日更新:我认为这个陈述只是部分正确。 Small 属性确实允许程序员控制型号(即可以由数据类型精确表示的数字),但不仅如此。从 Ada 95 开始,Small 属性在定点算术应该如何工作 (RM G.2.3) 以及大多数关于定点算术和分析定点算法的软件的文档中发挥着重要作用(参见,例如here) 假设硬件中类型的实际表示是已知的;他们对主题的处理并没有偏离绝对误差界限,而是总是偏离定点值的表示。

归根结底,这一切都是关于以数值精度交易资源(内存、浮点硬件)。

2020 年 10 月 24 日更新:此声明还需要备注:在 Ada 中执行定点运算不需要浮点运算取决于上下文。如果操作数的类型和操作的结果对于Small 具有特定值,则可以仅使用整数运算来完成定点运算,特别是乘法和除法。在这里放太多细节,但实际上可以在 GNAT 本身的有据可查的源代码中找到一些有趣的信息,例如,请参阅exp_fixd.adb

2020 年 10 月 24 日更新:总而言之,鉴于 Ada 95 的变化以及执行定点分析工具的当前最新技术,似乎没有强有力的论据以不同方式选择 DeltaSmall 的值。 Delta 属性仍然代表绝对误差界限,但它的值并不像最初想象的那么有用。正如您已经提到的,它的唯一主要用途似乎是在定点数据类型(RM 3.5.10 (5)Ada.Text_IO.Fixed_IO)的 I/O 中。

ma​​in.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, Fore => 2, Aft => 4, 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

【讨论】:

  • 但错误实际上不是由 'Small 限制,而不是由 'Delta 限制吗?在您的代码中替换它会给出较小的界限但正确,尽管我无法证明没有失败案例。我的部分问题是;除了Put () 默认给出的表示之外,在同时指定而不是只指定 Delta 时,还有什么不同吗?
  • 感谢您并为您的努力鼓掌,虽然这个答案包含很多关于错误界限的有用解释,但它并没有回答我的问题。答案是指定 delta、small 或两者的用例列表。
  • 你提到了浮点硬件,但这是正确的吗?我认为指定一个非 2 的幂 'Small 会导致额外的整数乘法指​​令进行缩放,但绝不会导致浮点指令。
  • @TamaMcGlinn 所以我承认:有了这个答案,我掉进了一个兔子洞。鉴于其历史,Ada 中的定点算术主题相当复杂。我用额外的 cmets 和某种结论更新了我的答案,希望能在某种程度上回答你的问题。
  • @TamaMcGlinn 是的,实际的错误界限是Small,但最初的意图是程序员会根据某些情况使用Delta 指定必需绝对错误界限一次性分析。然后编译器会在给定可用硬件的情况下为Small 选择一个值(因此是一个实际的绝对误差界限)。将需求与实际表示分开将允许算法在数值准确性方面具有某种可移植性。但是,正如我在更新的答案中所说,这并没有达到预期的效果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-03
  • 2011-09-22
  • 2014-04-10
  • 1970-01-01
  • 2017-07-10
  • 2017-07-25
  • 1970-01-01
相关资源
最近更新 更多