Re: [sympy] rounding difference between Python 3 and SymPy

2019-04-11 Thread Chris Smith
The question is whether we can, with limitations of binary representation, 
give a result that is consistent with what we would expect if using base-10 
notation. The advantage that SymPy and Decimal have is that they know, by 
merit of values given at instantiation, what the last significant digit is. 
So if we round to that digit and use only those digits when making the 
decision when rounding to fewer places we can get it right. e.g. 0.345 is 
stored as something like '0.344' If we know we are working with 
a precision of 3 then we can multiply by 1000, round to int and now use 
that number (345) to round to the second digit and use tie-breaking logic 
to give the result as 340 instead of 350 and return a final result of 0.340.

 >>> def r(n,p,prec):
 ... if p > prec:
 ... return n
 ... i = int(round(n*10**prec))
 ... m = 10**(prec-p)
 ... i, r = divmod(i, m)
 ... if i%2 and 2*r==m:
 ... i -= 1
 ... return i/float(10**p)
 ... 
 >>> bad=[]
 ... for i in range(1,1000):
 ...   n = str(i)+'5'
 ... if int(str(r(eval('.'+n),len(n)-1,len(n)))[:len(n)+1][-1])%2!=0:bad.
append(i)
 ... 
 >>> len(bad)
 0 
 >>> r(.545, 2, 3)
 0.540 
 >>> round(.545,2)
 0.55

So I guess the answer to the original question is that it's ok for Decimal 
or SymPy to give a different result than Python when dealing with 
non-native number objects.

I will double check that SymPy's round is doing what it should and not 
generating any "incorrect/bad" results.

/c

On Thursday, April 11, 2019 at 6:17:27 AM UTC-5, Oscar wrote:
>
> I think that Python's float.__round__ is correct. AIUI it rounds 
> correctly based on the true value represented by the float: 
>
> In [4]: round(1.05, 1) 
> Out[4]: 1.1 
>
> In [5]: import decimal 
>
> In [6]: decimal.Decimal(1.1) 
> Out[6]: Decimal('1.100088817841970012523233890533447265625') 
>
> That's surprising just because the true exact value isn't what we 
> thought it should be. There is a rounding error but it happens in the 
> literal 1.1 that doesn't give us the float we think it should. It's 
> extra confusing because str(1.1) gives '1.1' making it seem like the 
> exact number we requested. The behaviour of round is mathematically 
> correct here though given the input received: it is computing the true 
> exact result correctly rounded (where "correctly rounded" refers to 
> the decimal to binary rounding at the end). 
>
> For decimals that can be exactly represented in binary the results are 
> as we would intuitively expect: 
>
> In [7]: round(1.25, 1) 
> Out[7]: 1.2 
>
> In [8]: round(1.75, 1) 
> Out[8]: 1.8 
>
> (Of course 1.2 and 1.8 are not exactly representable in binary 
> floating point but we have the floats that result from correctly 
> rounding the true numbers 1.2 and 1.8.) 
>
> -- 
> Oscar 
>
> On Thu, 11 Apr 2019 at 05:29, Chris Smith > 
> wrote: 
> > 
> > I am aware of how the numbers are stored but was overly optimistic that 
> the shift could resolve this in all cases. It can't (and thanks for the 
> correction). 
> > But my suggested alternative makes a significant difference in 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*100 != 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. 
> > 
> > -- 
> > 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/b50c0504-ec96-4583-84b1-d5a3902277c2%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/fb01d0ad-049e-48c2-9b43-a8029bdf5328%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [sympy] rounding difference between Python 3 and SymPy

2019-04-11 Thread Oscar Benjamin
I think that Python's float.__round__ is correct. AIUI it rounds
correctly based on the true value represented by the float:

In [4]: round(1.05, 1)
Out[4]: 1.1

In [5]: import decimal

In [6]: decimal.Decimal(1.1)
Out[6]: Decimal('1.100088817841970012523233890533447265625')

That's surprising just because the true exact value isn't what we
thought it should be. There is a rounding error but it happens in the
literal 1.1 that doesn't give us the float we think it should. It's
extra confusing because str(1.1) gives '1.1' making it seem like the
exact number we requested. The behaviour of round is mathematically
correct here though given the input received: it is computing the true
exact result correctly rounded (where "correctly rounded" refers to
the decimal to binary rounding at the end).

For decimals that can be exactly represented in binary the results are
as we would intuitively expect:

In [7]: round(1.25, 1)
Out[7]: 1.2

In [8]: round(1.75, 1)
Out[8]: 1.8

(Of course 1.2 and 1.8 are not exactly representable in binary
floating point but we have the floats that result from correctly
rounding the true numbers 1.2 and 1.8.)

--
Oscar

On Thu, 11 Apr 2019 at 05:29, Chris Smith  wrote:
>
> I am aware of how the numbers are stored but was overly optimistic that the 
> shift could resolve this in all cases. It can't (and thanks for the 
> correction).
> But my suggested alternative makes a significant difference in 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*100 != 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.
>
> --
> 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/b50c0504-ec96-4583-84b1-d5a3902277c2%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/CAHVvXxS8kCZRXuXpoHJCWiXOZUjxeAMhY%3DRQPthEKkA_A_tdCQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.