Split decimal value in python

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]

Chủ Đề