Numeric coercions

  • Thread starter Steven D'Aprano
  • Start date
S

Steven D'Aprano

I sometimes find myself needing to promote[1] arbitrary numbers
(Decimals, Fractions, ints) to floats. E.g. I might say:

numbers = [float(num) for num in numbers]

or if you prefer:

numbers = map(float, numbers)

The problem with this is that if a string somehow gets into the original
numbers, it will silently be converted to a float when I actually want a
TypeError. So I want something like this:

def promote(x):
if isinstance(x, str): raise TypeError
return float(x)

but I don't like the idea of calling isinstance on every value. Is there
a better way to do this? E.g. some operation which is guaranteed to
promote any numeric type to float, but not strings?

For the record, calling promote() as above is about 7 times slower than
calling float in Python 3.3.




[1] Or should that be demote?
 
J

Joshua Landau

I sometimes find myself needing to promote[1] arbitrary numbers
(Decimals, Fractions, ints) to floats. E.g. I might say:

numbers = [float(num) for num in numbers]

or if you prefer:

numbers = map(float, numbers)

The problem with this is that if a string somehow gets into the original
numbers, it will silently be converted to a float when I actually want a
TypeError. So I want something like this:

def promote(x):
if isinstance(x, str): raise TypeError
return float(x)

but I don't like the idea of calling isinstance on every value. Is there
a better way to do this? E.g. some operation which is guaranteed to
promote any numeric type to float, but not strings?

For the record, calling promote() as above is about 7 times slower than
calling float in Python 3.3.
Traceback (most recent call last):
 
J

Joshua Landau

Nice!

That's almost as fast as calling float. That will probably do :)

*Almost* as fast?

%~> \python -m timeit -s "safe_float =
__import__('operator').methodcaller('__float__'); n = 423.213"
"float(n)"
1000000 loops, best of 3: 0.398 usec per loop
%~> \python -m timeit -s "safe_float =
__import__('operator').methodcaller('__float__'); n = 423.213"
"safe_float(n)"
1000000 loops, best of 3: 0.361 usec per loop

%~> \python -m timeit -s "safe_float =
__import__('operator').methodcaller('__float__'); n = 423" "float(n)"
1000000 loops, best of 3: 0.468 usec per loop
%~> \python -m timeit -s "safe_float =
__import__('operator').methodcaller('__float__'); n = 423"
"safe_float(n)"
1000000 loops, best of 3: 0.436 usec per loop

Actually, it's a fair bit faster (yes, I am purposely not showing you
the results for Decimals).
 
J

Joshua Landau

*Almost* as fast?
*incorrect timings*

Actually, it's a fair bit faster (yes, I am purposely not showing you
the results for Decimals).

*sigh*, it seems that was just because it's slower to access
__builtins__ than globals(). float() is maybe 2% faster if you fix
that.
 
V

Vlastimil Brom

2013/7/7 Steven D'Aprano said:
I sometimes find myself needing to promote[1] arbitrary numbers
(Decimals, Fractions, ints) to floats. E.g. I might say:

numbers = [float(num) for num in numbers]

or if you prefer:

numbers = map(float, numbers)

The problem with this is that if a string somehow gets into the original
numbers, it will silently be converted to a float when I actually want a
TypeError. So I want something like this:

...
Hi,
I guess, a naive approach like

numbers = [float(num+0) for num in numbers]

wouldn't satisfy the performance requirements, right?

vbr
 
J

Joshua Landau

2013/7/7 Steven D'Aprano said:
I sometimes find myself needing to promote[1] arbitrary numbers
(Decimals, Fractions, ints) to floats. E.g. I might say:

numbers = [float(num) for num in numbers]

or if you prefer:

numbers = map(float, numbers)

The problem with this is that if a string somehow gets into the original
numbers, it will silently be converted to a float when I actually want a
TypeError. So I want something like this:

...
Hi,
I guess, a naive approach like

numbers = [float(num+0) for num in numbers]

wouldn't satisfy the performance requirements, right?

%~> \python -m timeit -s "n = 123" "n"
10000000 loops, best of 3: 0.0467 usec per loop

%~> \python -m timeit -s "n = 123" "float(n)"
1000000 loops, best of 3: 0.483 usec per loop

%~> \python -m timeit -s "n = 123" "n+0"
10000000 loops, best of 3: 0.0991 usec per loop

%~> \python -m timeit -s "n = 123" "float(n+0)"
1000000 loops, best of 3: 0.537 usec per loop


# And with more local lookup
%~> \python -m timeit -s "n = 123; ffloat=float" "ffloat(n)"
1000000 loops, best of 3: 0.424 usec per loop

%~> \python -m timeit -s "n = 123; ffloat=float" "ffloat(n+0)"
1000000 loops, best of 3: 0.483 usec per loop


"+0" takes only a small fraction of the time of "float" (about 25% at
worst), so I don't think that's too big a deal. My version of
safe_float much closer to that of float (only about 2% relative
overhead) but I'd say
it's also arguably more correct -- any value which is coercible to a
float has a ".__float__" method but not all numeric types are addable
to others:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'

It's conceivable that a numeric type could refuse to add to integers,
although very unlikely. The only reason Decimal refuses to add to
floats, AFAIK, is precision.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,528
Members
45,000
Latest member
MurrayKeync

Latest Threads

Top