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.
https://www.geeksforgeeks.org/next-higher-number-with-same-n...
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, (@bitCast(U, 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. 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 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.
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