ENH: Add 'bitorder' keyword to packbits, unpackbits#12962
Conversation
There was a problem hiding this comment.
There probably is a more elegant way to flip the bits
There was a problem hiding this comment.
I think you need to rewrite the loop about, and not do build <<= 1, but rather OR-in an already shifted element (i.e., something like build |= (inputr[j] != 0) << shift where shift depends on order and j).
There was a problem hiding this comment.
You mention "adopted" but I think the code is still the same, no? I meant the loop above (not "about"), which now has the build <<= 1. I think that loop can be rewritten to just do the right thing for each order.
There was a problem hiding this comment.
I could write this as below, is that better? I don't understand why I needed to use unsigned char instead of char for the order != 'b' case, I am only ever shifting right. If unsigned char is required it should be for the left shifts, no?
Details
for (; index < n_out; index++) {
unsigned char build = 0;
int i, maxi;
npy_intp j;
maxi = (index == n_out - 1) ? remain : 8;
if (order == 'b') {
for (i = 0; i < maxi; i++) {
build <<= 1;
for (j = 0; j < element_size; j++) {
build |= (inptr[j] != 0);
}
inptr += in_stride;
}
if (index == n_out - 1) {
build <<= 8 - remain;
}
}
else
{
for (i = 0; i < maxi; i++) {
build >>= 1;
for (j = 0; j < element_size; j++) {
build |= (inptr[j] != 0) ? 128 : 0;
}
inptr += in_stride;
}
if (index == n_out - 1) {
build >>= 8 - remain;
}
}
*outptr = (char)build;
outptr += out_stride;
}
There was a problem hiding this comment.
I think this was a bug, if not we need to add it back in. I don't have a big-endian system to test on.
There was a problem hiding this comment.
Hmm, this does get turned into bytes in the end, and those will have a different order in the uint64 here. So, my guess is that you do need to swap (i.e., have the reverse swap for big and little endian). But am a bit too lazy to just write it out... Note that the test cases surely would fail for big endian if this was wrong...
There was a problem hiding this comment.
Now tested on a big-endian system since I have access to the gcc build farm and a sparc64 machine. See 5d1b9208d
|
Waiting for #10855 to land, then this will need updating |
|
Tests pass, no extra modifications were needed for |
mhvk
left a comment
There was a problem hiding this comment.
I like the idea. Some initial comments.
There was a problem hiding this comment.
Flip around for packbits [0, 0, 0, 0, 0, 1, 1] => 3
There was a problem hiding this comment.
I think you need to rewrite the loop about, and not do build <<= 1, but rather OR-in an already shifted element (i.e., something like build |= (inputr[j] != 0) << shift where shift depends on order and j).
There was a problem hiding this comment.
Put this in an else clause of the if statement below.
There was a problem hiding this comment.
I'm confused: exactly the same v is constructed here as below, the only difference being the byte swap. Can we just fill both big and little in one loop? (one the unswapped, the other the swapped v)?
There was a problem hiding this comment.
Hmm, this does get turned into bytes in the end, and those will have a different order in the uint64 here. So, my guess is that you do need to swap (i.e., have the reverse swap for big and little endian). But am a bit too lazy to just write it out... Note that the test cases surely would fail for big endian if this was wrong...
|
Following this guide, I set up a mips big-endian qemu machine, translated numpy, and ran the tests in |
mhvk
left a comment
There was a problem hiding this comment.
Two comments about comments, otherwise just a question about the initialization.
There was a problem hiding this comment.
Adjust the comment to make clear this is for the "big-endian" case.
There was a problem hiding this comment.
removed reference to endian, since the 'b' makes the intention clear. Moved the rest of the comment to mark the intrinsic
There was a problem hiding this comment.
You mention "adopted" but I think the code is still the same, no? I meant the loop above (not "about"), which now has the build <<= 1. I think that loop can be rewritten to just do the right thing for each order.
There was a problem hiding this comment.
Good to adjust this comment too...
There was a problem hiding this comment.
"fixed" by removing "big endian"
There was a problem hiding this comment.
Can we match python here in int.from_bytes (C longobject.c:int_to_bytes_impl), and actually require the full string?
Could consider using the internal _PyUnicode_EqualToASCIIId, which I think is available in the python versions we care about.
There was a problem hiding this comment.
I used strncmp instead, which does not require Py_LIMITED_API
There was a problem hiding this comment.
We already depend on Py_LIMITED_API, so I don't think we need to care
There was a problem hiding this comment.
one of the test runners failed when I used _PyUnicode_EqualToASCIIString, saying the function was not defined.
Edit: fix function name
There was a problem hiding this comment.
There's no guarantee that char(256) overflows, CHAR_BIT might not be 8.
Why not just use a simple loop here?
for (int i = 0; i < 8; i++) {
*outptr = (bool)(*inptr & (1 << i));
outptr += out_stride;
}There was a problem hiding this comment.
Shouldn't there be 8 items in this list, not 7?
I'm not sure I'd call this the common standard - I frankly find it bizarre that unpackbits(1)[0] does not refer to "bit 0" of x.
Could instead compare to binary literal syntax, stating that 0b00000011 => [0, 0, 0, 0, 0, 0, 1, 1]
There was a problem hiding this comment.
Seeing the comment, I remember being bitten by exactly this (which is of course why "little" will be useful!).
Maybe the docstring can be agnostic about which is "standard" - both have advantages. I like the idea of adding 3 = 0b00000011 => [...]
be968b2 to
bf8b7cd
Compare
|
@eric-wieser ping |
1 similar comment
|
@eric-wieser ping |
There was a problem hiding this comment.
The order refers to the list of bits in the input.
There was a problem hiding this comment.
redid the comment and changed order -> bitorder
There was a problem hiding this comment.
Should be
bitorder : {'big', 'little'}, optional
Also below.
|
close/reopen |
| `numpy.packbits` and `numpy.unpackbits` accept an ``order`` keyword | ||
| ------------------------------------------------------------------- | ||
| The ``order`` keyword defaults to ``big``, and will order the **bits** | ||
| accordingly. For ``'big'`` 3 will become ``[0, 0, 0, 0, 0, 0, 1, 1]``, and |
There was a problem hiding this comment.
Period. But I will fix it when editing the release note.
|
Thanks Matti. |
| } | ||
|
|
||
| /* setup lookup table under GIL, big endian 0..256 as bytes */ | ||
| if (unpack_init == 0) { |
There was a problem hiding this comment.
how does the removal of the lookup table impact performance?
As I recall it was significant when I added it.
I realize the lookup table is a bunch of extra code, but it is a factor 8 speedup. |

Fixes #11172
Adds an 'order' kwarg to packbits, unpackbits. Should this hit the mailing list? It is a small enhancement. I removed the separate handling of NPY_BYTE_ORDER, we only have little endian systems to test on, but it shouldn't matter for uint8 processing.