I tested a division by zero for a custom float type on two different environments. On one of them I got a Constraint_Error, while on the other I got a +Inf variable. I looked through the standards and found some info on RM A.5.3 Attributes of Floating Point Types. When I checked that S’Machine_Overflows, I noticed it is set to False for all Float types, on both environments.
I know that Ada standards do not impose any behavior for this scenario, but I am curious what might cause this difference in processing and if there is any other document or standard that describes this. Also, which would be the best behavior from a technical point of view?
Below is the code. I tried to resume it as much as possible, exactly how it is in our project:
with Ada.Text_IO; use Ada.Text_IO;
procedure Divide_By_Zero is
type Float8_T is digits 15;
for Float8_T’Size use 64;
type Geo_Scalar_T is new Float8_T;
type Integer4_T is range -2 \*\* 31 .. 2 \*\* 31 - 1;
for Integer4_T’Size use 32;
subtype Integer_T is Integer4_T;
subtype Cost_T is Integer_T range -10 \*\* 7 + 1 .. 10 \*\* 7 - 1;
Cost_First : constant Cost_T := Cost_T’Succ (Cost_T’First);
Cost_Last : constant Cost_T := Cost_T’Last;
None : constant Cost_T := Cost_T’First;
function Check (Value : Geo_Scalar_T) return Cost_T is
(if Value > Geo_Scalar_T (Cost_Last)
then Cost_Last
elsif Value < Geo_Scalar_T (Cost_First)
then Cost_First
else Cost_T (Value));
function “/” (Left : Cost_T; Right : Integer4_T) return Cost_T is
begin
if Left = None then
raise Constraint_Error;
end if;
return Check (Geo_Scalar_T (Left) / Geo_Scalar_T (Right));
end “/”;
Left : constant Cost_T := Cost_T (100);
Right : constant Integer4_T := Integer4_T (0);
Result : Cost_T;
begin
Result := Left / Right;
Put_Line ("Result = " & Result’Image);
exception
when Constraint_Error =>
Put_Line (“Constraint_Error raised”);
end Divide_By_Zero;
In Ada operations are performed in the machine type. If the machine does not generate overflow then it does not. However, when the result of an expression is assigned or passed as an argument then it is checked to be a valid value.
Long story short, do this:
type Geo_Scalar_T is new Float8_T range Float8_T'First..Float8_T'Last;
Constraint_Error will be raised at the line 32, when the function Check gets NaN for Geo_Scalar_T.
Leaving Float aside for a moment, let’s clean up some of this:
pragma Ada_2012;
with
Ada.Exceptions;
procedure Divide_By_Zero is
Limit_31 : constant := 2**31; -- 31-bit limit, for INTEGER4 ranges.
Limit_15 : constant := 10**7; -- Limit for INTEGER4 representation of floating-point.
-- Defining a 64-bit floating point number.
type Float8 is digits 15 with Size => 64;
-- Eliminating the non-numeric values from the floating-point number will,
-- I believe, yield the results you seek. To do this remove the ";--" below.
type Geo_Scalar is new Float8;-- range Float8'range;
-- Defining a 32-bit Integer, two's complement.
type Integer4 is range -Limit_31 .. Limit_31 - 1 with Size => 32;
-- Defining subtypes for operation upon the above types.
subtype Extended_Cost is Integer4 range Integer4'Succ(-Limit_15) .. Integer4'Pred(Limit_15);
subtype Cost is Extended_Cost range Extended_Cost'Succ(Extended_Cost'First) .. Extended_Cost'Last;
subtype Geo_Cost is Geo_Scalar range Geo_Scalar(Cost'First)..Geo_Scalar(Cost'Last);
-- NONE: The value representing the non-existance of a COST.
None : constant Extended_Cost := Extended_Cost'First;
-- Conversion:
-- (1) if the value is NONE, raise constraint error,
-- (2) if the value is within COST's range, convert it,
-- (3) if the value is UNDERFLOW, clamp to lowest value,
-- (4) if the value is OVERFLOW, clamp to highest value.
-- (5) if none of the above, SOMETHING IS VERY WRONG.
function Check (Value : Geo_Scalar) return Cost is
(if Value = Geo_Scalar(None) then raise Constraint_Error with "LEFT is NONE."
elsif Value in Geo_Cost then Cost(Value)
elsif Value < Geo_Cost'First then Cost'First
elsif Value > Geo_Cost'Last then Cost'Last
else raise Program_Error with "Sanity check: LOGIC FAILURE!"
);
function "/" (Left : Cost'Base; Right : Integer4) return Cost is
GS_L : constant Geo_Scalar:= Geo_Scalar(Left);
GS_R : constant Geo_Scalar:= Geo_Scalar(Right);
begin
if Left not in Extended_Cost then
raise Constraint_Error with "LEFT is not in range.";
elsif Left = None then
raise Constraint_Error with "LEFT is NONE.";
end if;
Ada.Text_IO.Put_Line( GS_L'Image & " / " & GS_R'Image );
return Check (GS_L / GS_R);
end "/";
Left : constant Extended_Cost := Extended_Cost (100);
Right : constant Integer4 := Integer4(0);
Result : Cost;
begin
Result := Left / Right;
Put_Line ("Result = " & Result'Image);
exception
when E : Constraint_Error =>
Put_Line ("Constraint_Error raised -> " & Ada.Exceptions.Exception_Message(E) );
end Divide_By_Zero;
Notice how we can use subtypes (and attributes) to collapse assumptions (and manual checking) into the type-system.
@DanCons – I believe that I showed why the stuff was happening, in the referenced post, but it occurs to me that perhaps I ought to share the Is_NaN function:
function Is_NAN(X :Geo_Scalar) return Boolean is
((X < X) = (X > X));
-- …
function "/" (Left : Cost'Base; Right : Integer4) return Cost is
GS_L : constant Geo_Scalar:= Geo_Scalar(Left);
GS_R : constant Geo_Scalar:= Geo_Scalar(Right);
begin
if Is_NaN(GS_L) then
raise Constraint_Error with "LEFT is Not-a-Number.";
elsif Left not in Extended_Cost then
raise Constraint_Error with "LEFT is not in range.";
elsif Left = None then
raise Constraint_Error with "LEFT is NONE.";
end if;
Ada.Text_IO.Put_Line( GS_L'Image & " / " & GS_R'Image );
Put_Line ("Is_NaN(L/R) -> " & Is_Nan(GS_L/GS_R)'Image );
return Check (GS_L / GS_R);
end "/";