25

I am using a java.util.BitSet to store a dense vector of bits.

I want to implement an operation that shifts the bits right by 1, analogous to >>> on ints.

Is there a library function that shifts BitSets?

If not, is there a better way than the below?

public static void logicalRightShift(BitSet bs) {
  for (int i = 0; (i = bs.nextSetBit(i)) >= 0;) {
    // i is the first bit in a run of set bits.

    // Set any bit to the left of the run.
    if (i != 0) { bs.set(i - 1); }

    // Now i is the index of the bit after the end of the run.
    i = bs.nextClearBit(i);  // nextClearBit never returns -1.
    // Clear the last bit of the run.
    bs.clear(i - 1);

    // 0000111100000...
    //     a   b
    // i starts off the loop at a, and ends the loop at b.
    // The mutations change the run to
    // 0001111000000...
  }
}
Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
  • 2
    Wait, this is a left logical shift, not a right logical shift. Right? – Andrew McKinlay Oct 30 '13 at 02:48
  • 1
    I think of the bit at index zero of a BitSet as the left-most. There's not a clear most- or least-significant bit the way there is with a bit-string that represents an integer, so the labeling of directions is kind of arbitrary. – Mike Samuel Oct 30 '13 at 03:52

8 Answers8

23

That should do the trick:

BitSet shifted = bs.get(1, bs.length());

It will give you a bitset equal to the orginial one, but without the lower-most bit.

EDIT:

To generalize this to n bits,

BitSet shifted = bs.get(n, Math.max(n, bs.length()));
Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
Philipp Wendler
  • 11,184
  • 7
  • 52
  • 87
  • 1
    The [documentation](http://docs.oracle.com/javase/7/docs/api/java/util/BitSet.html#get(int, int)) on `get` confuses me. Nothing in "Returns a new BitSet composed of bits from this BitSet from fromIndex (inclusive) to toIndex (exclusive)." indicates that the bit at `fromIndex` in `this` maps to `0` in the output. – Mike Samuel Jan 25 '12 at 18:37
  • 1
    @Mike. It looks like it works similar to `String.substring( begin, end )`. Notice, that `begin` in this case is `1`, not `0`. – Alexander Pogrebnyak Jan 25 '12 at 18:41
  • @AlexanderPogrebnyak, did you determine that empirically, or is there actual documentation that guarantees that on all implementations? – Mike Samuel Jan 25 '12 at 18:42
  • @Mike. That's what the documentation says. At least to me :). – Alexander Pogrebnyak Jan 25 '12 at 18:44
  • 1
    @AlexanderPogrebnyak, I think the Javadoc I quoted could be interpreted as treating `x = bs.get(1, bs.cardinality()+1)` and `x = (BitSet) bs.clone(); x.clear(0)` – Mike Samuel Jan 25 '12 at 18:50
  • @AlexanderPogrebnyak, Empirically, Sun's/Oracle's implementation does map bit `i` in this to `i - fromIndex`. – Mike Samuel Jan 25 '12 at 18:53
  • that solves shifting to left, but how to shift right? – Radu Simionescu Jan 04 '16 at 10:00
  • @AlexanderPogrebnyak I highly doubt that BitSet.get works like String.substring. String is immutable, BitSet is not. If it worked like String.substring, then when changing a bit in the "shifted" bitset, the original bitset would also be affected. Fortunately, this doesn't happen. – Radu Simionescu Jan 04 '16 at 10:14
  • @RaduSimionescu. Check the documentation for BitSet.get method. It starts with 'Returns a new BitSet ...'. "New" is the keyword here. This means original BitSet is not affected. As for shifting right, ask the question, and someone will provide. – Alexander Pogrebnyak Jan 05 '16 at 23:33
  • @AlexanderPogrebnyak You said that BitSet.get(int, int) works like String.substring(int, int). String.substring creates a new object which, uses the same array of chars used by the original String. It just maps to the same array, using an offset and a length. That is possible because String is immutable. The underlying array of chars is never going to change so new String instances can share the same char array under the hood. BitSet is mutable. The underlying array of bits can change so it cannot be shared by different instances. BitSet.get(int, int) is O(n) while String.substring is O(1). – Radu Simionescu Jan 06 '16 at 11:39
  • @AlexanderPogrebnyak so what I am saying is that they don't work the same at all. They just look similar in terms of parameters semantics. – Radu Simionescu Jan 06 '16 at 11:43
  • @RaduSimionescu From the perspective of the user, the two methods work exactly similar. Whether or not String.substring() shares the internal array is an implementation detail, the documentation does not guarantee O(1) behavior. In fact, this is no longer done since at least Java 7, an array copy is made in the relevant [String constructor](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/String.java#String.%3Cinit%3E%28char%5B%5D%2Cint%2Cint%29). – Philipp Wendler Jan 06 '16 at 12:10
  • @PhilippWendler they look the same, they behave similar. They don't "work" the same. The array copy from the String constructor is news to me – Radu Simionescu Jan 06 '16 at 14:54
7

Please find this code block where BitSet is "left-shifted"

/**
 * Shift the BitSet to left.<br>
 * For example : 0b10010 (=18) => 0b100100 (=36) (equivalent to multiplicate by 2)
 * @param bitSet
 * @return shifted bitSet
 */
public static BitSet leftShiftBitSet(BitSet bitSet) {
    final long maskOfCarry = 0x8000000000000000L;
    long[] aLong = bitSet.toLongArray();

    boolean carry = false;
    for (int i = 0; i < aLong.length; ++i) {
        if (carry) {
            carry = ((aLong[i] & maskOfCarry) != 0);
            aLong[i] <<= 1;
            ++aLong[i];
        } else {
            carry = ((aLong[i] & maskOfCarry) != 0);
            aLong[i] <<= 1;
        }
    }

    if (carry) {
        long[] tmp = new long[aLong.length + 1];
        System.arraycopy(aLong, 0, tmp, 0, aLong.length);
        ++tmp[aLong.length];
        aLong = tmp;
    }

    return BitSet.valueOf(aLong);
}
wooofer
  • 85
  • 1
  • 3
7

An alternative which is probably more efficient would be to work with the underlying long[].

Use bitset.toLongArray() to get the underlying data. Shift those longs accordingly, then create a new BitSet via BitSet.valueOf(long[]) You'll have to be very careful shifting the underlying longs, as you will have to take the low order bit and shift it into the high order bit on the next long in the array.

This should let you use the bit shift operations native on your processor to move 64 bits at a time, as opposed to iterating through each one separately.

EDIT: Based on Louis Wasserman's comment. This is only available in Java 1.7 API. Didn't realize that when I wrote it.

rfeak
  • 8,124
  • 29
  • 28
  • Doesn't that require me to manually catch the low bit and propagate it onto the end of the previous long? Does this perform two array copies? – Mike Samuel Jan 25 '12 at 18:38
  • @MikeSamuel - Yes to both of those. However, I believe it would still be faster. Not sure if that matters for your problem. Looking at Philipp's suggesting, I think that would be the simplest, and probably fastest. – rfeak Jan 25 '12 at 18:47
5

You can use BigInteger instead of BitSet. BigInteger already has ShiftRight and ShiftLeft.

Lii
  • 11,553
  • 8
  • 64
  • 88
Radu Simionescu
  • 4,518
  • 1
  • 35
  • 34
  • 2
    Your answer has been flagged as not an answer, "is not the most efficient approach" is interesting but you should try to show some code of example using the BigInteger class how this can be achieved... From Review – Petter Friberg Jan 06 '16 at 02:09
  • 3
    The author correctly points out, that builtin shift operators provide a good reason to use BI instead of BS's. Of course one can always do, ` BigInteger bi = new BigInteger(bs.toByteArray()); bi.shiftLeft(12); bs = BitSet.valueOf(bi.toByteArray());` if absolutely needed. – P Marecki Dec 26 '16 at 20:59
4

These functions mimic the << and >>> operators, respectively.

/**
 * Shifts a BitSet n digits to the left. For example, 0b0110101 with n=2 becomes 0b10101.
 *
 * @param bits
 * @param n the shift distance.
 * @return
 */
public static BitSet shiftLeft(BitSet bits, int n) {
    if (n < 0)
        throw new IllegalArgumentException("'n' must be >= 0");
    if (n >= 64)
        throw new IllegalArgumentException("'n' must be < 64");

    long[] words = bits.toLongArray();

    // Do the shift
    for (int i = 0; i < words.length - 1; i++) {
        words[i] >>>= n; // Shift current word
        words[i] |= words[i + 1] << (64 - n); // Do the carry
    }
    words[words.length - 1] >>>= n; // shift [words.length-1] separately, since no carry

    return BitSet.valueOf(words);
}

/**
 * Shifts a BitSet n digits to the right. For example, 0b0110101 with n=2 becomes 0b000110101.
 *
 * @param bits
 * @param n the shift distance.
 * @return
 */
public static BitSet shiftRight(BitSet bits, int n) {
    if (n < 0)
        throw new IllegalArgumentException("'n' must be >= 0");
    if (n >= 64)
        throw new IllegalArgumentException("'n' must be < 64");

    long[] words = bits.toLongArray();

    // Expand array if there will be carry bits
    if (words[words.length - 1] >>> (64 - n) > 0) {
        long[] tmp = new long[words.length + 1];
        System.arraycopy(words, 0, tmp, 0, words.length);
        words = tmp;
    }

    // Do the shift
    for (int i = words.length - 1; i > 0; i--) {
        words[i] <<= n; // Shift current word
        words[i] |= words[i - 1] >>> (64 - n); // Do the carry
    }
    words[0] <<= n; // shift [0] separately, since no carry

    return BitSet.valueOf(words);
}
kollerma
  • 3
  • 2
Jeff Piersol
  • 410
  • 3
  • 11
  • Thanks. The 64 bound on n seems arbitrary but that limit could be relaxed by first copying words into a fresh array shifting by (n / 64) in the appropriate direction. – Mike Samuel Apr 24 '15 at 11:59
  • The question is old, but still i would like to comment on it. Any methods limited a shifting n <= 64 is useless, the above code is slow. Better use primitive `long` if number of bits less then 64, or use `BigInteger` which has built-in functions for shifting bits left and right. If sticks to `BitSet`, should consider shifting the `bitIndex` before put bit values in `BitSet`. – RaymondM Jan 29 '18 at 03:37
  • 1. The function names are in reverse, I think. 2. This does not work for bits >64. 3. What I did (and it is slow) is to convert the bitset into biginteger for number of bits > 64 and then shifted the bitset number. – Ojasv singh Oct 11 '20 at 12:33
1

You can look at the BitSet toLongArray and the valueOf(long[]).
Basically get the long array, shift the longs and construct a new BitSet from the shifted array.

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
Ayman
  • 11,265
  • 16
  • 66
  • 92
0

In order to achieve better performance you can extend java.util.BitSet implementation and avoid unnecessary array copying. Here is implementation (I've basically reused Jeff Piersol implementation):

package first.specific.structure;

import java.lang.reflect.Field;
import java.util.BitSet;

public class BitSetMut extends BitSet {

    private long[] words;
    private static Field wordsField;

    static {
        try {
            wordsField = BitSet.class.getDeclaredField("words");
            wordsField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new IllegalStateException(e);
        }
    }

    public BitSetMut(final int regLength) {
        super(regLength);
        try {
            words = (long[]) wordsField.get(this);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    public void shiftRight(int n) {
        if (n < 0)
            throw new IllegalArgumentException("'n' must be >= 0");
        if (n >= 64)
            throw new IllegalArgumentException("'n' must be < 64");

        if (words.length > 0) {
            ensureCapacity(n);

            // Do the shift
            for (int i = words.length - 1; i > 0; i--) {
                words[i] <<= n; // Shift current word
                words[i] |= words[i - 1] >>> (64 - n); // Do the carry
            }
            words[0] <<= n; // shift [0] separately, since no carry
            // recalculateWordInUse() is unnecessary
        }
    }

    private void ensureCapacity(final int n) {
        if (words[words.length - 1] >>> n > 0) {
            long[] tmp = new long[words.length + 3];
            System.arraycopy(words, 0, tmp, 0, words.length);
            words = tmp;
            try {
                wordsField.set(this, tmp);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    }
}
bstorozhuk
  • 79
  • 4
  • 1
    This seems brittle. It depends on a private field having not only a particular type and semantics but also a particular name. Also, doesn't `ensureCapacity` lose the aliasing relationship between words and the super-class private field. It does fail-fast though so the brittleness might be manageable. What kind of performance speedup do you get in exchange for the brittleness? – Mike Samuel Sep 10 '15 at 18:42
  • @Mike you are absolutely right on ensureCapacity(n) method, it's my bug so I just fixed it. I've used this BitSetMut implementation as linear feedback shift register in some computational heavy, telecommunication algorithms like [scrambling](https://en.wikipedia.org/wiki/Scrambler). BitSetMut gives the opportunity to avoid unnecessary array copying and garbage generation so overall latencies are much lower. Scrambler implementation with BitSetMut is 2 times faster in comparison to Scrambler with BitSet and static shiftRight method. – bstorozhuk Sep 12 '15 at 04:08
0

With java SE8, it can be achieved more concise way:

BitSet b = new BitSet();
b.set(1, 3);
BitSet shifted = BitSet.valueOf(Arrays.stream(
       b.toLongArray()).map(v -> v << 1).toArray());

I was trying to figure out how to use LongBuffer to do so but not quite got it to work. Hopefully, someone who is familiar with low level programming can point out a solution.

Thanks in advance!!!

ReneWang
  • 516
  • 5
  • 7