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