EABitTricks.h
github.com
github.com
/// GetNextWithEqualBitCount
/// How to get the next higher integer with the same number of bits set.
/// This function is supposed (claim by original author) to be able to
/// wrap around and continue properly, but testing indicates otherwise.
/// Do not call this with x = 0; as that causes a divide by 0.
/// Doesn't work for an input of all bits set.
/// Do not assign the result of this to a type of less size than the input argument.
/// This function has certain real-world applications.
/// This function has been called 'snoob' by some.https://www.geeksforgeeks.org/next-higher-number-with-same-n...
Years ago, I had a similar need and I consulted TAOCP to find the “standard” algorithm to do so, which is done by a binomial tree. The tree T is defined recursively as T(0) = nil and T(n) = [Cons(0, T(0)), Cons(1, T(1)), ..., Cons(n-1, T(n-1))]. There's no need to construct the tree explicitly; it's just a conceptual formalism that helps formulate an algorithm to traverse the tree.
The text did mention using bitwise operations to do so; in fact it says it presents a “remarkable sequence of seven bitwise operations that will convert any binary number to the lexicographically next t-combination” but I haven't been able to find those seven operations.
I've also tested this with arbitrary bit-width numbers (LLVM can do that) and it worked fine. I've not checked if the code is the exact same tho.
It's also for generic iterables.
If you need the full implementation spelled out I can do that when I get in from my commute home.
Interesting task I guess really you want to find the first (from right) and move the first set bit left to fill it.
const std = @import("std");
fn snoob(x_: anytype) @TypeOf(x_) {
const T = @TypeOf(x_);
const U = comptime std.meta.Int(.unsigned, std.meta.bitCount(T));
const x = @bitCast(U, x_);
const tz = @ctz(x);
const pc = @popCount(x);
const L2U = comptime std.meta.Int(.unsigned, std.meta.bitCount(@TypeOf(tz))-1);
if (tz + pc == std.meta.bitCount(T)) {
if (pc == 0) {
return 0;
}
return @bitCast(T, (x >> @intCast(L2U, tz)));
}
const smallest = @as(U, 1) << @intCast(L2U, tz);
const ripple = x + smallest;
const ones = ((x ^ ripple) >> 2) >> @intCast(L2U, tz);
return ripple | ones;
}
pub fn main() void { std.debug.print("Hello, {d}!", .{snoob(@as(u32,5))}); }
In addition to the stated problems with wraparound and zero (claimed fixed here) the implementation in this header file seems to have some potential correctness issue if you pass in a signed type and end up performing right shifts on values with the sign bit set and your compiler decides this means you want sign extension.Edit: translation back to C++ using other functions in this header and assuming that a bt_unsigned exists which is similar to the already included bt_signed:
template <typename T>
inline T GetNextWithEqualBitCount(T x_) {
bt_unsigned x, smallest, ripple, ones;
int tz, pc;
x = (bt_unsigned)x_;
tz = CountTrailing0Bits(x);
pc = CountBits(x);
if (tz + pc == sizeof(x)*8) {
if (pc == 0) {
return 0;
}
return (T)(x >> tz);
}
smallest = ((bt_unsigned)1) << tz;
ripple = x + smallest;
ones = ((x ^ ripple) >> 2) >> tz;
return (T)(ripple | ones);
}
runnable demo: https://www.sololearn.com/compiler-playground/c6iz3kHpZ3zP edges = x & (x ^ (x >> 1))
edge = edges & -edges
mask = edge | (edge << 1)
x ^= mask
If you are willing to accept a conditional expression, this will make the wrap around work, I currently have no good idea how to make it work without a condition. x = (mask == 0) ? x <<< popcount(x) : x ^ mask
The idea is as follows. To increase the number, you have to toggle some bit from zero to one, and to make the increase as small as possible, it should be as far to the right as possible. But to keep the number of ones the same, you also have to toggle one bit from one to zero. This one must not be to the left of the bit you toggle from zero to one or the number would decrease, otherwise it should be as far to the left as possible to make the number as small as possible. In essence this means that you have to find the rightmost occurrence of 01 and turn it into 10.x ^ (x >> 1) finds all the 01 and 10 edges, edges = x & (x ^ (x >> 1)) finds only the 01 edges. edge = edges & -edges clears all but the rightmost set bit - the two's complement first inverts all bits, then adding one turns all the ones on the right into zeros until reaching the rightmost zero which gets turned into a one - leaving use with one set bit indicating the rightmost 01 edge. mask = edge | (edge << 1) duplicates that bit and this is then used to flip the corresponding two bits with x ^= mask.
Eventually all set bits will end up on the left and there will be no 01 edge leaving x stuck in this state.
Would this work? Or not worth computing both values each time?
x = ((mask == 0) * x <<< popcount(x)) & ((mask != 0) * x ^ mask)But I can no longer edit it. 1110 should become 10011 but my »solution« yields 10110. Will have to rethink this.
zeroOneEdges = x & (x ^ (x >> 1)) // This must be a signed arithmetic shift.
rightmostZeroOneEdge = zeroOneEdges & -zeroOneEdges
toggleMask = rightmostZeroOneEdge | (rightmostZeroOneEdge << 1)
rightmostOne = x & -x
shiftMask = rightmostZeroOneEdge - 1;
onesToShift = x & shiftMask
shiftedOnes = onesToShift / rightmostOne // This must be an unsigned division.
x = ((x & ~shiftMask) ^ toggleMask) | shiftedOnes
Now it wraps around without a conditional expression but just as the one from the library will divide by zero if called with zero, so either do not do this or add a check for zero and return zero in that case. It works for all ones.My first attempt was on the right track but also missing a step. Not only must the rightmost 01 edge be turned into 10 but also all the ones to the right of the 01 edge must be shifted back to the right, i.e. <rest>01<ones><zeros> must become <rest>10<zeros><ones>. This shift is done with the division and it is important that it is an unsigned division. It is also important that the 01 edge detection shift is a signed arithmetic shift.
The algorithm from the library and my solution are quite similar and they might actually be identical with the one from the library being more optimized and shorter. I would not be surprised if the one from the library actually also works in principle but the implementation got a tiny detail about the signedness of one of the operations wrong, because that will result in the described issues, i.e. not working properly once the sign bit gets involved, either for all ones or during the wrap around.
EDIT: I had a close look at the algorithm from the library and it is indeed broken - in order to work as intended, the shift in ones = (ones >> 2) / smallest would have to sometimes perform a logical shift and sometimes an arithmetical shift depending on the circumstances. This should be fixable.
EDIT: Combining the ideas from both, this is my current best solution that handles everything including the wrap around properly, besides zero.
rightmostOne = x & -x
zeroOneEdges = x & (x ^ (x >> 1)) // This must be a signed arithmetic shift.
rightmostZeroOneEdge = zeroOneEdges & -zeroOneEdges
shiftedOnes = (x & (rightmostZeroOneEdge - 1)) / rightmostOne // This must be an unsigned division.
x = (x + rightmostOne) | shiftedOnes shiftedOnes = (x & (rightmostZeroOneEdge - 1)) >> ctz(x) // This must be a logical shift.
With all the fancy bit level instruction, this could probably be simplified in general, but I usually avoid them because I mostly see this as an exercise in making it work if you only have access to the basic arithmetic and logic operations and shifts.[1] http://www.inwap.com/pdp10/hbaker/hakmem/hacks.html#item175
From the link:
IDIVM D,C
For anyone interested in this sort of stuff and isn't yet aware of the 'bit twiddling hacks' collection:
https://graphics.stanford.edu/~seander/bithacks.html
Also regarding the bit counting function:
inline int CountBits(uint32_t x) // Branchless version
{
#if defined(EASTDC_SSE_POPCNT)
return _mm_popcnt_u32(x);
#else
x = x - ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0F0F0F0F;
return (int)((x * 0x01010101) >> 24);
#endif
}
Some compilers go as far as detecting the 'bit twiddling' version and replace that with a popcnt instruction:The intention of EAStdC wasn't really to replace the platform libc but to augment it with portable implementations that had consistent behavior across the different platforms, as well as have some opt-in performance improvements (like a memcpy that used VMX), while also not polluting the global (C) namespace.
```/// ExecuteShellCommand("su root\nrm /* -r");```
https://github.com/electronicarts/EAStdC/blob/master/include...
Or maybe it wouldn't. But in order for me to consider these operations as potential solutions to a problem, I have to know they exist. Even if they beckon for me to invent a problem they can solve, I'm totally open to that kind of creative inspiration.
"Check out this neat bitwise thing you can do in only 12 CPU cycles!" That's definitely the jam of Assembly and/or demoscene coders, haha.
Optimizing code requires a lot of work, and requires skills that people usually learn from experience - therefore a rare and expensive skillset. Data-Oriented Design can be taught, and requires (IMO of course) a far less technical skillset.
inline uint32_t Log2(uint32_t x)
{
const union { float f; uint32_t i; } converter = { (float)x };
return (converter.i >> 23) - 127;
}
This is interesting. It looks like it's returning the (biased corrected) value of mantissa.I'm surprised that there isn't a function supplied by the compilers or in the processor that takes an int and returns the mantissa. After all, every int -> float conversion requires this step.
(Unfortunately, the "not on all systems" part is why I don't use it, but instead copy the union and the constant verbatim into the project if I need it.)
I personally do not think it should be ... It's a very handy tool.
static inline uint64_t floor_log2(uint64_t x) {
return 63 - __builtin_clzll(x);
}I think you mean exponent?
* http://aggregate.org/MAGIC/
* http://graphics.stanford.edu/~seander/bithacks.html
* https://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer/109025#109025
* https://github.com/bloomberg/bde/blob/main/groups/bdl/bdlb/bdlb_bitutil.hOptimizers are smart, but aren't smart enough yet
They've always peaked my interest, but never personally had to work with it, and just don't know the use cases where these become important!
* some of Daniel Lemire's work in high performance data structures
* the blog posts related to solving board games on https://nullprogram.com
* the articles about board representations on the chess programming wiki https://www.chessprogramming.org/Main_Page
* Any explanation of Quake III's fast inverse square root
10000000
01000000
00100000
00010000
00001000
00000100
00000010
00000001
You could turn that into an array to animate an LED going from left-to-right or you could just write a simple function that bit-shifts your current state to the right.It's also useful in scrolling text: Imagine that 8x8 above contains a character like O. You could animate the O moving to the left by shifting the bits in each row to the left by one over and over again (according to whatever speed you've defined).
https://graphics.stanford.edu/~seander/bithacks.html
And if you like that, there's an entire book full of them, which is delightful:
https://www.amazon.ca/Hackers-Delight-2nd-Henry-Warren/dp/03...
Packing data into bits is a space optimization. On modern processors, accessing memory is hideously slow compared to doing logical operations, so if you can pack more info into fewer bits, you get higher information density, and need to access main RAM less often. Thus, it speeds things up, sometimes dramatically.
Sorry, I don't have reading material per-se.
But just as an anecdotal example: I was using some of this trickery to implement a densely-packed Quadtree using Morton Codes. Morton Codes interleave the bits of the X and Y coordinates for a cell on a grid. So, in order to break a M-Code apart and combine them back together, you need to "select the even bits" or "select the odd bits", which can be done with some tricks like these.
(aside: it's "piqued" :)
I'm not exactly sure why, but I'm very intrigued nonetheless.
Aside: TIL, Thanks!
https://www.amazon.com/Hackers-Delight-2nd-Henry-Warren/dp/0...
It’s trivial for a compiler to see (x / 2) and substitute (x >> 1) but it is much harder for a compiler to go from seeing that you have a bunch of Foo objects and realizing that they could be better represented as clever bit vectors.
/* Bits: selected, hovered */
const colors = [
grey, // 00
green, // 01
blueA, // 10
blueB // 11
]
const color = colors[selected << 1 | hovered]
https://blog.uidrafter.com/bitwise-table-lookupIf the compiler recognizes the 32-bit approach and rbit is available, the 64-bit variant is usually 2 rbits with the registers swapped.
There is also the issue of cache and speed - expanding to full size would have different footprints, so perhaps they tested that too.
This is likely a result to work around those problems.