I have come up with two statements that can divide positive and negative numbers into integer and fraction without compromising accuracy [bit overflow] and speed.
As an example, a positive or negative value of the value 100.1323
will be divided as:
100.1323
-> [100
, 0.1323
]
-100.1323
-> [-100
, -0.1323
]
Code
# Divide a number [x] into integer and fraction
i = int[x] # Get integer
f = [x*1e17 - i*1e17] / 1e17 # Get fraction
Speedtest
The performance test shows that the two statements are faster than
math.modf
, as long as they are not put into their own function or method.
test.py
:
#!/usr/bin/env python
import math
import cProfile
""" Get the performance of both statements and math.modf """
X = -100.1323 # The number to be divided into integer and fraction
LOOPS = range[5 * 10 ** 6] # Number of loops
def scenario_a[]:
""" Get the performance of the statements """
for _ in LOOPS:
i = int[X] # -100
f = [X*1e17-i*1e17]/1e17 # -0.1323
def scenario_b[]:
""" Tests the speed of the statements when integer need to be float.
NOTE: The only difference between this and math.modf is the accuracy """
for _ in LOOPS:
i = int[X] # -100
i, f = float[i], [X*1e17-i*1e17]/1e17 # [-100.0, -0.1323]
def scenario_c[]:
""" Tests the speed of the statements in a function """
def modf[x]:
i = int[x]
return i, [x*1e17-i*1e17]/1e17
for _ in LOOPS:
i, f = modf[X] # [-100, -0.1323]
def scenario_d[]:
""" Tests the speed of math.modf """
for _ in LOOPS:
f, i = math.modf[X] # [-0.13230000000000075, -100.0]
def scenario_e[]:
""" Tests the speed of math.modf when the integer part should be integer """
for _ in LOOPS:
f, i = math.modf[X] # [-0.13230000000000075, -100.0]
i = int[i] # -100
if __name__ == '__main__':
cProfile.run['scenario_a[]']
cProfile.run['scenario_b[]']
cProfile.run['scenario_c[]']
cProfile.run['scenario_d[]']
cProfile.run['scenario_e[]']
Result:
4 function calls in 1.357 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno[function]
1 0.000 0.000 1.357 1.357 :1[]
1 1.357 1.357 1.357 1.357 test.py:11[scenario_a]
1 0.000 0.000 1.357 1.357 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
4 function calls in 1.858 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno[function]
1 0.000 0.000 1.858 1.858 :1[]
1 1.858 1.858 1.858 1.858 test.py:18[scenario_b]
1 0.000 0.000 1.858 1.858 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
5000004 function calls in 2.744 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno[function]
1 0.000 0.000 2.744 2.744 :1[]
1 1.245 1.245 2.744 2.744 test.py:26[scenario_c]
5000000 1.499 0.000 1.499 0.000 test.py:29[modf]
1 0.000 0.000 2.744 2.744 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
5000004 function calls in 1.904 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno[function]
1 0.000 0.000 1.904 1.904 :1[]
1 1.073 1.073 1.904 1.904 test.py:37[scenario_d]
1 0.000 0.000 1.904 1.904 {built-in method builtins.exec}
5000000 0.831 0.000 0.831 0.000 {built-in method math.modf}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
5000004 function calls in 2.547 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno[function]
1 0.000 0.000 2.547 2.547 :1[]
1 1.696 1.696 2.547 2.547 test.py:43[scenario_e]
1 0.000 0.000 2.547 2.547 {built-in method builtins.exec}
5000000 0.851 0.000 0.851 0.000 {built-in method math.modf}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Use C/C++ extension
I have tried to compile the two statements with C/C++ support and the result was even better. With Python extension module it was possible to get a method that was faster and more accurate than math.modf
.
math2.pyx
:
def modf[number]:
cdef float num = number
cdef int i = num
return i, [num*1e17 - i*1e17] / 1e17
See Basics of Cython
test.py
:
#!/usr/bin/env python
import math
import cProfile
import math2
""" Get the performance of both statements and math.modf """
X = -100.1323 # The number to be divided into integers and fractions
LOOPS = range[5 * 10 ** 6] # Number of loops
def scenario_a[]:
""" Tests the speed of the statements in a function using C/C++ support """
for _ in LOOPS:
i, f = math2.modf[X] # [-100, -0.1323]
def scenario_b[]:
""" Tests the speed of math.modf """
for _ in LOOPS:
f, i = math.modf[X] # [-0.13230000000000075, -100.0]
if __name__ == '__main__':
cProfile.run['scenario_a[]']
cProfile.run['scenario_b[]']
Result:
5000004 function calls in 1.629 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno[function]
1 0.000 0.000 1.629 1.629 :1[]
1 1.100 1.100 1.629 1.629 test.py:10[scenario_a]
1 0.000 0.000 1.629 1.629 {built-in method builtins.exec}
5000000 0.529 0.000 0.529 0.000 {math2.modf}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
5000004 function calls in 1.802 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno[function]
1 0.000 0.000 1.802 1.802 :1[]
1 1.010 1.010 1.802 1.802 test.py:16[scenario_b]
1 0.000 0.000 1.802 1.802 {built-in method builtins.exec}
5000000 0.791 0.000 0.791 0.000 {built-in method math.modf}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
NOTE
The statements can be faster with modulo, but modulo can not be used to split negative numbers into integer and fraction parts.
i, f = int[x], x*1e17%1e17/1e17 # Divide a number [x] into integer and fraction
As an example, a positive or negative value of the value 100.1323
will be divided as:
100.1323
-> [100
,
0.1323
]
-100.1323
-> [-100
, 0.8677
]