2's complement conversion. Is this right?

Peter Otten __peter__ at web.de
Sun Apr 20 06:21:24 EDT 2008


Bob Greschke wrote:

> On 2008-04-18 23:35:12 -0600, Ivan Illarionov <ivan.illarionov at gmail.com>
> said:
> 
>> On Sat, 19 Apr 2008 04:45:54 +0000, Ivan Illarionov wrote:
>> 
>>> On Fri, 18 Apr 2008 22:30:45 -0500, Grant Edwards wrote:
>>> 
>>>> On 2008-04-18, Bob Greschke <bob at passcal.nmt.edu> wrote:
>>>> 
>>>>> However, in playing around with your suggestion and Grant's code I've
>>>>> found that the struct stuff is WAY slower than doing something like
>>>>> this
>>>>> 
>>>>> Value = (ord(Buf[s])*65536)+(ord(Buf[s+1])*256)+ord(Buf[s+2]) if
>>>>> Value
>>>>>> = 0x800000:
>>>>> Value -= 0x1000000
>>>>> 
>>>>> This is almost twice as fast just sitting here grinding through a few
>>>>> hundred thousand conversions (like 3sec vs. ~5secs just counting on my
>>>>> fingers - on an old Sun...it's a bit slow).  Replacing *65536 with
>>>>> <<16 and *256 with <<8 might even be a little faster, but it's too
>>>>> close to call without really profiling it.
>>>> 
>>>> I didn't know speed was important.  This might be a little faster
>>>> (depending on hardware):
>>>> 
>>>> Value = (ord(Buf[s])<<16) | (ord(Buf[s+1])<<8) | ord(Buf[s+2])
>>>> 
>>>> It also makes the intention a bit more obvious (at least to me).
>>>> 
>>>> A decent C compiler will recognize that <<16 and <<8 are special and
>>>> just move bytes around rather than actually doing shifts.  I doubt the
>>>> Python compiler does optimizations like that, but shifts are still
>>>> usually faster than multiplies (though, again, a good compiler will
>>>> recognize that multiplying by 65536 is the same as shifting by 16 and
>>>> just move bytes around).
>>> 
>>> So why not put it in C extension?
>>> 
>>> It's easier than most people think:
>>> 
>>> <code>
>>> from3bytes.c
>>> ============
>>> #include <Python.h>
>>> 
>>> PyObject*
>>> from3bytes(PyObject* self, PyObject* args) {
>>> const char * s;
>>> int len;
>>> if (!PyArg_ParseTuple(args, "s#", &s, &len))
>>> return NULL;
>>> long n = (s[0]<<16) | (s[1]<<8) | s[2]; if (n >= 0x800000)
>>> n -= 0x1000000;
>>> return PyInt_FromLong(n);
>>> }
>>> 
>>> static PyMethodDef functions[] = {
>>> {"from3bytes",    (PyCFunction)from3bytes, METH_VARARGS}, {NULL,
>>> NULL, 0, NULL},
>>> };
>>> 
>>> 
>>> DL_EXPORT(void)
>>> init_from3bytes(void)
>>> {
>>> Py_InitModule("_from3bytes", functions);
>>> }
>>> 
>>> buildme.py
>>> ==========
>>> import os
>>> import sys
>>> from distutils.core import Extension, setup
>>> 
>>> os.chdir(os.path.dirname(os.path.abspath(__file__))) sys.argv =
>>> [sys.argv[0], 'build_ext', '-i'] setup(ext_modules =
>>> [Extension('_from3bytes', ['from3bytes.c'])]) </code>
>>> 
>>> 'python buildme.py' will create '_from3bytes.so' file 'from _from3bytes
>>> import from3bytes' will import C-optimized function
>>> 
>>> Hope this helps.
>> 
>> Sorry,
>> the right code should be:
>> 
>> PyObject* from3bytes(PyObject* self, PyObject* args)
>> {
>>     const char * s;
>>     int len;
>>     if (!PyArg_ParseTuple(args, "s#", &s, &len))
>>         return NULL;
>>         long n = ((((unsigned char)s[0])<<16) | (((unsigned
>>         char)s[1])<<8) |
>> ((unsigned char)s[2]));
>>     if (n >= 0x800000)
>>         n -= 0x1000000;
>>     return PyInt_FromLong(n);
>> }
> 
> No thanks.  Being able to alter and install these programs on whatever
> computer they are installed on is more important than speed.  I went
> down the C-extension path years ago and it turned into a big mess.
> Everything has to run on Sun's, Mac's, Linux and Windows without any
> major hassels if they have to be changed.  I can't reley on everything
> the program needs to be rebuilt being installed beyond Python and
> Tkinter (and pySerial and PIL for a couple of programs), which they
> need to run.  So no compiling and everything is in one file, in case a
> new version has to be upgraded by a user by emailing it to them while
> they're sitting on some mountain in Tibet.  Just unzip, stick the .py
> somewhere logical, (usually) double-click, and they are off and running.
> 
> Bob

Just for fun, here's a way to convert lots of 3-byte integers using PIL:

from PIL import Image
import array

def int3_pil(s, trafo="\x00"*128+"\xff"*128):
    n = len(s)//3
    im = Image.new("RGB", (n, 1))
    im.fromstring(s)
    hi, mid, lo = im.split()
    sign = Image.new("L", (n, 1))
    sign.fromstring(hi.tostring().translate(trafo))
    im = Image.merge("RGBA", (lo, mid, hi, sign))
    a = array.array("i")
    a.fromstring(im.tostring())
    return a.tolist()

Not as fast as I had hoped, though. Also, it needs some work to make it
independent of the processor architecture.  

Peter



More information about the Python-list mailing list