Oh, another comment...

On 18/12/12 01:36, Oscar Benjamin wrote:

I have often found myself writing awkward functions to prevent a
rounding error from occurring when coercing an object with int().
Here's one:

def make_int(obj):
     '''Coerce str, float and int to int without rounding error
     Accepts strings like '4.0' but not '4.1'
     '''
     fnum = float('%s' % obj)
     inum = int(fnum)
     assert inum == fnum
     return inum


Well, that function is dangerously wrong. In no particular order,
I can find four bugs and one design flaw.

1) It completely fails to work as advertised when Python runs with
optimizations on:


[steve@ando python]$ cat make_int.py
def make_int(obj):
    '''Coerce str, float and int to int without rounding error
    Accepts strings like '4.0' but not '4.1'
    '''
    fnum = float('%s' % obj)
    inum = int(fnum)
    assert inum == fnum
    return inum

print make_int('4.0')
print make_int('4.1')  # this should raise an exception


[steve@ando python]$ python -O make_int.py
4
4


2) Even when it does work, it is misleading and harmful to raise
AssertionError. The problem is with the argument's *value*, hence
*ValueError* is the appropriate exception, not ImportError or
TypeError or KeyError ... or AssertionError. Don't use assert as
a lazy way to get error checking for free.


3) Worse, it falls over when given a sufficiently large int value:

py> make_int(10**500)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in make_int
OverflowError: cannot convert float infinity to integer


but at least you get an exception to warn you that something has
gone wrong.

4) Disturbingly, the function silently does the wrong thing even
for exact integer arguments:

py> n = 10**220  # an exact integer value
py> make_int(n) == n
False


5) It loses precision for string values:

py> s = "1"*200
py> make_int(s) % 10
8L

And not by a little bit:

py> make_int(s) - int(s)  # should be zero
13582401819835255060712844221836126458722074364073358155901190901
52694241435026881979252811708675741954774190693711429563791133046
96544199238575935688832088595759108887701431234301497L


Lest you think that it is only humongous numbers where this is a
problem, it is not. A mere seventeen digits is enough:

py> s = "10000000000000001"
py> make_int(s) - int(s)
-1L


And at that point I stopped looking for faults.



--
Steven
_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor

Reply via email to