* 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