Ada:将浮点数转换为十进制

Ada: Convert float to decimal

提问人:Adaenthusiast 提问时间:3/16/2023 最后编辑:Adaenthusiast 更新时间:3/24/2023 访问量:356

问:

这篇文章链接到这个 Ada 2005 访问类型。目标是使用 Ada 十进制类型来获得与手动(和计算器)计算类似的结果,其中在每个中间步骤中使用了 6 位小数。

从下表可以看出,当使用欧拉方法进行进一步迭代时,使用 Ada 码获得的值开始与最后一位数字的手动计算不同

Ada 代码的问题之一是主代码 diff.adb 中的行:return 2 * Real(XY)*;如果我也将其保留为返回 2 * X * Y,也没关系。

微分方程 (O.D.E.) 正在使用基本的欧拉方法(这是一种近似方法,不太准确)进行求解。 D.E. 为 dy/dx = 2xy。初始条件为 y0(x=x0=1) = 1。解析解为 y = e^((x^2)-1)。目标是获得 y(x=1.5)。

我们从点 (x0,y0) = (1,1) 开始。我们使用步长 h = 0.1,即 x 随着欧拉方法中的每次迭代而增加到 1.1、1.2、1.3,..等等,y(正在寻求解的变量)的对应值由欧拉算法确定,即:

y(n) = y(n-1) + h * f(x(n-1), y(n-1))

这里 y(n-1) 当我们启动算法时,y(0) = 1。此外,x(n-1) 是我们的起始 x(0) = 1。函数 f 是上面给出的导数函数 dy/dx = 2xy。

简而言之,h * f(x(n-1), y(n-1)) 是“两个连续 x 值之间的水平距离”乘以梯度。梯度公式为 dy/dx = delta y /delta x,它给出 delta y 或(y 的变化)为

增量 y = 增量 x * dy/dx。

在欧拉公式中,h 是 delta x,dy/dx 是梯度。所以 h * f(x(n-1), y(n-1)) 给出 delta y,即 delta y 的值变化,即 delta y。然后,将 y 中的这种变化添加到 y 的先前值中。欧拉方法基本上是一阶泰勒近似,x 变化很小。在曲线上绘制一条梯度线,解变量 y 的下一个值位于这条切线上,连续值为 x,即 xnew = xold + h,其中 h 是步长。

接下来的表格显示了通过手动(和计算器)计算的欧拉方法对变量 y 的解值,通过我的 Ada 代码计算,最后在最后一列中显示了精确的解。

x y(手) ADA代码 y(精确)
1.1 1.200000 1.200000 1.233678
1.2 1.464000 1.464000 1.552707
1.3 1.815360 1.815360 1.993716
1.4 2.287354 2.287353 2.611696
1.5 2.927813 2.927811 3.490343

例如,通过手工和计算器,y(x=1.1) 即 y(1) 在 x = x(1) 时计算为 y(x=1.1) = y(0) + h * f(x=1,y=1) = 1 + 0.1 * (2 * 1* 1) = 1.200000 到 6 d.p.

y(2) 在 x = x(2) 时计算为 y(x=1.2) = y(1) + h * f(x=1.1,y=1.200000) = 1.200000 + 0.1 * (2 * 1.1* 1.200000) = 1.464000 至 6 d.p.

y(3) 在 x = x(3) 时计算为 y(x=1.3) = y(2) + h * f(x=1.2,y=1.464000) = 1.464000 + 0.1 * (2 * 1.2* 1.464000) = 1.815360 至 6 d.p.

y(4) 在 x = x(4) 处计算为 y(x=1.4) = y(3) + h * f(x=1.3,y=1.815360) = 1.815360 + 0.1 * (2 * 1.3* 1.815360) = 2.287354 至 6 d.p.

y(5) 在 x = x(5) 时计算为 y(x=1.5) = y(4) + h * f(x=1.4,y=2.287354) = 2.287354 + 0.1 * (2 * 1.4* 2.287354) = 2.927813 至 6 d.p.

现在我想修改代码,以便它们使用固定数量的小数位,即小数点后 6 位。

主代码为 diff.adb

with Ada.Text_IO;
with Euler;
procedure Diff is

  type Real is delta 0.000001 digits 9;
  type Vector is array(Integer range <>) of Real;
  type Ptr is access function (X: Real; Y: Real) return Real;
   
  package Real_IO is new Ada.Text_IO.Decimal_IO(Num => Real); 
  use Real_IO; 

  procedure Solve is new Euler(Decimal_Type => Real, Vector_Type => Vector, Function_Ptr => Ptr);

  function Maths_Func(X: Real; Y: Real) return Real is
  begin
    return 2 * Real(X*Y);
  end Maths_Func;

  
  Answer: Vector(1..6);

begin
  Solve(F => Maths_Func'Access, Initial_Value => 1.0, Increment => 0.1, Result => Answer);
  for N in Answer'Range loop
    Put(1.0 + 0.1 * Real(N-1), Exp => 0);
    Put( Answer(N), Exp => 0);
    Ada.Text_IO.New_Line;
  end loop;
end Diff;

然后是 euler.ads

generic
  type Decimal_Type is delta <> digits <>;
  type Vector_Type is array(Integer range <>) of Decimal_Type;
  type Function_Ptr is access function (X: Decimal_Type; Y: Decimal_Type) return Decimal_Type;
procedure Euler(
  F: in Function_Ptr; Initial_Value, Increment: in Decimal_Type; Result: out Vector_Type);

和包体 euler.adb

procedure Euler
  (F : in Function_Ptr; Initial_Value, Increment : in Decimal_Type; Result : out Vector_Type)
is
   Step : constant Decimal_Type := Increment;
   Current_X : Decimal_Type := 1.0;

begin
   Result (Result'First) := Initial_Value;
   for N in Result'First + 1 .. Result'Last loop
      Result (N) := Result (N - 1) + Step * F(Current_X, Result (N - 1));
      Current_X := Current_X + Step;
   end loop;
end Euler;

在编译时,我收到指向 diff.adb 的消息:

无法从上下文中确定类型

需要显式转换为结果类型

对于行,返回 2.0 倍 X 倍 Y;

也许 2.0 在这里造成了麻烦。如何将这个浮点数转换为十进制?

我相信在diff.adb的下方,我会遇到同样的问题:

Solve(F => Maths_Func'Access, Initial_Value => 1.0, Increment => 0.1, Result => Answer);

因为它也包含浮点数。

编译是在 Windows 上使用 2011 年的 32 位 GNAT 社区版完成的。为什么是2011年?这是因为我更喜欢那一年的 IDE,而不是最近几年出现的苍白的 IDE。

接下来给出基于垃圾神代码的修订代码:

主文件 diff.adb

with Ada.Numerics.Generic_Elementary_Functions; use Ada.Numerics;
with Ada.Text_IO;                               use Ada.Text_IO;
with Euler;

procedure Diff is

   type Real is digits 7;
   type Vector is array (Positive range <>) of Real;
   type Ptr is access function (X : Real; Y : Real) return Real;
   type Round_Ptr is access function (V : Real) return Real;

   procedure Solve is new Euler (Float_Type => Real, Vector => Vector, Function_Ptr => Ptr, Function_Round_Ptr => Round_Ptr);
   package Real_Functions is new Generic_Elementary_Functions (Real);
   use Real_Functions;
   package Real_IO is new Ada.Text_IO.Float_IO (Real);
   use Real_IO;

   function DFDX (X, Y : Real) return Real is (2.0 * X * Y);
   function F (X : Real) return Real is (Exp (X**2.0 - 1.0));
   function Round (V : in Real) return Real is (Real'Rounding (1.0E6 * V) / 1.0E6);

   XI      : constant Real := 1.0;
   YI      : constant Real := 1.0;
   Step    : constant Real := 0.1;
   Result  : Vector (Positive'First .. 6); --11 if step = 0.05
   X_Value : Real;

begin
   Solve (DFDX'Access, Round'Access, XI, YI, Step, Result);
   Put_line("        x      calc     exact     delta");
   for N in Result'Range loop
      X_Value := 1.0 + Step * Real (N - 1);
      Put (X_Value, Exp => 0);
      Put (" ");
      Put (Result (N), Exp => 0);
      Put (" ");
      Put (F (X_Value), Exp => 0);
      Put (" ");
      Put (Result (N) - F (X_Value), Exp => 0);
      Ada.Text_IO.New_Line;
   end loop;
end Diff;

文件 euler.ads

generic
   type Float_Type is digits <>;
   type Vector is array (Positive range <>) of Float_Type;
   type Function_Ptr is access function (X, Y : Float_Type) return Float_Type;
   type Function_Round_Ptr is access function (V : Float_Type) return Float_Type;
procedure Euler
  (DFDX : in Function_Ptr; Round : Function_Round_Ptr; XI, YI, Step : in Float_Type; Result : out Vector);

文件 euler.adb

procedure Euler
  (DFDX : in Function_Ptr; Round : Function_Round_Ptr; XI, YI, Step : in Float_Type; Result : out Vector)
is
   H : constant Float_Type := Step;
   X : Float_Type          := XI;
begin
   Result (Result'First) := YI;
   for N in Result'First + 1 .. Result'Last loop
       Result (N) :=  Round(Result (N - 1)) + Round(H * DFDX (X, Result (N - 1)));
       X          := X + Step;
   end loop;
end Euler;

给出 **Step H = 0.1 ** 的输出

x calc (阿达) 确切 三角洲
1.1 1.200000 1.233678 1.233678
1.2 1.464000 1.552707 -0.033678
1.3 1.815360 1.993716 -0.088707
1.4 2.287354 2.611696 -0.178356
1.5 2.927813 3.490343 -0.562530

calc (Ada) 结果与手动(和计算器)计算一致。

浮点 十进制 ADA

评论

0赞 trashgod 3/18/2023
推断您的目标是评估有限精度的计算,而不仅仅是显示有限精度的结果,这是否正确?
0赞 Adaenthusiast 3/18/2023
@trashgod不是真的要评估。但是,常微分方程的数值方法,如欧拉方法族和 Runge kutta 方法族,在所有教科书中都是按照固定的小数位计算的,当计算逐步显示时,比如用手和计算器。我想重现这种逐步计算,其中中间值保持在固定的小数位数,比如 6。但是我尝试过使用 Mathcad 及其舍入函数 round(number, no of d.p.) 可以解决问题,我得到的结果与手动和计算器相似。
1赞 Adaenthusiast 3/20/2023
@trashgod我现在已经尽可能多地输入了信息。谢谢。

答:

2赞 Guber 3/16/2023 #1

这对我来说似乎是一个错误。它允许您执行任何包含 2 个乘法运算符和一个变量的表达式,但不能执行任何表达式。另一件奇怪的事情是,通常您想转换不兼容的类型,但似乎也失败了,并出现了错误。看起来该表达式将作为一种解决方法。 return X * Y * 2;return X * 2.0;return Real (Incompatibly_Typed_Variable) * X * Y;return 2.0 * Real (X * Y);

评论

0赞 Guber 3/16/2023
注意:我只在 Windows 上使用 GNAT studio 来编译它,如果有人有任何其他 Ada 编译器,如果这个错误也以同样的方式出现或编译正确,那就太好了。
3赞 Jeffrey R. Carter 3/16/2023 #2

首先,是一个真正的数字文字。它具有类型,并且可以隐式转换为任何实际类型,包括您的类型。因此,此问题与浮动<>十进制转换无关。2.0universal_realReal

从规范上讲,定点类型由整数表示;等效值是存储的整数乘以该类型的增量。固定点类型在乘法和除法方面存在一些问题:诸如 之类的表达式等价于 ,它没有明显的类型。因此,对 '“*”' 的所有调用都会调用对类型生成的操作,并且需要定义结果类型的上下文或对结果类型的显式转换。XX'X = X' * Real'deltaX * Y(X' * Real'delta) * (Y' * Real'delta) = X' * Y' * Real'delta ** 2universl_fixeduniversl_fixed

乘法运算符在 ARM 4.5.5 中定义。请注意第 19.a/2 段,“如果 A、B 和 C 都是某种固定点类型,我们要求在 A * B * C 等表达式中插入显式转换。因此,您必须显式地将其中一个乘法的结果转换为实数;然后,上下文将定义其他乘法的结果类型。

但是这个规范定义也允许乘以整数值的漏洞:,其中是某个整数类型,等价于具有 Real 类型的类型(前提是在范围内)。这些定义见第14段。因此,对于这种情况,您的返回表达式可以是 ,这应该有效。不需要显式转换。I * XI(I * X') * Real'deltaI * X'2 * X * Y

在一般情况下,您需要转换为所需的类型:。Real (2.7 * X) * Y

为了从代码中获得与手动操作相同的结果,他们需要执行相同的操作。通过手动,您将一个值计算为大量有效数字,然后将其四舍五入到 6 位。为了让你的代码做到这一点,你需要类似的东西

with Ada.Text_IO;
procedure Diff is
   type Real is digits 9;
   type Vector is array (Positive range <>) of Real;
   
   package Real_IO is new Ada.Text_IO.Float_IO (Num => Real); 

   function F (X : Real; Y : Real) return Real is
      (2.0 * X * Y);
      
   function Round (V : in Real) return Real is
      (Real'Rounding (1.0E6 * V) / 1.0E6);
     
   Step : constant := 0.1;
  
   Current_X : Real := 1.0;
   Answer    : Vector (1 .. 6);
begin -- Diff
  Answer (1) := 1.0;
  
   Solve : for N in 2 .. Answer'Last loop
      Answer (N) := Round (Answer (N - 1) + Step * F (Current_X, Answer (N - 1) ) );
      Current_X := Current_X + Step;
   end loop Solve;
   
   Print : for N in Answer'Range loop
      Real_IO.Put (Item => 1.0 + 0.1 * Real (N - 1), Aft => 1, Exp => 0);
      Real_IO.Put (Item => Answer (N), Aft => 8, Exp => 0);
      Ada.Text_IO.New_Line;
   end loop Print;
end Diff;

我曾经在四舍五入之前允许您的手牌计算中可能存在的额外有效数字。我已经输出以表明舍入有效;你要用它来避免额外的零。digits 9Aft => 8Aft => 6

这输出

 1.0 1.00000000
 1.1 1.20000000
 1.2 1.46400000
 1.3 1.81536000
 1.4 2.28735400
 1.5 2.92781300

这里的教训是,小数定点不能直接替代带有舍入的浮点数。

*从技术上讲,类型为,但对于十进制类型,这等于增量。

评论

0赞 Adaenthusiast 3/16/2023
使用转换为十进制类型返回 Real(2.0*XY) 会给出与帖子中给出的相同的编译器错误消息。但是使用return Real(2*XY)可以成功编译和运行。并且没有抱怨 Solve(F => Maths_Func'Access, Initial_Value => 1.0, Increment => 0.1, Result => Answer) 行,其中的值类似于 1.0 和 0.1。为什么这里我们不需要 Real(0.1) 和 Real(1.0) 进行十进制类型转换?我的目标是以 6 d.p. 的精度进行所有计算。
0赞 Jeffrey R. Carter 3/17/2023
我本可以更清楚,但我的转换是错误的。我已经编辑了这篇文章,希望它更清晰和正确。实例化中不需要进行任何转换,因为这些是简单的数字文本,而不是表达式,因此会隐式转换为所需的类型。
0赞 Adaenthusiast 3/18/2023
无论我如何使用 Ada 代码,我都无法获得与手动计算它们和小步长 h 计算器时相同的解决方案。最后几位略有不同。但是,像 Mathcad 这样的程序确实提供了我通过手和计算器获得的解决方案。在 Mathcad 中,我使用函数 round(number,6) 轻松获得小数点后 6 d.p.。关于 Ada,我尝试了 Decimal_Type(Float'Truncation(number)),但我也没有那么成功。
0赞 Jeffrey R. Carter 3/19/2023
没有看到你在 Ada 和手上做什么,我无法发表评论。但是,如果你的手牌计算四舍五入到 6 位,你必须在 Ada 中采取额外的步骤来做同样的事情。A * B * C
1赞 Jeffrey R. Carter 3/22/2023
我的版本更简单,因为它不那么通用。一般解决方案更有用,但更简单的版本更适合专注于正在讨论的概念。然后,您从简化版本中获取想法,并将它们应用到更通用的解决方案中,就像您所做的那样。有几件事我会做不同的事情,但作为一种学习体验,你的版本看起来很合理。
1赞 trashgod 3/21/2023 #3

我不确定更改类型是否会改善对应关系。下面的变体产生预期的结果。尝试调整或查看效果。另外RealStepdigits

  • Diff显示计算值、精确值和增量值以供参考;特别是 2.927813,与实际值 3.490343 相差 0.562530。F(1.5)

  • 它使用初始值 和 ,这不一定是初始条件。XIF(XI)

  • 它根据给定的 .ResultStepInterval

  • Euler现在计算下一个结果,然后递增。X

  • Diff已更新为使用 的索引。从零开始的数组更接近于此相关示例,现在中的迭代方案概括了 中使用的方案。ResultNaturalDiffEuler

        x      calc     exact     delta
 1.000000  1.000000  1.000000  0.000000
 1.100000  1.200000  1.233678  0.033678
 1.200000  1.464000  1.552707  0.088707
 1.300000  1.815360  1.993716  0.178356
 1.400000  2.287354  2.611696  0.324343
 1.500000  2.927813  3.490343  0.562530
 1.600000  3.806156  4.758821  0.952665
 1.700000  5.024126  6.619369  1.595242
 1.800000  6.732329  9.393331  2.661002
 1.900000  9.155968 13.599051  4.443083
 2.000000 12.635236 20.085537  7.450301

使用以下方式恢复:gnatchop

--
-- Euler method: https://en.wikipedia.org/wiki/Euler_method
-- https://stackoverflow.com/q/75749137/230513
--
with Ada.Numerics.Generic_Elementary_Functions; use Ada.Numerics;
with Ada.Text_IO;                               use Ada.Text_IO;
with Euler;
with System;

procedure Diff is

   type Real is digits System.Max_Digits;
   type Vector is array (Natural range <>) of Real;
   type Ptr is access function (X : Real; Y : Real) return Real;
   procedure Solve is new Euler (Real, Vector, Ptr);

   package Real_Functions is new Generic_Elementary_Functions (Real);
   use Real_Functions;
   package Real_IO is new Ada.Text_IO.Float_IO (Real);
   use Real_IO;

   function DYDX (X, Y : Real) return Real is (2.0 * X * Y); -- ODE
   function F (X : Real) return Real is (Exp (X**2.0 - 1.0)); -- Exact

   XI       : constant Real := 1.0;
   YI       : constant Real := F (XI);
   Step     : constant Real := 0.1;
   Interval : constant Real := 1.0;
   Aft      : constant      := 6;
   X_Value  : Real          := XI;
   Result   : Vector (Natural'First .. Natural (Interval / Step));

begin
   Solve (DYDX'Access, XI, YI, Step, Result);
   Put_Line ("        x      calc     exact     delta");
   for N in Result'Range loop
      Put (X_Value, Aft => Aft, Exp => 0);
      Put (" ");
      Put (Result (N), Aft => Aft, Exp => 0);
      Put (" ");
      Put (F (X_Value), Aft => Aft, Exp => 0);
      Put (" ");
      Put (F (X_Value) - Result (N), Aft => Aft, Exp => 0);
      Ada.Text_IO.New_Line;
      X_Value := X_Value + Step;
   end loop;
end Diff;
--
-- Euler method: https://en.wikipedia.org/wiki/Euler_method
-- https://stackoverflow.com/q/75749137/230513
--
procedure Euler
  (F : in Function_Ptr; XI, YI, Step : in Float_Type; Result : out Vector)
is
   H : constant Float_Type := Step;
   X : Float_Type          := XI;
begin
   Result (Result'First) := YI;
   for N in Result'First + 1 .. Result'Last loop
      Result (N) := Result (N - 1) + H * F (X, Result (N - 1));
      X          := X + Step;
   end loop;
end Euler;
--
-- Euler method: https://en.wikipedia.org/wiki/Euler_method
-- https://stackoverflow.com/q/75749137/230513
--
generic
   type Float_Type is digits <>;
   type Vector is array (Natural range <>) of Float_Type;
   type Function_Ptr is access function (X, Y : Float_Type) return Float_Type;
procedure Euler
  (F : in Function_Ptr; XI, YI, Step : in Float_Type; Result : out Vector);

评论

0赞 trashgod 3/21/2023
很高兴你得到了预期的结果。是的,我的意思是将符号用于原始函数;虽然只是正式名称,但我会为分析解决方案寻找一个新名称。请编辑问题以包含修改后的代码;尝试处理多个文件。DYDXFgntatchop
0赞 Adaenthusiast 3/22/2023
这些更改已在帖子中进行。但是当只有一个文件 .ada 时会使用 gnatchop,我们想将其分解为它的规格和正文。
0赞 Adaenthusiast 3/22/2023
我现在处于两难境地,该接受谁的答案。你玩了我自己的代码,我做了一些更改,包括 Jeffrey 的舍入功能,以使代码正常工作。Jeffrey 的新代码现在可以正常工作,并且更简单,没有访问类型。两个答案都没问题,我学到了很多东西!
0赞 trashgod 3/22/2023
我敦促你接受对你帮助最大的答案;您还可以对您认为有用的答案投赞成票。
0赞 Adaenthusiast 3/22/2023
由于这是我第一次使用泛型和访问类型,如果您能在问题中查看我的最新代码,看看样式是否正常,将不胜感激。 谢谢。