Last active
July 27, 2017 23:05
-
-
Save PurpleMyst/28dd0c1e84df88ff8854c2775ddff0ac to your computer and use it in GitHub Desktop.
Fast way of calculating the number of digits in a number in Python, using C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from math import floor, log10 | |
from digits import bacherikow, intlen, bordum, stephen, bisect | |
def python_strlen(n, str=str, len=len): | |
return len(str(n)) | |
def python_intlen(n, floor=floor, log10=log10): | |
return floor(log10(n)) + 1 | |
def python_bacherikow(n, enumerate=enumerate, | |
table=[10**x for x in range(1, 10)]): | |
for i, k in enumerate(table): | |
if n < k: | |
return i + 1 | |
def python_bordum(n): | |
l = 1 | |
base = k = 10 | |
while n >= k: | |
k *= base | |
l += 1 | |
return l | |
def main(): | |
import random | |
import sys | |
from timeit import timeit | |
n = random.randint(10 ** 8, 10 ** 9) | |
tests = 1000000 | |
print(f"Running under {sys.implementation.name}") | |
for func in (bacherikow, intlen, bordum, stephen, bisect, | |
python_bacherikow, python_intlen, python_bordum, | |
python_strlen): | |
assert func(132890) == 6, func.__name__ | |
thyme = timeit(f"{func.__name__}({n!r})", | |
setup=f"from __main__ import {func.__name__}", | |
number=tests) | |
print(f"{func.__module__}.{func.__name__}({n!r}) ran {tests} times " | |
f"takes {thyme * 1000:.2f} milliseconds") | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Python.h> | |
#include <math.h> | |
// Compilation command: | |
// clang digits.c -o digits.so -Ofast -std=c99 -shared -fPIC -I /usr/include/python3.6 | |
static PyObject* | |
digits_intlen(PyObject *self, PyObject *args) { | |
int num; | |
if (!PyArg_ParseTuple(args, "i", &num)) return NULL; | |
return PyLong_FromLong(1 + floor(log10(num))); | |
} | |
// TODO: Build this list with a macro. | |
static const int powers[] = {10, 100, 1000, 10000, 100000, 1000000, 10000000}; // and so on... | |
static const int power_amount = sizeof powers / sizeof powers[0]; | |
static PyObject* | |
digits_bacherikow(PyObject *self, PyObject *args) { | |
int num; | |
if (!PyArg_ParseTuple(args, "i", &num)) return NULL; | |
for (int i = 1; i <= power_amount; ++i) { | |
if (num - powers[i - 1] < 0) return PyLong_FromLong(i); | |
} | |
Py_RETURN_NONE; | |
} | |
static int bisect_log10(long int n, int s, int e) { | |
int a = (e - s) / 2 + s; | |
if(s >= e) | |
return s; | |
if((powers[a] - n) <= 0) | |
return bisect_log10(n, a + 1, e); | |
else | |
return bisect_log10(n, s, a); | |
} | |
static PyObject* | |
digits_bisect(PyObject *self, PyObject *args) { | |
int num; | |
if (!PyArg_ParseTuple(args, "i", &num)) return NULL; | |
return PyLong_FromLong(1 + bisect_log10(num, 0, power_amount)); | |
} | |
static PyObject* | |
digits_bordum(PyObject *self, PyObject *args) { | |
int num; | |
if (!PyArg_ParseTuple(args, "i", &num)) return NULL; | |
int l = 1; | |
int k = 10; | |
int base = 10; | |
// TODO: Maybe make this a for loop? | |
while (num >= k) { | |
k *= base; | |
l += 1; | |
} | |
return PyLong_FromLong(l); | |
} | |
static unsigned int baseTwoDigits(unsigned int x) { | |
return x ? 32 - __builtin_clz(x) : 0; | |
} | |
static unsigned int tenToThe[] = { | |
1, | |
10, | |
100, | |
1000, | |
10000, | |
100000, | |
1000000, | |
10000000, | |
100000000, | |
1000000000, | |
}; | |
static unsigned int guess[33] = { | |
0, 0, 0, 0, 1, 1, 1, 2, 2, 2, | |
3, 3, 3, 3, 4, 4, 4, 5, 5, 5, | |
6, 6, 6, 6, 7, 7, 7, 8, 8, 8, | |
9, 9, 9 | |
}; | |
static PyObject* | |
digits_stephen(PyObject *self, PyObject *args) { | |
int num; | |
if (!PyArg_ParseTuple(args, "i", &num)) return NULL; | |
unsigned int digits = guess[baseTwoDigits(num)]; | |
return PyLong_FromLong(digits + (num >= tenToThe[digits])); | |
} | |
static PyMethodDef digits_methods[] = { | |
{"intlen", digits_intlen, METH_VARARGS, | |
"Utilizes the base 10 logarithm to return the number of digits in a number."}, | |
{"bacherikow", digits_bacherikow, METH_VARARGS, | |
"Utilizes the bacherikow method to return the number of digits in a number."}, | |
{"bisect", digits_bisect, METH_VARARGS, | |
"Utilizes the bacherikow method to return the number of digits in a number."}, | |
{"bordum", digits_bordum, METH_VARARGS, | |
"Utilizes the bordum method to return the number of digits in a number."}, | |
{"stephen", digits_stephen, METH_VARARGS, | |
"Utilizes the stephen method to return the number of digits in a number."}, | |
{NULL, NULL, 0, NULL} /* Sentinel */ | |
}; | |
static struct PyModuleDef digits_module = { | |
PyModuleDef_HEAD_INIT, | |
"digits", | |
NULL, | |
-1, | |
digits_methods | |
}; | |
PyMODINIT_FUNC | |
PyInit_digits(void) | |
{ | |
return PyModule_Create(&digits_module); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment