I came across a surprising behaviour in that I cannot take the square powerof a variable. Ada permits that the square power of the actual value be taken though. Below is my sample code:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Long_Float_Text_IO;
with Ada.Float_Text_IO;
with Ada.Numerics.Long_Elementary_Functions;
use Ada.Numerics.Long_Elementary_Functions;
procedure Test_power is
testing : constant := -0.11603;
begin
Ada.Text_IO.Put("Works fine with numerical value entered directly: ");
Ada.Long_Float_Text_IO.Put (Item => -0.11603 ** 2.0, Fore => 3, Aft => 5, Exp => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.New_Line;
Ada.Text_IO.Put("This does not work: ");
Ada.Long_Float_Text_IO.Put (Item => testing ** 2.0, Fore => 3, Aft => 5, Exp => 0);
end Test_power;
The outputon the screen is:
Works fine with numerical value entered directly: -0.01346
This does not work:
Execution terminated by unhandled exception
Exception name: ADA.NUMERICS.ARGUMENT_ERROR
Message: a-ngelfu.adb:96 instantiated at a-nlelfu.ads:18
Call stack traceback locations:
0x413ed5 0x42771d 0x427a2a 0x4010b4 0x401146 0x7c817075
The compilation was done using:
GPS 4.4.1 (20091215) hosted on i686-pc-mingw32
GNAT GPL 2010 (20100603)
Two questions:
(a) What am I doing wrong when I take testing ** 2.0 ?
(b) The square of -0.11603 is positive 0.01346. So why I am also getting the negative sign in my first ouput?
After some additional testing:
If I take only positive 0.11603, then output (its square) gives
(a) -0.01346
(b) 0.01346
which can imply that part of the problem is with the negative sign.
Thanks a lot...
The "**" operator raises Numerics.Operator_Error when the left operand is negative, or when both operands have the value zero.
It's not unreasonable to expect it to work when the right operand is mathematically an exact integer like 2.0, but for non-integer exponents the mathematical result is a complex number, and Ada.Numerics.Generic_Elementary_Functions doesn't treat real numbers that happen to be exact integers as a special case.
If all you want to do is square a number, use the built-in "**" operator with an integer exponent. In other words, change this:
testing ** 2.0
to this:
testing ** 2
Related
I'm translating an exercise I made in Dafny into SPARK, where one verifies a tail recursive function against a recursive one. The Dafny source (censored, because it might still be used for classes):
function Sum(n:nat):nat
decreases n
{
if n==0 then n else n+Sum(n-1)
}
method ComputeSum(n:nat) returns (s:nat)
ensures s == Sum(n)
{
s := 0;
// ...censored...
}
What I got in SPARK so far:
function Sum (n : in Natural) return Natural
is
begin
if n = 0 then
return n;
else
return n + Sum(n - 1);
end if;
end Sum;
function ComputeSum(n : in Natural) return Natural
with
Post => ComputeSum'Result = Sum(n)
is
s : Natural := 0;
begin
-- ...censored...
return s;
end ComputeSum;
I cannot seem to figure out how to express the decreases n condition (which now that I think about it might be a little odd... but I got graded for it a few years back so who am I to judge, and the question remains how to get it done). As a result I get warnings of possible overflow and/or infinite recursion.
I'm guessing there is a pre or post condition to be added. Tried Pre => n <= 1 which obviously does not overflow, but I still get the warning. Adding Post => Sum'Result <= n**n on top of that makes the warning go away, but that condition gets a "postcondition might fail" warning, which isn't right, but guess the prover can't tell. Also not really the expression I should check against, but I cannot seem to figure what other Post I'm looking for. Possibly something very close to the recursive expression, but none of my attempts work. Must be missing out on some language construct...
So, how could I express the recursive constraints?
Edit 1:
Following links to this SO answer and this SPARK doc section, I tried this:
function Sum (n : in Natural) return Natural
is
(if n = 0 then 0 else n + Sum(n - 1))
with
Pre => (n in 0 .. 2),
Contract_Cases => (n = 0 => Sum'Result = 0,
n >= 1 => Sum'Result = n + Sum(n - 1)),
Subprogram_Variant => (Decreases => n);
However getting these warnings from SPARK:
spark.adb:32:30: medium: overflow check might fail [reason for check: result of addition must fit in a 32-bits machine integer][#0]
spark.adb:36:56: warning: call to "Sum" within its postcondition will lead to infinite recursion
If you want to prove that the result of some tail-recursive summation function equals the result of a given recursive summation function for some value N, then it should, in principle, suffice to only define the recursive function (as an expression function) without any post-condition. You then only need to mention the recursive (expression) function in the post-condition of the tail-recursive function (note that there was no post-condition (ensures) on the recursive function in Dafny either).
However, as one of SPARK's primary goal is to proof the absence of runtime errors, you must have to prove that overflow cannot occur and for this reason, you do need a post-condition on the recursive function. A reasonable choice for such a post-condition is, as #Jeffrey Carter already suggested in the comments, the explicit summation formula for arithmetic progression:
Sum (N) = N * (1 + N) / 2
The choice is actually very attractive as with this formula we can now also functionally validate the recursive function itself against a well-known mathematically explicit expression for computing the sum of a series of natural numbers.
Unfortunately, using this formula as-is will only bring you somewhere half-way. In SPARK (and Ada as well), pre- and post-conditions are optionally executable (see also RM 11.4.2 and section 5.11.1 in the SPARK Reference Guide) and must therefore themselves be free of any runtime errors. Therefore, using the formula as-is will only allow you to prove that no overflow occurs for any positive number up until
max N s.t. N * (1 + N) <= Integer'Last <-> N = 46340
as in the post-condition, the multiplication is not allowed to overflow either (note that Natural'Last = Integer'Last = 2**31 - 1).
To work around this, you'll need to make use of the big integers package that has been introduced in the Ada 202x standard library (see also RM A.5.6; this package is already included in GNAT CE 2021 and GNAT FSF 11.2). Big integers are unbounded and computations with these integers never overflow. Using these integers, one can proof that overflow will not occur for any positive number up until
max N s.t. N * (1 + N) / 2 <= Natural'Last <-> N = 65535 = 2**16 - 1
The usage of these integers in a post-condition is illustrated in the example below.
Some final notes:
The Subprogram_Variant aspect is only needed to prove that a recursive subprogram will eventually terminate. Such a proof of termination must be requested explicitly by adding an annotation to the function (also shown in the example below and as discussed in the SPARK documentation pointed out by #egilhh in the comments). The Subprogram_Variant aspect is, however, not needed for your initial purpose: proving that the result of some tail-recursive summation function equals the result of a given recursive summation function for some value N.
To compile a program that uses functions from the new Ada 202x standard library, use compiler option -gnat2020.
While I use a subtype to constrain the range of permissible values for N, you could also use a precondition. This should not make any difference. However, in SPARK (and Ada as well), it is in general considered to be a best practise to express contraints using (sub)types as much as possible.
Consider counterexamples as possible clues rather than facts. They may or may not make sense. Counterexamples are optionally generated by some solvers and may not make sense. See also the section 7.2.6 in the SPARK user’s guide.
main.adb
with Ada.Numerics.Big_Numbers.Big_Integers;
procedure Main with SPARK_Mode is
package BI renames Ada.Numerics.Big_Numbers.Big_Integers;
use type BI.Valid_Big_Integer;
-- Conversion functions.
function To_Big (Arg : Integer) return BI.Valid_Big_Integer renames BI.To_Big_Integer;
function To_Int (Arg : BI.Valid_Big_Integer) return Integer renames BI.To_Integer;
subtype Domain is Natural range 0 .. 2**16 - 1;
function Sum (N : Domain) return Natural is
(if N = 0 then 0 else N + Sum (N - 1))
with
Post => Sum'Result = To_Int (To_Big (N) * (1 + To_Big (N)) / 2),
Subprogram_Variant => (Decreases => N);
-- Request a proof that Sum will terminate for all possible values of N.
pragma Annotate (GNATprove, Terminating, Sum);
begin
null;
end Main;
output (gnatprove)
$ gnatprove -Pdefault.gpr --output=oneline --report=all --level=1 --prover=z3
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: flow analysis and proof ...
main.adb:13:13: info: subprogram "Sum" will terminate, terminating annotation has been proved
main.adb:14:30: info: overflow check proved
main.adb:14:32: info: subprogram variant proved
main.adb:14:39: info: range check proved
main.adb:16:18: info: postcondition proved
main.adb:16:31: info: range check proved
main.adb:16:53: info: predicate check proved
main.adb:16:69: info: division check proved
main.adb:16:71: info: predicate check proved
Summary logged in [...]/gnatprove.out
ADDENDUM (in response to comment)
So you can add the post condition as a recursive function, but that does not help in proving the absence of overflow; you will still have to provide some upper bound on the function result in order to convince the prover that the expression N + Sum (N - 1) will not cause an overflow.
To check the absence of overflow during the addition, the prover will consider all possible values that Sum might return according to it's specification and see if at least one of those value might cause the addition to overflow. In the absence of an explicit bound in the post condition, Sum might, according to its return type, return any value in the range Natural'Range. That range includes Natural'Last and that value will definitely cause an overflow. Therefore, the prover will report that the addition might overflow. The fact that Sum never returns that value given its allowable input values is irrelevant here (that's why it reports might). Hence, a more precise upper bound on the return value is required.
If an exact upper bound is not available, then you'll typically fallback onto a more conservative bound like, in this case, N * N (or use saturation math as shown in the Fibonacci example from the SPARK user manual, section 5.2.7, but that approach does change your function which might not be desirable).
Here's an alternative example:
example.ads
package Example with SPARK_Mode is
subtype Domain is Natural range 0 .. 2**15;
function Sum (N : Domain) return Natural
with Post =>
Sum'Result = (if N = 0 then 0 else N + Sum (N - 1)) and
Sum'Result <= N * N; -- conservative upper bound if the closed form
-- solution to the recursive function would
-- not exist.
end Example;
example.adb
package body Example with SPARK_Mode is
function Sum (N : Domain) return Natural is
begin
if N = 0 then
return N;
else
return N + Sum (N - 1);
end if;
end Sum;
end Example;
output (gnatprove)
$ gnatprove -Pdefault.gpr --output=oneline --report=all
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: flow analysis and proof ...
example.adb:8:19: info: overflow check proved
example.adb:8:28: info: range check proved
example.ads:7:08: info: postcondition proved
example.ads:7:45: info: overflow check proved
example.ads:7:54: info: range check proved
Summary logged in [...]/gnatprove.out
I landed in something that sometimes works, which I think is enough for closing the title question:
function Sum (n : in Natural) return Natural
is
(if n = 0 then 0 else n + Sum(n - 1))
with
Pre => (n in 0 .. 10), -- works with --prover=z3, not Default (CVC4)
-- Pre => (n in 0 .. 100), -- not working - "overflow check might fail, e.g. when n = 2"
Subprogram_Variant => (Decreases => n),
Post => ((n = 0 and then Sum'Result = 0)
or (n > 0 and then Sum'Result = n + Sum(n - 1)));
-- Contract_Cases => (n = 0 => Sum'Result = 0,
-- n > 0 => Sum'Result = n + Sum(n - 1)); -- warning: call to "Sum" within its postcondition will lead to infinite recursion
-- Contract_Cases => (n = 0 => Sum'Result = 0,
-- n > 0 => n + Sum(n - 1) = Sum'Result); -- works
-- Contract_Cases => (n = 0 => Sum'Result = 0,
-- n > 0 => Sum'Result = n * (n + 1) / 2); -- works and gives good overflow counterexamples for high n, but isn't really recursive
Command line invocation in GNAT Studio (Ctrl+Alt+F), --counterproof=on and --prover=z3 my additions to it:
gnatprove -P%PP -j0 %X --output=oneline --ide-progress-bar --level=0 -u %fp --counterexamples=on --prover=z3
Takeaways:
Subprogram_Variant => (Decreases => n) is required to tell the prover n decreases for each recursive invocation, just like the Dafny version.
Works inconsistently for similar contracts, see commented Contract_Cases.
Default prover (CVC4) fails, using Z3 succeeds.
Counterproof on fail makes no sense.
n = 2 presented as counterproof for range 0 .. 100, but not for 0 .. 10.
Possibly related to this mention in the SPARK user guide: However, note that since the counterexample is always generated only using CVC4 prover, it can just explain why this prover cannot prove the property.
Cleaning between changing options required, e.g. --prover.
I am going through the learn.adacore.com tutorial and have hit upon a problem that I am unsure of.
Specifically I understand that Ada is designed to trap attempts to overflow a variable with a specified range definition.
In the case below, the first attempt to do this causes a compiler 'range check failure' which is expected. However the following line doesn't trap it and I am not sure why:
with Ada.Text_IO; use Ada.Text_IO;
procedure Custom_Floating_Types is
type T_Norm is new float range -1.0 .. 1.0;
D : T_Norm := 1.0;
begin
Put_Line("The value of D =" & T_Norm'Image(D));
-- D := D + 1.0; -- This causes a range check failure at run time = completely expected.
Put_Line("The value of D =" & T_Norm'Image(D + 1.0)); -- This doesn't?
end Custom_Floating_Types;
You have a couple of pretty good answers, but I'm going to add another because it's clear that you expect the expression D + 1.0 to raise an exception, and the answers you have don't explain why it doesn't.
A type declaration like
type T_Norm is new float range -1.0 .. 1.0;
is roughly equivalent to
type T_Norm'Base is new Float;
subtype T_Norm is T_Norm'Base range -1.0 .. 1.0;
The type (called the "base type") isn't given a name, though it can often be referenced with the 'Base attribute. The name that is given is to a subtype, called the "first-named subtype".
This distinction is important and is often not given enough attention. As explained by egilhh, T_Norm'Image is defined in terms of T_Norm'Base. This is also true of the arithmetic operators. For example, "+" is defined as
function "+" (Left : in T_Norm'Base; Right : in T_Norm'Base) return T_Norm'Base;
2.0 is clearly in the range of T_Norm'Base, so evaluating D + 1.0 doesn't violate any constraints, nor does passing it to T_Norm'Image. However, when you try to assign the resulting value to D, which has subtype T_Norm, a check is performed that the value is in the range of the subtype, and an exception is raised because the check fails.
This distinction is used in other places to make the language work reasonably. For example, a constrained array type
type A is array (1 .. 10) of C;
is roughly equivalent to
type A'Base is array (Integer range <>) of C;
subtype A is A'Base (1 .. 10);
If you do
V : A;
... V (2 .. 4)
you might expect problems because the slice doesn't have the bounds of A. But it works because the slice doesn't have subtype A but rather the anonymous subtype A'Base (2 ..4).
The definition of 'Image says:
For every scalar subtype S:
S'Image denotes a function with the following specification:
function S'Image(Arg : S'Base)
return String
As you can see, it takes a parameter of the base (unconstrained) type
T_Norm'Image(D + 1.0) neither assigns nor reads an out-of-range value. It asks for the 'Image attribute (string representation) of (D + 1.0), which is the same as asking for (1.0 + 1.0).
I can see two ways the confusion might arise. First, the name "attribute" could be misinterpreted to suggest that 'Image involves something intrinsic to D. It doesn't. The 'Image attribute is just a function, so D is just part of the expression that defines the value of the parameter (in your example = 2.0).
Second, the 'Image attribute comes from Float. Thus, any Float can be its parameter.
You can create a function that accepts only parameters of T_Norm'Range and make that function an attribute of T_Norm. See Ada Reference Manual 4.10.
Because you don't store a value greater than range in D.
I'm about 5 hours into learning Ada. I made a simple program and I was putting different values into it and experienced behavior I can't explain.
My program works fine with normal inputs like km = 100, litres = 10, result = 10.0.
And obviously bad inputs work: km = "cat" result in "raised ADA.IO_EXCEPTIONS.DATA_ERROR"
But this combination of inputs has me baffled: km = 100..10. The program skips over the litres input and presents a result that works back to a litres value of 0.10. I was expecting a constraint error like when I entered 'cat'.
Can someone please explain this to me and how Ada programmers work around it so that 100..10 results in an error.
Here's my program:
-- This program takes km driven and litres consumed to calculate
-- fuel economy.
with Ada.Text_IO;
with Ada.Float_Text_IO;
procedure Main is
package TIO renames Ada.Text_IO;
package FIO renames Ada.Float_Text_IO;
type Km is new Float range 0.01 .. 10_000.00;
type Litres is new Float range 0.01 .. 10_000.00;
package Km_IO is new TIO.Float_IO (Km);
package Litres_IO is new TIO.Float_IO (Litres);
Entered_Km : Km;
Entered_Litres : Litres;
function Fuel_Economy (
Entered_Km : in Km;
Entered_Litres : in Litres
) return Float is
-- returns fuel economy normalized to litres per 100 km
begin
return Float(Entered_Litres) / Float(Entered_Km) * 100.0;
end Fuel_Economy;
begin
TIO.Put_Line ("This program calculates fuel economy.");
TIO.Put_Line ("Enter km:");
Km_IO.Get (Entered_Km);
TIO.Put_Line ("Enter litres:");
Litres_IO.Get (Entered_Litres);
TIO.Put ("Your fuel economy is ");
FIO.Put (Item => Fuel_Economy (Entered_Km, Entered_Litres),
Fore => 1,
Aft => 2,
Exp => 0
);
TIO.Put_Line (" litres per 100 km.");
end Main;
Exact output:
This program calculates fuel economy.
Enter km:
100..10
Enter litres:
Your fuel economy is 0.10 litres per 100 km.
[2018-05-06 14:08:16] process terminated successfully, elapsed time: 04.01s
I'm using GPS 2017 on windows 7.
Many thanks.
Typically experienced Ada users read the entire line into a String (using function Get_Line) and then extract the numeric value(s) from the String. Since programs like this typically loop until the user enters a valid value, reading into a String avoids a common problem with direct numeric I/O in which invalid input results in an infinite loop:
Get_Km : loop
Handle_Invalid : begin
Put_Line (Item => "Enter km");
Get (Item => Entered_Km);
exit Get_Km;
exception -- Handle_Invalid
when others =>
Put_Line ("Invalid input");
end Handle_Invalid;
end loop Get_Km;
The invalid input remains in the buffer and is repeatedly processed by Get.
Wright demonstrated a variation of this, but usually anything left on the line after the value(s) is discarded, so that input can be redirected from a file with trailing comments:
13.3 -- km traveled
One solution would be to discard the rest of the line after having read the (leading) number:
TIO.Put_Line ("Enter km:");
Km_IO.Get (Entered_Km);
TIO.Skip_Line;
Another, less straightforward, would be to check that there wasn’t anything left over:
TIO.Put_Line ("Enter km:");
Get_Km:
loop
declare
Input : constant String := TIO.Get_Line;
Last : Natural;
begin
Km_IO.Get (From => Input, Item => Entered_Km, Last => Last);
exit Get_Km when Input (Last + 1 .. Input'Length)
= (Last + 1 .. Input'Length => ' ');
TIO.Put_Line ("... just the number, please; '"
& Input (Last + 1 .. Input'Length)
& "' was left over");
exception
when others =>
TIO.Put_Line ("... a number, please");
end;
end loop Get_Km;
According to the Ada Language Reference Manual A.10.9(15-16) your run-time library is behaving correctly, as these two formats are allowed syntax for Ada.Text_IO.Float_IO.Get:
[+|–]numeral.[exponent]
[+|–].numeral[exponent]
Let the following Ada types be defined:
type Type_Huge is array (1 .. Integer'Last) of Float;
type Type_B is (foo, bar, blop, bloub);
type Type_A ( disc : Type_B := foo) is
record
case disc is
when foo =>
My_int : Integer;
when bar =>
huge_data : Type_Huge := (others => 0.0);
when others =>
null;
end case;
end record;
1- Do you confirm the following ?
my_var : Type_A;
(Type_A'Size = my_var'Size) returns False
2- what is the real value of my_var'Size ?
I would say the size is at least:
Type_B'Size + Integer'Size
3- What is the value of Type_A'Size ?
I would say the size is the max of the possible configurations.
3- Is there anything else the compiler would add (probably hidden) to my_var?
I have also read some articles concerning Value_Size and Object_Size
But I don't get the full picture right now.
Thx
As quoted in another answer the LRM requires 'Size be defined by the implementation for indefinite types; in addition, LRM M.2 (45) requires that the implementation documents this characteristic:
(45) The meaning of Size for indefinite subtypes.
If your compiler is GNAT, this is what it states in its reference manual:
Size for an indefinite subtype is the maximum possible size,
(...).
You can see the compiler's choice when you add compiler switch -gnatR3. The output also lists numbers for 'Value_Size as they depend on the record discriminant's value. (The (...) part talks about sizes of subprogram parameters.)
Quoting section 13.3 in the LRM:
(44) For every subtype S:
(45) S'Size [...]
(48) If S is indefinite, the meaning is implementation defined. [...]
In other words: It is implementation defined.
I'm new to Ada, and have been trying out the fixed-point "delta" types. Specifically, I've created a 32-bit delta type range 0.0 .. 1.0. However, when I try to square certain values, I get a CONSTRAINT_ERROR. As far as I know, that should't happen with my specified range. The threshold for this error appears to be sqrt(1/2). I'm using GNAT from MinGW-w64 version 4.8.0.
Test code (all of it compiles in the form of gnatmake <file> with no warnings/errors):
types.ads:
pragma Ada_2012;
with Ada.Unchecked_Conversion;
with Ada.Text_IO;
package Types is
type Fixed_Type is delta 1.0 / 2**32 range 0.0 .. 1.0
with Size => 32;
type Modular_Type is mod 2**32
with Size => 32;
function Fixed_To_Mod is new Ada.Unchecked_Conversion(Fixed_Type, Modular_Type);
package MIO is new Ada.Text_IO.Modular_IO(Modular_Type);
package FIO is new Ada.Text_IO.Fixed_IO(Fixed_Type);
end Types;
specifics.adb:
pragma Ada_2012;
with Ada.Text_IO;
with Types; use Types;
procedure Specifics is
package TIO renames Ada.Text_IO;
procedure TestValue(val: in Fixed_Type) is
square : Fixed_Type;
begin
square := val * val;
TIO.Put_Line("Value " & Fixed_Type'Image(val) & " squares properly.");
TIO.Put_Line("Square: " & Fixed_Type'Image(square));
TIO.New_Line;
exception
when Constraint_Error =>
TIO.Put_Line("Value " & Fixed_Type'Image(val) & " does not square properly.");
TIO.Put_Line("Square: " & Fixed_Type'Image(val * val));
TIO.Put_Line("Not sure how that worked.");
TIO.New_Line;
end TestValue;
function ParseFixed(s: in String; last: in Natural; val: out Fixed_Type) return Boolean is
l : Natural;
begin
FIO.Get(s(s'First..last), val, l);
return TRUE;
exception
when others =>
TIO.Put_Line("Parsing failed.");
return FALSE;
end ParseFixed;
buffer : String(1..20);
last : Natural;
f : Fixed_Type;
begin
loop
TIO.Put(">>> ");
TIO.Get_Line(buffer, last);
exit when buffer(1..last) = "quit";
if ParseFixed(buffer, last, f) then
TestValue(f);
end if;
end loop;
end Specifics;
Output of specifics.adb:
>>> 0.1
Value 0.1000000001 squares properly.
Square: 0.0100000000
>>> 0.2
Value 0.2000000000 squares properly.
Square: 0.0399999998
>>> 0.4
Value 0.3999999999 squares properly.
Square: 0.1599999999
>>> 0.6
Value 0.6000000001 squares properly.
Square: 0.3600000001
>>> 0.7
Value 0.7000000000 squares properly.
Square: 0.4899999998
>>> 0.75
Value 0.7500000000 does not square properly.
Square: -0.4375000000
Not sure how that worked.
>>> quit
Somehow, multiplying val by itself yielded a negative number, which explains the CONSTRAINT_ERROR... but never mind that, why am I getting a negative number in the first place?
I then decided to test for the point at which squaring the numbers started failing, so I wrote the following snippet:
fixedpointtest.adb:
pragma Ada_2012;
with Ada.Text_IO;
with Types; use Types;
procedure FixedPointTest is
package TIO renames Ada.Text_IO;
test, square : Fixed_Type := 0.0;
begin
while test /= Fixed_Type'Last loop
square := test * test;
test := test + Fixed_Type'Delta;
end loop;
exception
when Constraint_Error =>
TIO.Put_Line("Last valid value: " & Fixed_Type'Image(test-Fixed_Type'Delta));
TIO.Put("Hex value: ");
MIO.Put(Item => Fixed_To_Mod(test-Fixed_Type'Delta), Base => 16);
TIO.New_Line;
TIO.Put("Binary value: ");
MIO.Put(Item => Fixed_To_Mod(test-Fixed_Type'Delta), Base => 2);
TIO.New_Line;
TIO.New_Line;
TIO.Put_Line("First invalid value: " & Fixed_Type'Image(test));
TIO.Put("Hex value: ");
MIO.Put(Item => Fixed_To_Mod(test), Base => 16);
TIO.New_Line;
TIO.Put("Binary value: ");
MIO.Put(Item => Fixed_To_Mod(test), Base => 2);
TIO.New_Line;
TIO.New_Line;
end FixedPointTest;
and got the following output:
Last valid value: 0.7071067810
Hex value: 16#B504F333#
Binary value: 2#10110101000001001111001100110011#
First invalid value: 0.7071067812
Hex value: 16#B504F334#
Binary value: 2#10110101000001001111001100110100#
So, sqrt(1/2), we meet again. Could someone please explain to me why my code is doing this? Is there a way to make it multiply properly?
I think you are asking for 1 more bit of precision than is actually available "under the hood".
Your declaration
type Fixed_Type is delta 1.0 / 2**32 range 0.0 .. 1.0
with Size => 32;
is only accepted because GNAT has used a biased representation; there's no room for a sign bit. You can see this because 0.7071067810 is represented as 16#B504F333#, with the most significant bit set. So, when you multiply 0.71 by 0.71, the result has the most significant bit set; and the low-level code thinks that this must be the sign bit, so we have an overflow.
If you declare Fixed_Type as
type Fixed_Type is delta 1.0 / 2**31 range 0.0 .. 1.0
with Size => 32;
all should be well.
A further point: in your report of the behaviour of specifics with an input of 0.75, you quote the result
>>> 0.75
Value 0.7500000000 does not square properly.
Square: -0.4375000000
Not sure how that worked.
I rebuilt with gnatmake specifics.adb -g -gnato -bargs -E, and the result is now
>>> 0.75
Value 0.7500000000 does not square properly.
Execution terminated by unhandled exception
Exception name: CONSTRAINT_ERROR
Message: 64-bit arithmetic overflow
Call stack traceback locations:
0x100020b79 0x10000ea80 0x100003520 0x100003912 0x10000143e
and the traceback decodes as
system__arith_64__raise_error (in specifics) (s-arit64.adb:364)
__gnat_mulv64 (in specifics) (s-arit64.adb:318)
specifics__testvalue.2581 (in specifics) (specifics.adb:20) <<<<<<<<<<
_ada_specifics (in specifics) (specifics.adb:45)
main (in specifics) (b~specifics.adb:246)
and specifics.adb:20 is
TIO.Put_Line("Square: " & Fixed_Type'Image(val * val));
in the exception handler, which involves the problematic square again (not a good thing to do in an exception handler). You can see that the value 0.75 was printed without any problem in the line above: and in fixedpointtest.adb there was no problem in the additions leading to the last valid value 0.7071067810.
I was rather surprised to find that -gnato detects this error, since I'd thought it only applied to integer arithmetic; but in fact there's a discussion in the GNAT User Guide which states that it applies to fixed-point arithmetic too. It turns out that you can avoid the constraint error and get the correct arithmetic result by using -gnato3:
>>> 0.75
Value 0.7500000000 squares properly.
Square: 0.5625000000
but only at the cost of using arbitrary multiple-precision arithmetic - not a good idea for a time-constrained system!