提问人:Adaenthusiast 提问时间:3/16/2023 最后编辑:Adaenthusiast 更新时间:3/24/2023 访问量:356
Ada:将浮点数转换为十进制
Ada: Convert float to decimal
问:
这篇文章链接到这个 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) 结果与手动(和计算器)计算一致。
答:
这对我来说似乎是一个错误。它允许您执行任何包含 2 个乘法运算符和一个变量的表达式,但不能执行任何表达式。另一件奇怪的事情是,通常您想转换不兼容的类型,但似乎也失败了,并出现了错误。看起来该表达式将作为一种解决方法。 return X * Y * 2;
return X * 2.0;
return Real (Incompatibly_Typed_Variable) * X * Y;
return 2.0 * Real (X * Y);
评论
首先,是一个真正的数字文字。它具有类型,并且可以隐式转换为任何实际类型,包括您的类型。因此,此问题与浮动<>十进制转换无关。2.0
universal_real
Real
从规范上讲,定点类型由整数表示;等效值是存储的整数乘以该类型的增量。固定点类型在乘法和除法方面存在一些问题:诸如 之类的表达式等价于 ,它没有明显的类型。因此,对 '“*”' 的所有调用都会调用对类型生成的操作,并且需要定义结果类型的上下文或对结果类型的显式转换。X
X'
X = X' * Real'delta
X * Y
(X' * Real'delta) * (Y' * Real'delta) = X' * Y' * Real'delta ** 2
universl_fixed
universl_fixed
乘法运算符在 ARM 4.5.5 中定义。请注意第 19.a/2 段,“如果 A、B 和 C 都是某种固定点类型,我们要求在 A * B * C 等表达式中插入显式转换。因此,您必须显式地将其中一个乘法的结果转换为实数;然后,上下文将定义其他乘法的结果类型。
但是这个规范定义也允许乘以整数值的漏洞:,其中是某个整数类型,等价于具有 Real 类型的类型(前提是在范围内)。这些定义见第14段。因此,对于这种情况,您的返回表达式可以是 ,这应该有效。不需要显式转换。I * X
I
(I * X') * Real'delta
I * 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 9
Aft => 8
Aft => 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
这里的教训是,小数定点不能直接替代带有舍入的浮点数。
*从技术上讲,类型为小,但对于十进制类型,这等于增量。
评论
A * B * C
我不确定更改类型是否会改善对应关系。下面的变体产生预期的结果。尝试调整或查看效果。另外Real
Step
digits
Diff
显示计算值、精确值和增量值以供参考;特别是 2.927813,与实际值 3.490343 相差 0.562530。F(1.5)
它使用初始值 和 ,这不一定是初始条件。
XI
F(XI)
它根据给定的 .
Result
Step
Interval
Euler
现在计算下一个结果,然后递增。X
Diff
已更新为使用 的索引。从零开始的数组更接近于此相关示例,现在中的迭代方案概括了 中使用的方案。Result
Natural
Diff
Euler
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);
评论
DYDX
F
gntatchop
评论