!standard A.05.03(26) 04-04-20 AI95-00379/01
!standard A.05.03(29)
!standard A.05.03(47)
!class binding interpretation 04-04-20
!status work item 04-04-20
!status received 04-04-05
!priority Low
!difficulty Medium
!subject Static evaluation of numeric attributes
!summary
!question
The wording of Remainder specifies that the result is zero if the result is not
a machine number. That seems very strange. It might make sense if the operands
are assumed to be machine numbers, but if they are not (as in static
evaluation), this could lead to nonsense results.
The definition of Scaling and Compose require the result to be a machine
number. Is this intended even for static evaluation?
!wording
Change A.5.3(47) by approximately
"If the result R is a machine number, the result is R; if R satisfies
T'Pred(0.0) < R < T'Succ (0.0), then the result is 0.0; otherwise, the result
is one of the machine numbers adjacent to R."
Dunno what to change A.5.3(26) and A.5.3(29) to (if anything).
!discussion
The requirement for Compose and Scaling to return a machine number means that
an implementation has to discard any extra precision in the calculation. On a
machine like the Intel processors, where operations are done in extra
precision, and instructions exist to implement these attributes, that's a
significant extra expense.
--!corrigendum
!example
!ACATS test
!appendix
!topic Unexpected semantics of 'Floor, 'Ceiling and other static attributes
!reference RM95-A.5.3(30-35)
!from Gary Dismukes 2004-04-05
!discussion
[Note: This comment is submitted on behalf of Geert Bosch of Ada Core
Technologies. This issue is outside my area of expertise and I have
only made minor editorial changes to the write-up from Geert. -- Gary]
The following program raises constraint error:
procedure t is
X : constant Float := Float'Floor (2.0**32 - 0.5);
begin
if X >= 2.0**32 then
raise Constraint_Error;
end if;
end t;
The reason is that the Float'Floor attribute returns the largest integer
smaller than or equal to its argument even if that number is not a machine
number. When leaving the static context, the value gets rounded to the
nearest machine number, which rounds up in this case.
Even though this is consistent with a literal reading of the RM, this
seems unintended and is unexpected. It seems more useful to define
these attributes as follows:
30. S'Floor
S'Floor denotes a function with the following specification:
31. function S'Floor (X : T)
return T
32. The function yields the value Floor(X), i.e., the largest
(most positive) integral value less than or equal to X. When
>>> ^^^^^machine number
X is zero, the result has the sign of X; a zero result
otherwise has a positive sign.
33. S'Ceiling
S'Ceiling denotes a function with the following specification:
34. function S'Ceiling (X : T)
return T
35. The function yields the value Ceiling(X), i.e., the smallest
(most negative) integral value greater than or equal to X.
>>> ^^^^^ machine number
When X is zero, the result has the sign of X; a zero result
otherwise has a negative sign when S'Signed_Zeros is True.
(That is, replace "integral value" with "integral machine number".)
Rounding and Unbiased_Rounding need similar replacements of value by
machine number.
****************************************************************
From: Adam Beneschan
Sent: Monday, April 5, 2004 3:15 PM
I disagree, at least with respect to Floor and Ceiling. The
definition of "Floor" given in the RM is the mathematical definition.
If I were to say Y := Floor(X), I would assume that after this
statement,
0.0 <= X-Y < 1.0
regardless of the value of X beforehand. But, assuming "Float" is a
32-bit float with a 23-bit mantissa (which really ought to be stated
in a post like this---we can't assume that every Float in every Ada
compiler is implemented this way), the proposed definition would, for
X = 2.0**32, yield a value of Y = Floor(X) where X-Y = 256. This, to
me, would be unexpected semantics.
If it's *useful* to return the largest integral machine number smaller
than or equal to the argument, then let's propose a new attribute for
that purpose. But it seems wrong to change the definition of a
well-known mathematical term.
****************************************************************
From: Tucker Taft
Sent: Monday, April 5, 2004 3:36 PM
The suggestion to replace "integral value" with "integral machine number"
makes sense to me.
****************************************************************
From: Randy Brukardt
Sent: Monday, April 5, 2004 4:18 PM
I agree with Adam, this seems like a horrible idea.
A common use of these attributes is to use them immediately before a conversion
to some integer type:
procedure t is
X : constant Integer := Integer(Float'Floor (2.0**31 - 0.5));
begin
if X /= 2.0**31-1 then
raise Program_Error;
end if;
end t;
I think virtually everyone would find it goofy if the above program raised
Program_Error. Yet, the proposed wording change would require X to be 2**31-128
on IEEE machines.
The case that causes concern can only happen for a static expression (if the
argument had already been a machine number, the answer also would have to be a
machine number), and as the above shows, the change is likely to produce
nonsense as it is to fix something. And it's clearly incompatible (it's
changing the answer of something in use for years). So I don't think the change
meets the "smell" test.
****************************************************************
From: Nick Roberts
Sent: Monday, April 5, 2004 6:58 PM
> (That is, replace "integral value" with "integral machine number".)
I don't think the proposed wording is acceptable, as such, because (if
followed pedantically) it would produce silly results. For example,
Float'Floor(5.1) could produce the result 4.0000... if 5.0000... happened
not to be a machine number. I'm sure that this is not the intended effect of
the proposed wording (and would not be acceptable to anyone).
I suggest the required wording for Floor is to change "integral value less
than or equal to X" to "integral value less than or equal to X whose nearest
machine number is not greater than X." Similarly, for Ceiling, change
"integral value greater than or equal to X" to "integral value greater than
or equal to X whose nearest machine number is not less than X."
> Rounding and Unbiased_Rounding need similar replacements of value by
> machine number.
For the Rounding function, the existing wording is:
S'Rounding denotes a function with the following specification:
function S'Rounding (X : T) return T
The function yields the integral value nearest to X, rounding away
from zero if X lies exactly halfway between two integers. A zero
result has the sign of X when S'Signed_Zeros is True.
This could be changed to:
The function yields the machine number nearest to the integer
nearest to X, preferring the result further from zero if X lies exactly
halfway between two integers. A zero result has the sign of X
when S'Signed_Zeros is True.
S'Unbiased_Rounding denotes a function with the following specification:
function S'Unbiased_Rounding (X : T) return T
The function yields the integral value nearest to X, rounding toward
the even integer if X lies exactly halfway between two integers. A
zero result has the sign of X when S'Signed_Zeros is True.
This could be changed to:
The function yields the machine number nearest to the integer
nearest to X, preferring the result nearer the even integer of the
two integers nearest X if X lies exactly halfway between two
integers. A zero result has the sign of X when S'Signed_Zeros is
True.
I support these changes, with the proviso that a minimal survey is first
conducted to confirm that it is unlikely that existing (important,
operational) software will not be adversely affected. I can confirm that
none of the software I am responsible for (which isn't much) would be.
****************************************************************
From: Randy Brukardt
Sent: Monday, April 5, 2004 8:08 PM
> > Rounding and Unbiased_Rounding need similar replacements of value by
> > machine number.
I hadn't noticed this before. I would be very opposed to any such change to
Unbiased_Rounding. That's because that attribute is often used directly in
Integer type conversions. On the Intel processors, we optimize these down to
just the store instruction (rounding comes for free in that instruction).
If the expression is at all complex, it will have been evaluated in the
80-bit precision of the Intel floating point registers. In order to
implement any machine number wording, we'd have to truncate the expression
value to a machine number. That is an expensive operation, as the
implementation usually involves writing the value to memory and then reading
it back. So adding such a requirement would not only change the answers in
significant ways, but it also would cost quite a bit in performance.
Rounding is pretty common!
****************************************************************
From: Pascal Leroy
Sent: Tuesday, April 6, 2004 4:26 AM
> The reason is that the Float'Floor attribute returns the
> largest integer smaller than or equal to its argument even if
> that number is not a machine number. When leaving the static
> context, the value gets rounded to the nearest machine
> number, which rounds up in this case.
The analysis is confused here. The issue has nothing to do with
"leaving the static context", because all the expressions involved are
static. The issue comes from the "tweaking" of floating-point values
required by 4.9(38). In other words the result of Float'Floor is
2.0**32-1.0, but the value of X is 2.0**32 because that's presumably the
machine number closer to 2.0**32-1.0.
> (That is, replace "integral value" with "integral machine number".)
>
> Rounding and Unbiased_Rounding need similar replacements of
> value by machine number.
I am going to strenuously oppose this change. For one, it's grossly
incompatible: it would silently change the semantics of existing code,
in ways that may only be detected the hard way under the debugger. But
more importantly it's completely misguided: one very important use of
the attributes of A.5.3 is for static computations. For such
computations, we want to stick to the mathematical definition as much as
possible. After all, exact evaluation of static expressions is one of
the great features of Ada. Introducing dependencies on machine numbers
here is not only killing all hopes of portability, it also makes it
impossible to reason about static expressions, as Adam and Randy have
pointed out. Furthermore, it leads to definitional problems; for
instance, what is the value of:
Float'Ceiling (10.0*10000) - 10.0*10000
I hope it is 0.0, but I can't deduce that from the proposed wording
because there is no machine number above 10.0*10000 (assuming a
reasonably-size Float).
As much as possible, we want to avoid any dependency on the set of
machine numbers in A.5.3. In fact, I have always assumed that the
references to machine numbers in A.5.3 do not really apply to static
computations, and I notice that GNAT has the same view; witness the fact
that the following declarations compile with both GNAT and Apex:
X : constant := Float'Compose (1.0, -1000);
Y : constant := 1 / Boolean'Pos (X = 2.0 ** (-1001));
A strict reading of A.5.3(26) would lead to the conclusion that, because
2.0 ** (-1001) is not a machine number of Float, X should be 0.0 or the
machine number immediately above 0.0. However, that interpretation
would be actively harmful as it would lose the accuracy of static
computations for no benefit at all.
****************************************************************
From: Tucker Taft
Sent: Tuesday, April 6, 2004 8:01 AM
Pascal's discussion convinces me we should not change the definition
of this attribute.
****************************************************************
From: Nick Roberts
Sent: Tuesday, April 6, 2004 9:17 AM
It doesn't convince me.
I'm not certain that the issue is important for Rounding and
Unbiased_Rounding, but I would prefer the definitions I proposed (after
Geert and Gary) for Floor and Ceiling, because they would correspond to the
semantics that I would require in real working software (as opposed to the
artificial examples given by Randy and Pascal).
Presumably Geert's customer will write their own, probably non-portable,
versions of Floor and Ceiling in order to get the semantics they need.
That's hardly a victory for the language standard, is it?
****************************************************************
From: Randy Brukardt
Sent: Tuesday, April 6, 2004 2:41 PM
Why would they need a non-portable version? The attributes of A.5.3 and the
'Machine attribute provide all of the support needed to write Floor and
Ceiling. (The only reason that they're built-in is that some hardware
supports such operations, and the attributes provide a way to use those
operations.)
Indeed, Janus/Ada implements Floor and Ceiling in software, using A.5.3
attributes.
****************************************************************
From: Geert Bosch
Sent: Tuesday, April 6, 2004 4:22 PM
Hi everyone,
I just joined this list after Gary submitted my comment earlier today.
I'd like to clarify a few points.
> I disagree, at least with respect to Floor and Ceiling. The
> definition of "Floor" given in the RM is the mathematical definition.
> If I were to say Y := Floor(X), I would assume that after this
> statement,
>
> 0.0 <= X-Y < 1.0
>
> regardless of the value of X beforehand. But, assuming "Float" is a
> 32-bit float with a 23-bit mantissa (which really ought to be stated
> in a post like this---we can't assume that every Float in every Ada
> compiler is implemented this way), the proposed definition would, for
> X = 2.0**32, yield a value of Y = Floor(X) where X-Y = 256. This, to
> me, would be unexpected semantics.
I'd like to point out, that (assuming a 32-bit float type T with
T'Machine_Radix = 2 and T'Machine_Mantissa = 24) if X = 2.0**32,
this is an integral machine number, so T'Floor (X) = T'Ceiling (X) = X,
and X - T'Floor (X) = 0.0.
Finally, in a statement
Y := T'Floor (X);
the value in Y is necessarily a machine number and that may
cause the equality 0.0 <= X - T'Floor (X) < 1.0 to be false.
Currently, the following case
X : constant := 2.0**32 - 0.5;
Y : T := T'Floor (X);
will cause Y to be negative: neither of the conditions you gave
are guaranteed to hold for all values of X. With my proposed
change, at least X - T'Floor (X) will be guaranteed not to be
negative. This is in my opinion an important property.
Finally, the 'Floor attribute is stated to return a value of type T,
not a universal_real value, so I feel the current language in the RM
isn't very clear on the intended behavior.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, April 6, 2004 4:50 PM
Geert Bosch wrote:
...
> I'd like to point out, that (assuming a 32-bit float type T with
> T'Machine_Radix = 2 and T'Machine_Mantissa = 24) if X = 2.0**32,
> this is an integral machine number, so T'Floor (X) = T'Ceiling (X) = X,
> and X - T'Floor (X) = 0.0.
I'm sure Adam meant your example of X := 2.0**32-0.5.
> Finally, the 'Floor attribute is stated to return a value of type T,
> not a universal_real value, so I feel the current language in the RM
> isn't very clear on the intended behavior.
So is the result of "*", and the language is just as clear about that:
-- Static expressions are evaluated exactly;
-- Extra precision is allowed in intermediate results.
Thus the result type has nothing to do with the result other than for
resolution purposes.
As Pascal pointed out, the problem you're worrying about comes from the
conversion to a machine number, and that isn't part of the attribute (or any
of the others) at all.
I don't care much about Floor (it's pretty useless), but I'm sure that we
can't make silent incompatibilities because someone has some code that
expects behavior not guaranteed by the standard. It would be better to
delete the attributes altogether (but that of course would be an even worse
incompatibility).
And, then you have to wonder why these attributes are special. It's crystal
clear that you don't want any machine number baloney going on in the
rounding attributes; but Floor and Ceiling are just (funny) rounding
attributes. Why should they be different?
****************************************************************
From: Adam Beneschan
Sent: Tuesday, April 6, 2004 6:11 PM
> I'm sure Adam meant your example of X := 2.0**32-0.5.
Yes, indeed that's what I meant. Sorry about the mistake, which
appears to be an effect of Caffeine Deficiency Syndrome . . .
****************************************************************
From: Tucker Taft
Sent: Tuesday, April 6, 2004 5:19 PM
> ...
> Finally, the 'Floor attribute is stated to return a value of type T,
> not a universal_real value, so I feel the current language in the RM
> isn't very clear on the intended behavior.
Referring to a value of type T does not imply a machine number.
Static arithmetic is infinite precision even for non-univeral-
real floating point types. In fact, universality has relatively
little to do with precision in Ada 95. Staticness is what
matters.
After reading Pascal's argument, I really don't see how
we can change the definition of these attributes. We could
add a note to warn users that they are essentially useless
for non-static calculation when the values are so large that
the integral values are not machine numbers.
I'm not sure what the original example was trying to prove,
but if the goal was to find the largest integral machine number
less than 2**32, I would suggest you use 'Pred.
****************************************************************
From: Adam Beneschan
Sent: Tuesday, April 6, 2004 6:06 PM
That would certainly work in the original example. The way Geert
wanted to redefine 'Floor was as "the largest integral machine number
less than or equal to X". My first reaction to this was that perhaps
this was, for some reason, a useful function, one whose value would be
'Pred if the number was so large that 'Floor couldn't be represented
as a machine number, but would be 'Floor for numbers with smaller
exponents in the range where the delta between consecutive machine
numbers would be <= 0.5. I suggested that if this really was useful,
a new attribute could be proposed.
But it's now occurred to me that this isn't necessary. I believe that
"the largest integral machine number less than or equal to X" can
always be obtained via:
Float'Min (Float'Floor (X), Float'Pred (X))
I haven't yet tried to prove it formally, but I believe that for an
IEEE representation, Float'Pred(X) must always be an integer if
Float'Pred(X) < Float'Floor(X), and that should be true of any other
real-life floating-point representation. Thus the above formula will
return the value in the above proposal.
****************************************************************
From: Geert Bosch
Sent: Tuesday, April 6, 2004 6:13 PM
If X itself is an integral machine number, this is not correct.
Thanks anyway for the help. I've solved the problem (which was
related to range-checking in float-to-integer conversion) directly
in the front-end by using extra tests and adjustments.
****************************************************************
From: Geert Bosch
Sent: Tuesday, April 6, 2004 6:09 PM
> After reading Pascal's argument, I really don't see how
> we can change the definition of these attributes. We could
> add a note to warn users that they are essentially useless
> for non-static calculation when the values are so large that
> the integral values are not machine numbers.
OK. I think we can do without the note, it may only cause
more confusion. The original problem came up in computing
bounds for range/overflow checking in float-to-integer
conversion.
Thanks for everyone's help and views.
****************************************************************
From: Nick Roberts
Sent: Tuesday, April 6, 2004 6:10 PM
> I'm not sure what the original example was trying to prove,
> but if the goal was to find the largest integral machine number
> less than 2**32, I would suggest you use 'Pred.
But I feel that's a bit like saying to users: here's a function that
computes the Floor/Ceiling of a floating point expression; but don't use it,
because it doesn't work properly (under certain conditions), you should
define your own function in terms of Pred/Succ instead.
Why not just fix the standard functions to work properly?
****************************************************************
From: Pascal Leroy
Sent: Wednesday, April 7, 2004 3:38 AM
Nick, by your definition of "properly", no (nonstatic) floating-point
computation works properly. I don't see why you are focusing on Floor here.
Take plain old arithmetic. For some values of X, it is perfectly possible for:
X - 1.0E9 >= X
to return True (just make X big enough).
Anyone believing that (nonstatic) floating-point computations have any
ressemblance to mathematical computations is seriously confused, and should
urgenly re-read Knuth's chapter 4.2.2.
Again, the great thing about static computations is that they match their
mathematical counterparts, and this makes it much easier to reason about them.
****************************************************************
From: Nick Roberts
Sent: Thursday, April 8, 2004 10:46 AM
By 'properly' what I mean is 'usefully'. Plain old (non-static) arithmetic
does not work properly (it is inaccurate), but it is still useful.
I believe that the semantics that will be useful for almost all real,
working software, for Floor and Ceiling, will be what Geert wanted, not what
is currently defined. Whether it is mathematically correct is not what is
required for these functions, in practice, it is what Geert required.
Specifically, I believe the most important property for Floor/Ceiling is
that it never returns a value greater/less than its operand under any
circumstances.
That is why I think the language standard definition of the functions should
be changed.
In order to supply the mathematically correct functions, we need to
introduce something like Universal_Real'Floor and so on.
There also seems (to me) to be implications for Truncation, and a separate
question regarding Remainder seems to have been raised.
This is a complicated issue, and I'm still analysing it (while doing my day
job).
****************************************************************
From: Geert Bosch
Sent: Tuesday, April 6, 2004 5:54 PM
> In fact, I have always assumed that the
> references to machine numbers in A.5.3 do not really apply to static
> computations,
I do not see any grounds to assume that references to machine numbers
in A.5.3 do not apply to static computations. At least the 'Succ,
'Pred, 'Adjacent, 'Remainder and 'Machine attributes would not be
meaningful without this.
The main use of floating-point attributes in static expressions is
to be able to determine how arbitrary precision constants or
constant expressions will be rounded to machine numbers by the
implementation. This way, it is possible to adjust for rounding
errors that result from the finite precision of machine numbers.
****************************************************************
From: Pascal Leroy
Sent: Wednesday, April 7, 2004 3:31 AM
> I do not see any grounds to assume that references to machine numbers
> in A.5.3 do not apply to static computations. At least the 'Succ,
> 'Pred, 'Adjacent, 'Remainder and 'Machine attributes would not be
> meaningful without this.
You're absolutely right regarding Succ, Pred, Adjacent and Machine. Their sole
purpose in life is to expose the properties of the underlying machine numbers,
so of course references to machine numbers in the RM text is appropriate here.
I should have been more precise and I should have mentioned the exact
paragraphs that I believe are faulty. Take for instance Remainder. It's
meaning is perfectly well-defined mathematically, independently of machine
numbers, so any reference to machine numbers in A.5.3(47) is bogus. Consider
the (static) expression:
Float'Remainder (Pi, E)
and assume that Pi and E are static values with a fairly large number of digits
(say 50, as shown in A.5(3)). With the notation of A.5.3(47), n is 1 here, and
v is Pi-E. Surely v is not a machine number of Float (it's somewhere between
two machine numbers) so the result of this attribute is +0.0 by a strict
reading of A.5.3(47). Now that's a clear absurdity and every reasonable
compiler will return Pi-E here.
****************************************************************
From: Tucker Taft
Sent: Thursday, April 8, 2004 8:43 AM
I had never noticed this, but it seems that the definition
of Float'Remainder must simply be wrong. All of the
other attributes talk about returning an "adjacent"
machine number. But this one says zero if the
result is not a machine number. That doesn't make
any sense to me. Maybe it makes sense to someone else...
As far as machine numbers applying to static calculations,
I believe the specification of machine numbers was
intentional for Compose and Scaling (and our compiler definitely
applies 'Machine to the result, even if static). I am
completely confused as to why Remainder is defined
the way it is. All I can think was that someone concluded
that if the result were not a machine number, then one
of the adjacent machine numbers would be zero, and so we
might as well specify that that is the one always chosen.
But your Remainder(Pi,e) example clearly disproves that.
Perhaps it should have said that 'Machine is applied to the
two operands *before* computing the Remainder. In that
case, I guess I can believe the result will always be
a machine number unless you get underflow, in which
case zero seems like a reasonable answer.
Perhaps someone else can shed more light...
****************************************************************
From: Robert A. Duff
Sent: Thursday, April 8, 2004 8:54 AM
I suggest you ask Ken Dritz.
****************************************************************
From: Pascal Leroy
Sent: Thursday, April 8, 2004 10:13 AM
This entire section was written by someone who missed the implications
of static computations. In other words, it was written exclusively with
run-time computations in mind.
As explained in IEC 559, the run-time version of remainder is always
exact. There is one caveat, though: if Y is very close to 0.0 (for
instance if Y is the machine number immediately above 0.0) then the
result may not be representable (because it is always less than Y/2.0).
In this case, returning 0.0 makes sense; however, it smells of
overspecification because I can't find a similar rule in IEC 559.
But clearly, for static computations, the sentence about machine numbers
is absurd...
> Perhaps it should have said that 'Machine is applied to the
> two operands *before* computing the Remainder. In that
> case, I guess I can believe the result will always be
> a machine number unless you get underflow, in which
> case zero seems like a reasonable answer.
No, if you want to apply Machine automatically, you can always do that
yourselves explicitly. If the current wording really gives you the
willies, we could fix it, but let's not change the semantics.
All this verbiage about machine numbers in Remainder, Scaling and the
like is actually completely unnecessary: we don't have anything
equivalent for the basic arithmetic operations "+", "*", etc. All we
say is that they have their usual mathematical meaning, and that (in
strict mode) you get a value in the proper interval. In the case of the
attribute we have to specify their "mathematical meaning", because it's
not obvious, but other than that we should let the strict mode do its
job.
It is actually worse that you think because, unlike all the rest of the
language, it doesn't let you keep more accuracy. If you are on a
Pentium and your type Float corresponds to the 32-bit format, the result
of Scaling has to be a machine number of Float. So if you did the
intermediate computations using the 80-bit format (quite likely on this
architecture ;-), you have to explicitly emit a load/store pair to get
rid of the extra accuracy. Yuck!
****************************************************************
From: Tucker Taft
Sent: Thursday, April 8, 2004 10:39 AM
> This entire section was written by someone who missed the implications
> of static computations. In other words, it was written exclusively with
> run-time computations in mind.
I believe Ken Dritz of Argonne National Lab wrote much of
this. He is no fool. I suspect he wanted the results to
be in terms of machine numbers for Compose, Scaling, and Remainder.
We of course may disagree. However, I wouldn't be surprised
if various compilers implement the attributes as written
(I know we do). For Remainder, they are definitely getting
bizarre answers if they don't first apply 'Machine to the
operands (we don't).
I think we need to fix the wording for Remainder, one way
or the other. For Scaling and Compose, I have no problem
leaving them the way they are as returning machine numbers.
I think that is better than silently changing the behavior.
Also, these seem to be oriented toward manipulating machine
representations more than abstract real values.
On the other hand, Remainder is clearly broken the way it
is currently written, and perhaps we can make our own decision
which way makes more sense. I could go either way,
though I probably have a slight preference for specifying
that 'Machine is applied before using the given algorithm,
but you could convince me the opposite.
> ...
> But clearly, for static computations, the sentence about machine numbers
> is absurd...
Unless 'Machine is applied to the operands.
>
>
>>Perhaps it should have said that 'Machine is applied to the
>>two operands *before* computing the Remainder. In that
>>case, I guess I can believe the result will always be
>>a machine number unless you get underflow, in which
>>case zero seems like a reasonable answer.
>
>
> No, if you want to apply Machine automatically, you can always do that
> yourselves explicitly. If the current wording really gives you the
> willies, we could fix it, but let's not change the semantics.
But what exactly *are* the semantics? For this one, I am
not sure. The notion of a Remainder that is sometimes
positive and sometimes negative depending not on the
sign of the operands, but rather their values, is a bit
bizarre to me, and I really don't know what this is used
for. Is it for argument reduction?
> All this verbiage about machine numbers in Remainder, Scaling and the
> like is actually completely unnecessary: we don't have anything
> equivalent for the basic arithmetic operations "+", "*", etc...
That's true, but these are not basic arithmetic operations.
At least scaling and compose are pretty low level manipulations
that seem pretty oriented toward representation.
Remainder I will admit sounds more "mathematical" and
as I indicated, I could be convinced that any reference to
machine numbers should be removed.
> It is actually worse that you think because, unlike all the rest of the
> language, it doesn't let you keep more accuracy...
I believe that was intentional. You can argue (and you have ;-)
that that was a mistake. Whether it should be changed now is
a debate in my mind. We argued for not changing what the manual
said about Floor for compatibility reasons. The same could
certainly be said about Scaling and Compose. I am at a loss
as to say what to do with Remainder. If we use the "RM never
says anything silly" rule, then the only reasonable interpretation
of Remainder is either that 'Machine is applied to the operands, or
something like "if the result cannot be represented exactly,
the result is zero."
In any case, I think it is a bit of a stretch to allow additional
accuracy in the run-time results of Scaling and Compose,
based on the well-defined wording already present in the manual.
****************************************************************
From: Tucker Taft
Sent: Thursday, April 8, 2004 10:44 AM
One last comment. Scaling and Compose are designed
to be performed by directly manipulating the IEEE
representation, rather than doing multiplication or
division. For Scaling, you just adjust the exponent
and leave the mantissa as is. For Compose you shove
the exponent and the mantissa into the appropriate parts
of the 32-bit or 64-bit representation.
So I don't think the 80-bit intermediate form is really
an issue.
****************************************************************
From: Pascal Leroy
Sent: Thursday, April 8, 2004 11:15 AM
It is when it comes to underflows/overflows. I have always thought that
these attributes should be implemented as efficiently as possible, using
one or a few machine instructions. Now you want me to do an extra
conversion/check to detect underflow/overflow. Sorry, that doesn't
sound like a good idea to me.
****************************************************************
From: Geert Bosch
Sent: Thursday, April 8, 2004 11:44 AM
In order to implement these attributes efficiently, you need to
work on the in-memory representation anyway (which will have caused
rounding to the type's precision). I don't see where the inefficiency
is.
****************************************************************
From: Michael F. Yoder
Sent: Thursday, April 8, 2004 3:01 AM
>As explained in IEC 559, the run-time version of remainder is always
>exact. There is one caveat, though: if Y is very close to 0.0 (for
>instance if Y is the machine number immediately above 0.0) then the
>result may not be representable (because it is always less than Y/2.0).
>In this case, returning 0.0 makes sense; however, it smells of
>overspecification because I can't find a similar rule in IEC 559.
>
If the representation incorporates gradual underflow, as the 'single'
and 'double' IEEE reps do, then the remainder of two machine numbers is
always representable as a machine number. If Y is the machine number
immediately above 0.0, the remainder is always exactly 0.0 when X is a
machine number.
The case cited only obtains if the floating point representation doesn't
use gradual underflow. In that case, converting underflowed results to
0.0 is easier in hardware than other methods. (I don't recall whether
there are machines that do anything different.) I'd be willing to bet
lots of money at even odds that this is the origin of the rule, but I
could be wrong.
Unfortunately, the above suggests that the right generalization to
static expressions differs according to whether the type T incorporates
gradual underflow. I'd say, if it does, the right generalization of
T'Remainder would be to make the remainder exact. If not, I'd say the
right generalization is to force underflowed results to zero, and
otherwise use an exact result.
The "gradual underflow" case corresponds to T'Denorm being true.
It's likely the language in A.5.3(47) is a slip based on the implicit
assumption that the operands are machine numbers (in which case a result
that isn't a machine number must be an underflow).
My personal suggestion: make the result be forced to zero if and only if
T'Denorm is false, and the result R is such that T'Pred(0.0) < R <
T'Succ (0.0).
****************************************************************
From: Pascal Leroy
Sent: Friday, April 9, 2004 3:18 AM
> The case cited only obtains if the floating point representation doesn't
> use gradual underflow. In that case, converting underflowed results to
> 0.0 is easier in hardware than other methods. (I don't recall whether
> there are machines that do anything different.) I'd be willing to bet
> lots of money at even odds that this is the origin of the rule, but I
> could be wrong.
Thank you, Mike, for a very rigorous analysis. You're absolutely
correct here.
> It's likely the language in A.5.3(47) is a slip based on the implicit
> assumption that the operands are machine numbers (in which case a result
> that isn't a machine number must be an underflow).
I concur.
> My personal suggestion: make the result be forced to zero if and only if
> T'Denorm is false, and the result R is such that T'Pred(0.0) < R < T'Succ (0.0).
I find the dependence on T'Denorm unpalatable. However I could probably
live with this: it is much more sensible that Tuck's notion of applying
Machine to the arguments.
****************************************************************
From: Pascal Leroy
Sent: Friday, April 9, 2004 4:35 AM
> I believe Ken Dritz of Argonne National Lab wrote much of this. He is
> no fool.
Right, and the description is exactly correct for nonstatic
computations, as pointed out by Mike Yoder. (I withdraw my previous
comment that the reference to zero in Remainder is overspecification:
Mike convinced me that it is exactly the right thing.)
A little bit of historical context helps here: this section is the
result of integrating ISO/IEC 11729:1994 into the Ada 95 RM. Now 11729
was defining a predefined unit, so staticness was irrelevant.
Unfortunately my copy of 11729 seems to have evaporated so I cannot
check it, but I suspect that the problematic wording must have been
inherited from 11729.
> However, I wouldn't be
> surprised if various compilers implement the attributes as
> written (I know we do).
Sorry, I don't take your word for it. We need a compiler survey here.
The version of GNAT that I am using (arguably oldish) seems to agree
with my interpretation for Compose; to believe that Scaling is never
static; and to be using some form of inexact computation for Remainder
(but not applying Machine to the arguments).
Writing tests for static evaluation is really hard when you don't know
what to expect, so it would be useful for implementers to tell us what
they think they implement.
****************************************************************
From: Tucker Taft
Sent: Friday, April 9, 2004 7:31 AM
We do the following for static computations of these
attributes:
S'Compose: S'Machine(Fraction*S'Machine_Radix**(-k) *
S'Machine_Radix**Exponent)
where "k" is the normalized exponent of Fraction
S'Scaling: S'Machine(X * S'Machine_Radix**Adjustment)
S'Remainder: Let R = X - Y * S'Unbiased_Rounding(X/Y)
if R = S'Machine(R) then R else 0.0 end if
I would suggest that we leave Compose and Scaling as is
(including the application of S'Machine to the result).
S'Remainder makes no sense as is. What do you propose
we do instead? My preference would be to change it
to return S'Machine(R), but making sure that it returns
0 on underflow.
Note that S'Machine takes advantage of denormalized numbers
if they exist on the machine, so there is no need to
explicitly depend on S'Denorm.
****************************************************************
From: Pascal Leroy
Sent: Tuesday, April 13, 2004 7:14 AM
This is _not_ what A.5.3(26, 29) require, btw.
For Scaling, it should be, by a strict reading of the RM:
V = X * S'Machine_Radix**Adjustment;
if abs V >= T'Model_Small or else V = S'Machine (V) then
return V;
else
return S'Machine (V);
end if;
Similarly for Compose.
Your implementation is going to cause trouble when V is large because
S'Machine will raise C_E (or be illegal in the case of static
computations).
****************************************************************
From: Tucker Taft
Sent: Tuesday, April 13, 2004 6:35 AM
Interesting. You are clearly right. The person who implemented
our static evaluation must have thought for some reason just
calling S'Machine would do the right thing. But that again presumes
the inputs are machine numbers. And as you point out, even if
they are machine numbers, it might give the wrong answer for
large positive adjustments.
> Your implementation is going to cause trouble when V is large because
> S'Machine will raise C_E (or be illegal in the case of static
> computations).
Blech. Oh well, it's nice to have somebody else
debug our compiler for a change... ;-)
****************************************************************
From: Randy Brukardt
Sent: Monday, April 26, 2004 8:33 PM
> You _may_ but you don't _need_to_ if you hardware has appropriate
> instructions. On a Pentium you can (should?) implement some of these
> attributes using the FSCALE and FXTRACT instructions, and that won't
> touch memory.
For what it's worth (probably not much), Janus/Ada uses FSCALE and FXTRACT
to implement these operations, and doesn't truncate to machine numbers.
As I recall, these attributes are used frequently in the GEF implementation;
you'd want to control the use of 'Machine only to those parts of the code
where its actually needed.
****************************************************************
From: Geert Bosch
Sent: Wednesday, April 28, 2004 7:33 AM
This seems confused. We were discussing the behavior of these attributes
in a static context. If you would be using machine instructions such
as FSCALE and FXTRACT for the static evaluation, that would not seem
a conforming implementation is the operands are rounded to machine
numbers as well. Of course the results of FSCALE and FXTRACT, which
most likely are used for run-time evaluation, are by definition
machine numbers.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, April 28, 2004 2:49 PM
> This seems confused. We were discussing the behavior of these attributes
> in a static context.
Actually, Pascal was discussing them in a dynamic context as well. But
perhaps you missed that (I only noticed it when I re-read this stuff while
filing it).
> If you would be using machine instructions such
> as FSCALE and FXTRACT for the static evaluation, that would not seem
> a conforming implementation is the operands are rounded to machine
> numbers as well. Of course the results of FSCALE and FXTRACT, which
> most likely are used for run-time evaluation, are by definition
> machine numbers.
No, FSCALE and FXTRACT operate on the Pentium floating point stack, which
are 80-bit numbers. These definitely are not machine numbers for float (for
example). As Pascal notes, in an underflow case, these will be very
different than one of the machine numbers (since the exponents of the 80-bit
numbers go to +/- 1024); an implementation that doesn't truncate such
numbers to zero is wrong, by the wording of the standard.
But that means writing and rereading the values of memory on the Pentium,
which obviously a substantial extra expense. The reason that we recognize
these in the code generator is because they are commonly used in numerical
software (like GEF); and that's the last place we want to be paying a large
extra cost to truncate to machine numbers.
Obviously we could avoid that by adding Machine_Compose and Machine_Scale
attributes, but that seems like overkill. Or we can just not implement
strict mode (that was our solution); but it's hard to see who benefits from
that.
****************************************************************