!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. ****************************************************************