I am aware of how the numbers are stored and was overly optimistic that the 
shift could resolve this in all cases. It can't. But my suggested 
alternative makes a significant difference it how often the problem arises:

>>> bad=[]
>>> for i in range(1,1000):
...  n = str(i)+'5'
...  if int(str(round(int(n)/10**len(n),len(n)-1))[-1])%2!=0:bad.append(i)  # 
e.g. round(0.1235, 3)
...
>>> len(bad)
546


>>> bad=[]
>>> for i in range(1,1000):
...  n = str(i)+'5'
...  if round(int(n)/10**len(n)*10**(len(n)-1))%2!=0:bad.append(i)  # e.g. 
round(0.1235*1000)
...
>>> len(bad)
8
>>> bad  # e.g. 0.545*.1000 != 54.5
[54, 57, 501, 503, 505, 507, 509, 511]


So the question is whether we want to do better and keep the SymPy 
algorithm.

On Wednesday, April 10, 2019 at 8:29:46 PM UTC-5, Oscar wrote:
>
> The fact that the numbers are stored in binary is significant: 
>
> In [16]: nums = [eval('1.%d5' % n) for n in range(10)] 
>
> In [17]: nums 
> Out[17]: [1.05, 1.15, 1.25, 1.35, 1.45, 1.55, 1.65, 1.75, 1.85, 1.95] 
>
> In [18]: [round(n, 1) for n in nums] 
> Out[18]: [1.1, 1.1, 1.2, 1.4, 1.4, 1.6, 1.6, 1.8, 1.9, 1.9] 
>
> Proper decimal rounding might look like: 
>
> In [20]: from decimal import Decimal 
>
> In [21]: nums = [Decimal('1.%d5' % n) for n in range(10)] 
>
> In [22]: nums 
> Out[22]: 
> [Decimal('1.05'), 
>  Decimal('1.15'), 
>  Decimal('1.25'), 
>  Decimal('1.35'), 
>  Decimal('1.45'), 
>  Decimal('1.55'), 
>  Decimal('1.65'), 
>  Decimal('1.75'), 
>  Decimal('1.85'), 
>  Decimal('1.95') 
>
> In [23]: [round(n, 1) for n in nums] 
> Out[23]: 
> [Decimal('1.1'), 
>  Decimal('1.2'), 
>  Decimal('1.3'), 
>  Decimal('1.4'), 
>  Decimal('1.5'), 
>  Decimal('1.6'), 
>  Decimal('1.7'), 
>  Decimal('1.8'), 
>  Decimal('1.9'), 
>  Decimal('2.0')] 
>
> Or with half-even rounding: 
>
> In [24]: from decimal import getcontext 
>
> In [25]: getcontext().rounding = 'ROUND_HALF_EVEN' 
>
> In [26]: [round(n, 1) for n in nums] 
> Out[26]: 
> [Decimal('1.0'), 
>  Decimal('1.2'), 
>  Decimal('1.2'), 
>  Decimal('1.4'), 
>  Decimal('1.4'), 
>  Decimal('1.6'), 
>  Decimal('1.6'), 
>  Decimal('1.8'), 
>  Decimal('1.8'), 
>  Decimal('2.0')] 
>
> The binary floats don't work out correct because some are above and 
> some are below the number suggested by the original float literal. 
>
> On Thu, 11 Apr 2019 at 02:03, Chris Smith <smi...@gmail.com <javascript:>> 
> wrote: 
> > 
> > That floats are stored in binary is an implementation detail which need 
> not prevent base-10 rounding to still work. The 2nd argument to round is 
> intended to tell at which base-10 digit the rounding is to take place. By 
> shifting that digit to the ones position and then doing the rounding one 
> can easily detect whether what follows is above or below a half. 
> > 
> > >>> .345.as_integer_ratio() 
> > (1553741871442821, 4503599627370496) 
> > >>> (_*100).as_integer_ratio() 
> > (69, 2) 
> > 
> > "round to even" means "if what follows the digit to which you are 
> rounding is 5 then round so your digit is even". As is shown in the integer 
> ratios above, what follows the 4 is exactly 1/2 so we can round to the even 
> 4 (as in .34) instead of the odd 5 (as in .35) just like round(0.75,1) 
> rounds to 0.8 while round(0.25) rounds to 0.2. 
> > 
> > On Wednesday, April 10, 2019 at 7:29:18 PM UTC-5, Aaron Meurer wrote: 
> >> 
> >> Doesn't Python do rounding based on the binary representation of the 
> float? 
> >> 
> >> I'm a little confused what "round to even" means in that case. 
> >> 
> >> Aaron Meurer 
> >> 
> >> On Wed, Apr 10, 2019 at 6:16 PM Chris Smith <smi...@gmail.com> wrote: 
> >> > 
> >> > Python 3 implements "round to even on tie" logic so `round(12.5)` -> 
> 12,  not 13. I have updated, in #16608, the round function but there is a 
> difference in how ties are detected. I shift the desired position to the 
> ones position and then check for a tie so 12.345 is shifted to 1234.5 and 
> rounded to 1234 then is divided by 100 to give 12.34. Python doesn't do 
> this. I suspect it adds 0.05 and then detects that12.395 > 12395/1000 and 
> rounds up to 12.35 
> >> > 
> >> > 
> >> > >>> Rational(*.345.as_integer_ratio())-Rational(345,1000)  # .345 < 
> 345/1000 
> >> > -3/112589990684262400 
> >> > >>> Rational(*.395.as_integer_ratio())-Rational(395,1000)  # .395 > 
> 395/1000 
> >> > 1/56294995342131200 
> >> > 
> >> > 
> >> > >>> round(12.345,2)  # 12.345 rounds up b/c a tie is not detected 
> >> > 12.35 
> >> > 
> >> > 
> >> > 
> >> > Does anyone have objections to the proposed rounding? 
> >> > 
> >> > /c 
> >> > 
> >> > -- 
> >> > You received this message because you are subscribed to the Google 
> Groups "sympy" group. 
> >> > To unsubscribe from this group and stop receiving emails from it, 
> send an email to sy...@googlegroups.com. 
> >> > To post to this group, send email to sy...@googlegroups.com. 
> >> > Visit this group at https://groups.google.com/group/sympy. 
> >> > To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sympy/a84085c6-aa90-437c-b063-a87f909beac4%40googlegroups.com.
>  
>
> >> > For more options, visit https://groups.google.com/d/optout. 
> > 
> > -- 
> > You received this message because you are subscribed to the Google 
> Groups "sympy" group. 
> > To unsubscribe from this group and stop receiving emails from it, send 
> an email to sy...@googlegroups.com <javascript:>. 
> > To post to this group, send email to sy...@googlegroups.com 
> <javascript:>. 
> > Visit this group at https://groups.google.com/group/sympy. 
> > To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sympy/7873bfe6-6f91-4d39-af6f-9f4039714fa8%40googlegroups.com.
>  
>
> > For more options, visit https://groups.google.com/d/optout. 
>

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sympy+unsubscr...@googlegroups.com.
To post to this group, send email to sympy@googlegroups.com.
Visit this group at https://groups.google.com/group/sympy.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sympy/0919f8f2-ded1-401e-af97-9e6a68ad47ea%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to