WhaleFanny
WhaleFanny

Reputation: 87

Changing a bit within a byte in java

So I understand how to change an individual bit within a byte, what I am not sure is why my particular code is not working.

public static void setBit(byte[] input, int position, int value) {
    int byteLocation = position / 8;
    int bitLocation = position % 8;
    byte tempByte = input[byteLocation];

    if (value == 0) 
    tempByte = (byte) (tempByte & ~(1 << bitLocation));
    else
    tempByte = (byte) (tempByte | (1 << bitLocation));

    input[byteLocation] = tempByte;
}

Now I have been testing it with the string "Testing1" which is 64bits long, then attempting to set the bits and display the value. It works a treat up to 46 bits, then on the 47th bit if I attempt to set it to 1 it borks up, works fine with 0 however.

Can't see the error in my ways, here's how I am testing it

String test = "Testing1";

byte[] bytes = test.getBytes();

for (int i = 0; i < bytes.length; i++)
    System.out.print(String.format("%8s", Integer.toBinaryString(bytes[i])).replace(' ', '0') + "[" + i + "] ");

setBit(bytes, 44, 1);
System.out.println();
for (int i = 0; i < bytes.length; i++)
    System.out.print(String.format("%8s", Integer.toBinaryString(bytes[i])).replace(' ', '0') + "[" + i + "] ");

The following is the output when I attempt to change the 47th bit to a 1

01010100[0] 01100101[1] 01110011[2] 01110100[3] 01101001[4] 01101110[5] 01100111[6] 00110001[7] 
01010100[0] 01100101[1] 01110011[2] 01110100[3] 01101001[4] 11111111111111111111111111101110[5] 01100111[6] 00110001[7] 

Upvotes: 4

Views: 3883

Answers (5)

Slihp
Slihp

Reputation: 793

Ive recently had to do something like this.
I managed to achieve it through (a lot of use of the whiteboard but..) shifting the original bits right by the position of the LSB I wanted to replace and making all bits inclusive of the MSB I wanted to replace, 1's.
I then AND'ed the bits I want in place of the bits I want to replace, shifted left the same number I shifted right, OR'ing the result with the original and AND'ed by an XOR'ed mask of the replacement. (Take a breath, I'll try to explain)

Let's say I have the bytes:
1111 1010 0001 1001

and I want to replace the nibble 1010 with 0001 to produce:

1111 0001 0001 1001.

The operation I went through to achieve this is:

1) Shift right by 8 to produce:
0000 0000 1111 1010

2) OR a mask of 0xf (1111) to produce:
0000 0000 1111 1111

3) AND the replacement of 0001 with 0000 0000 1111 1111 to produce:
0000 0000 0000 0001

4) Shift left by 8 to produce:
0000 0001 0000 0000

5) Shift the mask by the LSB position and XOR with full bytes

1111 1111 1111 1111
0000 1111 0000 0000
==================
1111 0000 1111 1111

6) AND the XOR'ed, shifted mask with the original to produce:

1111 0000 0001 1001
1111 0000 1111 1111
==================
1111 0000 0001 1001

7) OR that result of the above with the replacement:

1111 0000 0001 1001
0000 0001 0000 0000
==================
1111 0001 0001 1001 << end result
==================


In java, this results in the function:

public long overwriteBits(long overwrite, long with, long shift, long mask)
{
    return ((((overwrite >> shift) | mask) & with) << shift) | (overwrite & (~0 ^ (mask << shift)));
}

Where "overwrite" is the original data, "with" are the bits you want in place of the bits in position "shift" and mask is a a series of positive bits with the same length of the replacement.

To do the above I'd call (in sudo):
overwriteBits(1111101000011001, 0001, 8, 1111)

I want to mention that the above will work for replacing bits in any primitive, no need for byte arrays. e.g. Replacing 11 bits, as below:

1101001010101101 1111 0101 101 001101
with 1010 1010 101

overwriteBits(1101001010101101 1111 0101 101 001101, 1010 1010 101, 6, 11111111111)

1101001010101101 1111 0101 101 001101
1101001010101101 1010 1010 101 001101

overwriteBits(1789785421l, 1365l, 6, 0x7FF)

Upvotes: 0

Bill K
Bill K

Reputation: 62789

I haven't looked at it in too much detail but I think the problem with the one bite is that it's being extended to an int (Since it's signed, the 1 extends to a negative int).

Just take the last 8 characters of the string and it will work fine.

Upvotes: 0

Evgeniy Dorofeev
Evgeniy Dorofeev

Reputation: 136112

Change formatting as

Integer.toBinaryString(0xFF & bytes[i])

byte needs to be masked off because it is sign-extended, not zero-extended, to 32-bit int

Upvotes: 3

femtoRgon
femtoRgon

Reputation: 33351

The problem is you are setting the sign bit in the byte in question. So, that byte now has a negative value. You call Integer.toBinaryString(), which takes an int as it's argument, not a byte. The byte get promoted to an int, and it correctly evaluates the value of:

11101110

to it's equivalent integer:

11111111111111111111111111101110

Upvotes: 2

Patashu
Patashu

Reputation: 21793

I made your method smaller using ^ (xor)

public static void setBit(byte[] input, int position, int value) {
    int byteLocation = position / 8;
    int bitLocation = position % 8;
    input[byteLocation] = (byte) (input[byteLocation] ^ (byte) (1 << bitLocation));
}

Upvotes: 0

Related Questions