comp.lang.ada
 help / color / mirror / Atom feed
* How to round to the nearest fixed-point value?
@ 2014-01-22 16:48 Natasha Kerensikova
  2014-01-22 17:53 ` G.B.
                   ` (2 more replies)
  0 siblings, 3 replies; 15+ messages in thread
From: Natasha Kerensikova @ 2014-01-22 16:48 UTC (permalink / raw)


Hello,

from what I understood of the LRM (at least in Ada 2005 mode),
Fixed_Point_Type'Round is supposed to be a function returning a
Fixed_Point_Type value closest to its argument.

So I'm a bit surprised by the behavior of the following code:


package Lib is
   type High is delta 0.001 digits 9;
   type Low is delta 0.01 digits 9;

   function Convert (Value : High) return Low;
end Lib;


package body Lib is

   function Convert (Value : High) return Low is
   begin
      return Low'Round (Value);
   end Convert;

end Lib;


with Ada.Text_IO;
with Lib;

procedure Testcase is
   Raw_Value : constant Lib.High := 0.999;
   Shown_Value : Lib.Low;
begin
   Ada.Text_IO.Put_Line (Lib.Low'Image (Lib.Low'Round (Raw_Value)));
   Shown_Value := Lib.Convert (Raw_Value);
   Ada.Text_IO.Put_Line (Lib.Low'Image (Shown_Value));
end Testcase;



I tried building it with gnat 4.6.3 from debian stable kfreebsd and with
gnat-aux 4.7.3 from FreeBSD ports, and in both situations I got the
following output:
 1.00
 0.99


Is there something I'm doing wrong that would trigger the truncation at
some point? Or is this a compiler bug? Would anyone know a workaround to
get correct results?


Thanks in advance for your help,
Natasha

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-22 16:48 How to round to the nearest fixed-point value? Natasha Kerensikova
@ 2014-01-22 17:53 ` G.B.
  2014-01-22 22:26   ` adambeneschan
  2014-01-22 22:45 ` adambeneschan
  2014-01-24  9:58 ` Natasha Kerensikova
  2 siblings, 1 reply; 15+ messages in thread
From: G.B. @ 2014-01-22 17:53 UTC (permalink / raw)


On 22.01.14 17:48, Natasha Kerensikova wrote:
>     function Convert (Value : High) return Low is
>     begin
>        return Low'Round (Value);
>     end Convert;

In view of LRM 4.6, Numeric Type Conversion,
I speculate that the compiler might be right. If only I knew
how truncation might play a role(+):

      * If the target type is a decimal fixed point type, then
        the result is truncated (toward 0) if the value of the
        operand is not a multiple of the small of the target
        type.

Then, rewriting,

    function Convert (Value : High) return Low is
       Result : constant High := High'Round (Value);
    begin
       return Low (Result);
    end Convert;

Result has 0.999 exactly if this is a multiple of High'Small
(it is, with GNAT on Intel), but converting to Low truncates
because Low'Small is too big by a factor of 10.

__
(+) Low'Round takes a universal_real (and returns Low'Base); does
universality affect things?

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-22 17:53 ` G.B.
@ 2014-01-22 22:26   ` adambeneschan
  2014-01-23  9:21     ` Georg Bauhaus
  0 siblings, 1 reply; 15+ messages in thread
From: adambeneschan @ 2014-01-22 22:26 UTC (permalink / raw)


On Wednesday, January 22, 2014 9:53:29 AM UTC-8, G.B. wrote:
> On 22.01.14 17:48, Natasha Kerensikova wrote:
> 
> >     function Convert (Value : High) return Low is
> >     begin
> >        return Low'Round (Value);
> >     end Convert;
> 
> In view of LRM 4.6, Numeric Type Conversion,
> I speculate that the compiler might be right. If only I knew
> how truncation might play a role(+):
> 
>       * If the target type is a decimal fixed point type, then
>         the result is truncated (toward 0) if the value of the
>         operand is not a multiple of the small of the target
>         type.
> 
> Then, rewriting,
> 
>     function Convert (Value : High) return Low is
>        Result : constant High := High'Round (Value);
>     begin
>        return Low (Result);
>     end Convert;

This rewrite has nothing to do with the OP's code.  The OP's program does not perform High'Round anywhere, nor does it have any type conversions to which 4.6 would apply.

                               -- Adam


> Result has 0.999 exactly if this is a multiple of High'Small
> (it is, with GNAT on Intel), but converting to Low truncates
> because Low'Small is too big by a factor of 10.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-22 16:48 How to round to the nearest fixed-point value? Natasha Kerensikova
  2014-01-22 17:53 ` G.B.
@ 2014-01-22 22:45 ` adambeneschan
  2014-01-23  5:29   ` J-P. Rosen
  2014-01-23  7:02   ` Natasha Kerensikova
  2014-01-24  9:58 ` Natasha Kerensikova
  2 siblings, 2 replies; 15+ messages in thread
From: adambeneschan @ 2014-01-22 22:45 UTC (permalink / raw)


On Wednesday, January 22, 2014 8:48:26 AM UTC-8, Natasha Kerensikova wrote:

> package Lib is
>    type High is delta 0.001 digits 9;
>    type Low is delta 0.01 digits 9;
>
>    function Convert (Value : High) return Low;
> end Lib;
> 
> package body Lib is
>    function Convert (Value : High) return Low is
>    begin
>       return Low'Round (Value);
>    end Convert;
> end Lib;

> with Ada.Text_IO;
> with Lib;
> procedure Testcase is
>    Raw_Value : constant Lib.High := 0.999;
>    Shown_Value : Lib.Low;
> begin
>    Ada.Text_IO.Put_Line (Lib.Low'Image (Lib.Low'Round (Raw_Value)));
>    Shown_Value := Lib.Convert (Raw_Value);
>    Ada.Text_IO.Put_Line (Lib.Low'Image (Shown_Value));
> end Testcase;

>  1.00
>  0.99

I'm pretty sure this is a compiler bug.  Since Low'Small is a multiple of High'Small, you can work around it by changing the definition of Convert to

    return Low (Value + Low'Small / 2);

(Low'Small is a universal real and therefore Low'Small/2 will be interpreted as a value of type High.  The type conversion to Low will truncate.)

                            -- Adam

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-22 22:45 ` adambeneschan
@ 2014-01-23  5:29   ` J-P. Rosen
  2014-01-23  7:00     ` Natasha Kerensikova
  2014-01-23  9:42     ` Georg Bauhaus
  2014-01-23  7:02   ` Natasha Kerensikova
  1 sibling, 2 replies; 15+ messages in thread
From: J-P. Rosen @ 2014-01-23  5:29 UTC (permalink / raw)


Le 22/01/2014 23:45, adambeneschan@gmail.com a écrit :
>>  1.00
>> >  0.99
> I'm pretty sure this is a compiler bug.

I concurr. Most likely, the first expression is evaluated at compile
time and there is a bug in 'round of the static evaluator.
-- 
J-P. Rosen
Adalog
2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX
Tel: +33 1 45 29 21 52, Fax: +33 1 45 29 25 00
http://www.adalog.fr


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-23  5:29   ` J-P. Rosen
@ 2014-01-23  7:00     ` Natasha Kerensikova
  2014-01-23  9:42     ` Georg Bauhaus
  1 sibling, 0 replies; 15+ messages in thread
From: Natasha Kerensikova @ 2014-01-23  7:00 UTC (permalink / raw)


On 2014-01-23, J-P. Rosen <rosen@adalog.fr> wrote:
> Le 22/01/2014 23:45, adambeneschan@gmail.com a écrit :
>>>  1.00
>>> >  0.99
>> I'm pretty sure this is a compiler bug.
>
> I concurr. Most likely, the first expression is evaluated at compile
> time and there is a bug in 'round of the static evaluator.

Well, unless I'm misunderstanding something, it's actually the first
expression that's correct: Low'Round (0.999) is supposed to be 1.00,
while Low (High'(0.999)) should be 0.99, right?


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-22 22:45 ` adambeneschan
  2014-01-23  5:29   ` J-P. Rosen
@ 2014-01-23  7:02   ` Natasha Kerensikova
  2014-01-23 16:41     ` adambeneschan
  1 sibling, 1 reply; 15+ messages in thread
From: Natasha Kerensikova @ 2014-01-23  7:02 UTC (permalink / raw)


On 2014-01-22, adambeneschan@gmail.com <adambeneschan@gmail.com> wrote:
> I'm pretty sure this is a compiler bug.  Since Low'Small is a multiple of High'Small, you can work around it by changing the definition of Convert to
>
>     return Low (Value + Low'Small / 2);
>
> (Low'Small is a universal real and therefore Low'Small/2 will be interpreted as a value of type High.  The type conversion to Low will truncate.)

I have been considering that, however it seems type conversion truncates
towards zero, so the expression above is only correct for positive
Values, while Low'Small/2 should be subtracted when Value is negative.

Or am I missing something?


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-22 22:26   ` adambeneschan
@ 2014-01-23  9:21     ` Georg Bauhaus
  0 siblings, 0 replies; 15+ messages in thread
From: Georg Bauhaus @ 2014-01-23  9:21 UTC (permalink / raw)


On 22.01.14 23:26, adambeneschan@gmail.com wrote:
> This rewrite has nothing to do with the OP's code.  The OP's program does not perform High'Round anywhere, nor does it have any type conversions to which 4.6 would apply.

I was silently hoping I was entirely wrong (and the confusing matter
of "'Round may be used to imply explicit conversion with rounding",
if seen accidentally, and seen out of context (G.2.3/4) should make
me shut up.)

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-23  5:29   ` J-P. Rosen
  2014-01-23  7:00     ` Natasha Kerensikova
@ 2014-01-23  9:42     ` Georg Bauhaus
  1 sibling, 0 replies; 15+ messages in thread
From: Georg Bauhaus @ 2014-01-23  9:42 UTC (permalink / raw)


On 23.01.14 06:29, J-P. Rosen wrote:
> Le 22/01/2014 23:45, adambeneschan@gmail.com a écrit :
>>>   1.00
>>>>   0.99
>> I'm pretty sure this is a compiler bug.
>
> I concurr. Most likely, the first expression is evaluated at compile
> time and there is a bug in 'round of the static evaluator.

When the value is read from input, the effect is still present, though.

I don't really know how to read -gnatdg, thus superficial observation
yields the follow as the way that Convert computes the returned object:

   T2b : constant lib__TlowB :=
     lib__TlowB?({lib__TlowB?(value #/ 1.00E-1)});



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-23  7:02   ` Natasha Kerensikova
@ 2014-01-23 16:41     ` adambeneschan
  0 siblings, 0 replies; 15+ messages in thread
From: adambeneschan @ 2014-01-23 16:41 UTC (permalink / raw)


On Wednesday, January 22, 2014 11:02:59 PM UTC-8, Natasha Kerensikova wrote:
> 
> > I'm pretty sure this is a compiler bug.  Since Low'Small is a multiple of High'Small, you can work around it by changing the definition of Convert to
> 
> >     return Low (Value + Low'Small / 2);
> 
> > (Low'Small is a universal real and therefore Low'Small/2 will be interpreted as a value of type High.  The type conversion to Low will truncate.)
> 
> 
> 
> I have been considering that, however it seems type conversion truncates
> towards zero, so the expression above is only correct for positive
> Values, while Low'Small/2 should be subtracted when Value is negative.
> 
> Or am I missing something?

My fault, I forgot about negative values.

                              -- Adam

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-22 16:48 How to round to the nearest fixed-point value? Natasha Kerensikova
  2014-01-22 17:53 ` G.B.
  2014-01-22 22:45 ` adambeneschan
@ 2014-01-24  9:58 ` Natasha Kerensikova
  2014-01-24 22:30   ` Randy Brukardt
  2 siblings, 1 reply; 15+ messages in thread
From: Natasha Kerensikova @ 2014-01-24  9:58 UTC (permalink / raw)


Hello,

On 2014-01-22, Natasha Kerensikova <lithiumcat@gmail.com> wrote:
>    function Convert (Value : High) return Low is
>    begin
>       return Low'Round (Value);
>    end Convert;

In case anyone is interested, the following rewrite works around the
problem:

   function Convert (Value : High) return Low is
   begin
      return Low'Round (Value * 1.0);
   end Convert;

I would have thought that the optimizer would have quickly brought both
version to the same generated code, but from -O0 to -O3 I get the
correct rounding with the latter but not with the former.

I guess that's enough to conclude there is a compiler bug somewhere (or
a weird bug in the language). I'll have to send that to AdaCore...


Natasha


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-24  9:58 ` Natasha Kerensikova
@ 2014-01-24 22:30   ` Randy Brukardt
  2014-01-24 22:47     ` Randy Brukardt
  2014-01-26 14:19     ` Natasha Kerensikova
  0 siblings, 2 replies; 15+ messages in thread
From: Randy Brukardt @ 2014-01-24 22:30 UTC (permalink / raw)


"Natasha Kerensikova" <lithiumcat@gmail.com> wrote in message 
news:slrnle4e9r.1lme.lithiumcat@sigil.instinctive.eu...
> Hello,
>
> On 2014-01-22, Natasha Kerensikova <lithiumcat@gmail.com> wrote:
>>    function Convert (Value : High) return Low is
>>    begin
>>       return Low'Round (Value);
>>    end Convert;
>
> In case anyone is interested, the following rewrite works around the
> problem:
>
>   function Convert (Value : High) return Low is
>   begin
>      return Low'Round (Value * 1.0);
>   end Convert;

I believe the above should be illegal - but in any case, it's not clear from 
the RM what this means.

The only multiply operations available for fixed point types either have one 
operand that is an integer (not the case above), or produce a 
Universal_Fixed result. 4.5.5(19.1/2) requires that the result be used in a 
context where the result type is identified, and specifically disallows the 
result from being Universal_Fixed.

The type of the operand of 'Round is Universal_Real. This appears to meet 
the letter of the rule, but not the intent (which is that the expected type 
determine the scaling and respresentation for the result).

> I would have thought that the optimizer would have quickly brought both
> version to the same generated code, but from -O0 to -O3 I get the
> correct rounding with the latter but not with the former.

No, the result of the operand here is Universal_Real. I believe the language 
says that runtime universal values are evaluated using the largest possible 
numeric type; in this case, that would be a large float type. It's very 
difficult to optimize out float operations (because of precision issues - 
you have to be very careful that the precision of the result is the same 
after optimization), so it probably can't be changed. So you're getting 
vastly different code.

> I guess that's enough to conclude there is a compiler bug somewhere (or
> a weird bug in the language). I'll have to send that to AdaCore...

I think there might be a bug in the language here (that is, for your 
rewrite, not for your original code), but I'll ask the ARG.

                           Randy.


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-24 22:30   ` Randy Brukardt
@ 2014-01-24 22:47     ` Randy Brukardt
  2014-01-26 14:19     ` Natasha Kerensikova
  1 sibling, 0 replies; 15+ messages in thread
From: Randy Brukardt @ 2014-01-24 22:47 UTC (permalink / raw)


>> In case anyone is interested, the following rewrite works around the
>> problem:
>>
>>   function Convert (Value : High) return Low is
>>   begin
>>      return Low'Round (Value * 1.0);
>>   end Convert;

To clarify a bit, I think this operates identically to:

   function Convert (Value : High) return Low is
   begin
      return Low'Round (Long_Float(Value));
   end Convert;

which probably works around any GNAT bug but also is injecting the 
possibility of floating point precision issues into your code. So I'm not 
sure it is a good idea (if Long_Float has enough bits, it might not matter).

                                           Randy.


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-24 22:30   ` Randy Brukardt
  2014-01-24 22:47     ` Randy Brukardt
@ 2014-01-26 14:19     ` Natasha Kerensikova
  2014-01-28 23:43       ` Randy Brukardt
  1 sibling, 1 reply; 15+ messages in thread
From: Natasha Kerensikova @ 2014-01-26 14:19 UTC (permalink / raw)


On 2014-01-24, Randy Brukardt <randy@rrsoftware.com> wrote:
> "Natasha Kerensikova" <lithiumcat@gmail.com> wrote in message 
>> In case anyone is interested, the following rewrite works around the
>> problem:
>>
>>   function Convert (Value : High) return Low is
>>   begin
>>      return Low'Round (Value * 1.0);
>>   end Convert;
>
> I believe the above should be illegal - but in any case, it's not clear from 
> the RM what this means.
>
> The only multiply operations available for fixed point types either have one 
> operand that is an integer (not the case above), or produce a 
> Universal_Fixed result. 4.5.5(19.1/2) requires that the result be used in a 
> context where the result type is identified, and specifically disallows the 
> result from being Universal_Fixed.
>
> The type of the operand of 'Round is Universal_Real. This appears to meet 
> the letter of the rule, but not the intent (which is that the expected type 
> determine the scaling and respresentation for the result).

Could it be somehow be "made legal" by G.2.3(4), merging "*" and
Low'Round into a single operation? (sounds a bit weird though, that
would mean an optional annex affecting legality)

This gets me worried, because in the "real" application where I
encountered this, Low'Round (Value) is one possibility of a case
statement, and all others are like Low'Round (Value * decimal_literal)
most of those literals being more precise than what can be handled by
High or Low (e.g. 6.55957). I expected the math to be handled properly,
but maybe I should look a bit closer at those cases too.

Is there a better way of expressing it?

Maybe something along the lines of
Low'Round (Value * High'(6.559) + (Value / 1000) * High'(0.57))
but with better thought on rounding.
However I would expect the compiler to be better than me at coming up
with a formula to accurately compute that.

On a site note, using Low'Round (Value * 1) behave in the same (wrong?)
way as Low'Round (Value).

>> I would have thought that the optimizer would have quickly brought both
>> version to the same generated code, but from -O0 to -O3 I get the
>> correct rounding with the latter but not with the former.
>
> No, the result of the operand here is Universal_Real. I believe the language 
> says that runtime universal values are evaluated using the largest possible 
> numeric type; in this case, that would be a large float type. It's very 
> difficult to optimize out float operations (because of precision issues - 
> you have to be very careful that the precision of the result is the same 
> after optimization), so it probably can't be changed. So you're getting 
> vastly different code.

Funnily, both versions lead to very similar code in GNAT internal
ada-ish language (output by -gnatD):

Low'Round (Value) yields:
T2b : constant lib__TlowB := lib__TlowB?({lib__TlowB?(value #{/} 1.0E-2)});

Low'Round (Value * 1.0) yeilds:
T2b : constant lib__TlowB := lib__TlowB?({lib__TlowB?(value #@{/} 1.0E-2)});

As far as 64-bit intel assembly code goes, it indeed looks vastly
different, but it involves only integer arithmetic, without any obvious
bound restriction.



Thanks for your help,
Natasha

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: How to round to the nearest fixed-point value?
  2014-01-26 14:19     ` Natasha Kerensikova
@ 2014-01-28 23:43       ` Randy Brukardt
  0 siblings, 0 replies; 15+ messages in thread
From: Randy Brukardt @ 2014-01-28 23:43 UTC (permalink / raw)


"Natasha Kerensikova" <lithiumcat@gmail.com> wrote in message 
news:slrnlea6aj.1lme.lithiumcat@sigil.instinctive.eu...
> On 2014-01-24, Randy Brukardt <randy@rrsoftware.com> wrote:
>> "Natasha Kerensikova" <lithiumcat@gmail.com> wrote in message
>>> In case anyone is interested, the following rewrite works around the
>>> problem:
>>>
>>>   function Convert (Value : High) return Low is
>>>   begin
>>>      return Low'Round (Value * 1.0);
>>>   end Convert;
>>
>> I believe the above should be illegal - but in any case, it's not clear 
>> from
>> the RM what this means.
>>
>> The only multiply operations available for fixed point types either have 
>> one
>> operand that is an integer (not the case above), or produce a
>> Universal_Fixed result. 4.5.5(19.1/2) requires that the result be used in 
>> a
>> context where the result type is identified, and specifically disallows 
>> the
>> result from being Universal_Fixed.
>>
>> The type of the operand of 'Round is Universal_Real. This appears to meet
>> the letter of the rule, but not the intent (which is that the expected 
>> type
>> determine the scaling and respresentation for the result).
>
> Could it be somehow be "made legal" by G.2.3(4), merging "*" and
> Low'Round into a single operation? (sounds a bit weird though, that
> would mean an optional annex affecting legality)

Tucker says that it's legal, and that G.2.3(4) defines what it means. I'm 
dubious about that last part, but mainly for wording reasons (the wording 
ought to say that 'Round *is* a conversion for the purposes of the rules in 
G.2.3; "implying" a conversion is vague at best). This appears to be a 
"hack" in the language that I wasn't aware of; I agree with you that it's 
weird that it's only defined in an optional annex -- but that's true for 
much of the semantics of float and fixed numbers so it follows the existing 
pattern.

So probably you ought to ignore my previous notes on this topic, sorry about 
that.

                                  Randy.


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2014-01-28 23:43 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-01-22 16:48 How to round to the nearest fixed-point value? Natasha Kerensikova
2014-01-22 17:53 ` G.B.
2014-01-22 22:26   ` adambeneschan
2014-01-23  9:21     ` Georg Bauhaus
2014-01-22 22:45 ` adambeneschan
2014-01-23  5:29   ` J-P. Rosen
2014-01-23  7:00     ` Natasha Kerensikova
2014-01-23  9:42     ` Georg Bauhaus
2014-01-23  7:02   ` Natasha Kerensikova
2014-01-23 16:41     ` adambeneschan
2014-01-24  9:58 ` Natasha Kerensikova
2014-01-24 22:30   ` Randy Brukardt
2014-01-24 22:47     ` Randy Brukardt
2014-01-26 14:19     ` Natasha Kerensikova
2014-01-28 23:43       ` Randy Brukardt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox