How to convert two's complement binary byte[] to decimal? - java

I receive datas, from a RS422 communication, in a byte tab (byte[]).
Some of my data are in two's complement binary with the following rules :
Significant bits Two's complement
MSB LSB
00000000 0
00000001 + LSB
01111111 + MSB - LSB
10000000 - MSB
10000001 - MSB + LSB
11111111 - LSB
To convert byte[] data to decimal, in pure binary, I use the following code :
Byte b05 = new Byte(new Integer(0x7A).byteValue()); // I use those bytes for my test
Byte b06 = new Byte(new Integer(0x00).byteValue());
Byte[] byteTabDay = new Byte[2] ;
byteTabDay[0] = b05 ;
byteTabDay[1] = b06 ;
int valueDay = byteTabDay[1] << 8 | byteTabDay[0] ;
System.out.println("day :" + valueDay); // print 122
But I don't know how to convert, like previously, byte[] that contain two's complement binary data like that:
Byte b20 = new Byte(new Integer(0x00).byteValue());
Byte b21 = new Byte(new Integer(0xFF).byteValue());
Byte b22 = new Byte(new Integer(0x3C).byteValue());
Those data contain, in theory, the value (more or less) : 1176
So I need help cause I don't understand how I can convert my byte data which contains two's complement binary to decimal.

Two's complement binary is the standard for representing numbers with negative numbers included.
For three-bit numbers:
Base 2 (One's Two's
complement) complement
000 = 0
001 = 1
010 = 2
011 = 3
100 = 4 -3 -4
101 = 5 -2 -3
110 = 6 -1 -2
111 = 7 -0 -1
Java assumes two's complement: the most significant bit being 1 means negative..
Also in java byte, short, int, long are signed.
As an aside, you used the Object wrappers for the primitive types. Primitive types are more immediate.
byte b05 = (byte) 0x7A;
byte b06 = (byte) 0x00; // MSB, positive as < 0x80
nyte[] byteTabDay = new byte[2];
byteTabDay[0] = b05;
byteTabDay[1] = b06;
int valueDay = ((int) byteTabDay[1]) << 8) | (0xFF & byteTabDay[0]);
System.out.println("day :" + valueDay); // print 122
What one has to do: keep the sign extension of the most significant byte, but for other bytes keep them to 8 bits by masking them with 0xFF.

The easiest way to convert an arbitrary byte array containing a two’s complement value to a decimal representation is new BigInteger(bytearray).toString().
This, however, expects the data to be in the big endian byte order. If the data is in little endian order as it seems in your question, you have to reverse it for the use with BigInteger.
byte[] bytearray={ 0x7A, 0x00 };
ByteBuffer b=ByteBuffer.allocate(bytearray.length);
for(int ix=bytearray.length; ix>0; ) b.put(bytearray[--ix]);
System.out.println(new BigInteger(b.array()).toString()); // print 122
If the length of the array matches a standard primitive value type size, you can use ByteBuffer to get the value directly:
byte[] bytearray={ 0x7A, 0x00 };
System.out.println(ByteBuffer.wrap(bytearray)
.order(ByteOrder.LITTLE_ENDIAN).getShort()); // print 122
However, don’t expect the value 1176.61254 as a result. That’s impossible.
If you think that this is the encoded value you will have to adapt your specification. There is no standard three-byte format for floating point values.

Related

Is there a more efficient way to combine 2 bytes and get the resulting decimal value?

I have 2 bytes in a byte array, and I'd like to "merge" them together so as to get byte 2's binary appended to byte 1's binary, and then get the decimal value of that binary.
byte 1: 01110110
byte 2: 10010010
combined gives 16 bits: 0111011010010010.
desired output: 30354
I am doing it like this right now, but wanted to know if there's a way that doesn't involve strings?
StringBuilder combined = new StringBuilder();
byte[] buffer = new byte[2];
buffer[0] = input[i];
buffer[1] = input[i + 1];
combined.append(Integer.toBinaryString(buffer[0] & 255 | 256).substring(1));
combined.append(Integer.toBinaryString(buffer[1] & 255 | 256).substring(1));
int key = Integer.parseInt(combined.toString(), 2);
Thanks!
To concatenate two bytes together efficiently, some bitwise arithmetic is required. However, to achieve the result that you want, we need to operate on the bytes as int values, as the result may be represented by more than 8 bits.
Consequently, we need to ensure that we're always working with the least significant 8 bits of each value (keep in mind that negative integers are represented using leading 1s in binary instead of 0s).
This can be accounted for by performing a bitwise-and of each value with 0xFF:
byte higher = (byte) 0b01110110;
byte lower = (byte) 0b10010010;
int concatenated = ((higher & 0xFF) << 8) | (lower & 0xFF);
System.out.println(concatenated);
As expected, the output of this snippet is:
30354

Unexpected result after addition

I'm coding a personal project in Java right now and have recently been using bit operations for the first time. I was trying to convert two bytes into a short, with one byte being the upper 8 bits and the other being the lower 8 bits.
I ran into an error when running the first line of code below.
Incorrect Results
short regPair = (short) ( (byte1 << 8) + (byte2) );
Correct Results
short regPair = (short) ( (byte1 << 8) + (byte2 & 0xFF) );
The expected results were: AAAAAAAABBBBBBBB, where A represents bits from byte1 and B represents bits from byte2.
Using the 1st line of code I would get the typical addition between a bit-shifted byte1 with byte 2 added to it.
Example of incorrect results
byte1 = 11, byte2 = -72
result = 2816 -72
= 2744
When using the line of code which produces the expected results I can get the proper answer of 3000. I am curious as to why the bit-masking is needed for byte2. My thoughts are that it converts byte2 into binary before the addition and then performs binary addition with both bytes.
In the incorrect case, byte2 is promoted to an int because of the + operator. This doesn't just mean adding some zeros to the start of the binary representation of byte2. Since integer types are represented in two's complement in Java, 1s will be added. After the promotion, byte2 becomes:
1111 1111 1111 1111 1111 1111 1011 1000
By doing & 0xFF, you force the promotion to int first, then you keep the least significant 8 bits: 1011 1000 and make everything else 0.
Print the intermediate value directly to see what is going on. Like,
System.out.printf("%d %s%n", ((byte) -72) & 0xFF, Integer.toBinaryString(((byte) -72) & 0xFF));
I get
184 10111000
So the correct code is actually adding 184 (not subtracting 72).
So I totally forgot that byte is singed in Java, therefore when performing math with a variable of this data type it will take the signed interpretation and not the direct value of the bits. By performing byte2 & 0xFF, Java converts the signed byte value into an unsigned int with all but the first 8 bits set as 0's. Therefore you can perform binary addition correctly.
signed byte value 0x11111111 = -1
unsigned byte value 0x11111111 = 255
In both cases byte values are promoted to int in the expression when
it is evaluated.
byte byte1 = 11, byte2 = -72;
short regPair = (short) ( (byte1 << 8) + (byte2) );
(2816) + (-72) = 2744
And even in below expression byte is promoted to int
short regPair = (short) ( (byte1 << 8) + (byte2 & 0xFF) );
2816 + 184 = 3000
Here in this expression there is no concatenation of two bytes like it has been expressed in the above question- AAAAAAAABBBBBBBB, where A represents bits from byte1 and B represents bits from byte2.
Actually -7 & 255 gives 184 which is added to 2816 to give the output 3000.

String or double to byte

I have a problem. I receive a double val=80.22. Then I split it:
String[] arr=String.valueOf(val).split("\\.");
int[] intArr=new int[2];
intArr[0]=Integer.parseInt(arr[0]); // 80
intArr[1]=Integer.parseInt(arr[1]); // 22
Now i need to put this values into a byte[]. But I don't need the value into the byte[], I need it like this: 0x80 , 0x22.
What I have tried:
byte firstbyte = Byte.parseByte(arr[0], 16);
byte secondbyte = Byte.parseByte(arr[1], 16);
This works fine but only to the value 80, then I receive:
java.lang.NumberFormatException: Value out of range. Value:"80" Radix:16
But I don't get it. I need the number up to the value 100.
I have tried to write it into a string like string = 0x80 but I don't know how to put it into the byte[] then.
Thanks for your help!
Its hard for me to understand the question, but if you want to put a number up to the value 100 in a byte data type you just need to write:
byte firstbyte = (byte)intArr[0];
byte secondbyte = (byte)intArr[1];
Because 0x80 is an hexadecimal value and its worth to the decimal 128 which is too big for that data type.
Bytes are signed in Java. The highest value in a byte, in hexadecimal is 7F, so 80 is too big. Unfortunately there is no parseUnsignedByte.
The workaround is to use the type with the next higher range up, a short, then cast the result back to byte.
byte firstbyte = (byte) Short.parseShort(arr[0], 16);
byte secondbyte = (byte) Short.parseShort(arr[1], 16);
This will turn "80" into -128, but the byte representation is correct: 1000 0000, or 80 in hexadecimal.
Note that if the short value is out of the range of even an unsigned byte (at least 256, or "100" in hexadecimal), then this will silently truncate the value to the least significant 8 bits, i.e. 256 -> 0, 257 -> 1, etc.
byte max value is 2^7 - 1 = 127 in dec.
0x80 is hex representation of 128 in dec.
Hex counts from 0 to 9 then from A to F.
So, 80 in dec will be 0x50.
Simply avoid second parameter in Byte.parseByte() or use Byte.valueOf("80")

Variable length-encoding of int to 2 bytes

I'm implementing variable lenght encoding and reading wikipedia about it. Here is what I found:
0x00000080 0x81 0x00
It mean 0x80 int is encoded as 0x81 0x00 2 bytes. That what I cannot understand. Okay, following the algorithm listed there we have.
Binary 0x80: 00000000 00000000 00000000 10000000
We move the sign bit to the next octet so we have and set to 1 (indicating that we have more octets):
00000000 00000000 00000001 10000000 which is not equals to 0x81 0x00. I tried to write a program for that:
byte[] ba = new byte[]{(byte) 0x81, (byte) 0x00};
int first = (ba[0] & 0xFF) & 0x7F;
int second = ((ba[1] & 0xFF) & 0x7F) << 7;
int result = first | second;
System.out.println(result); //prints 1, not 0x80
ideone
What did I miss?
Let's review the algorithm from the Wikipedia page:
Take the binary representation of the integer
Split it into groups of 7 bits, the group with the highest value will have less
Take these seven bits as a byte, setting the MSB (most significant bit) to 1 for all but the last; leave it 0 for the last one
We can implement the algorithm like this:
public static byte[] variableLengthInteger(int input) {
// first find out how many bytes we need to represent the integer
int numBytes = ((32 - Integer.numberOfLeadingZeros(input)) + 6) / 7;
// if the integer is 0, we still need 1 byte
numBytes = numBytes > 0 ? numBytes : 1;
byte[] output = new byte[numBytes];
// for each byte of output ...
for(int i = 0; i < numBytes; i++) {
// ... take the least significant 7 bits of input and set the MSB to 1 ...
output[i] = (byte) ((input & 0b1111111) | 0b10000000);
// ... shift the input right by 7 places, discarding the 7 bits we just used
input >>= 7;
}
// finally reset the MSB on the last byte
output[0] &= 0b01111111;
return output;
}
You can see it working for the examples from the Wikipedia page here, you can also plug in your own values and try it online.
Another Variable length encoding of integers exists and are widely used. For example ASN.1 from 1984 does define "length" field as:
The encoding of length can take two forms: short or long. The short
form is a single byte, between 0 and 127.
The long form is at least two bytes long, and has bit 8 of the first
byte set to 1. Bits 7-1 of the first byte indicate how many more bytes
are in the length field itself. Then the remaining bytes specify the
length itself, as a multi-byte integer.
This encoding is used for example in DLMS COSEM protocol or https certificates. For simple code, you can have a look at ASN.1 java library.

How to convert negative byte value to either short or integer?

We have a file which contains byte array in particular format like header and then followed by data. This file is generated by another java program and then we are reading that same file through another java program in the same format in which it was written.
The program which is making that file, populates lengthOfKey as byte which is right as that's what we need as shown below.
for (Map.Entry<byte[], byte[]> entry : holder.entrySet()) {
byte typeKey = 0;
// getting the key length as byte (that's what we need to do)
byte lengthOfKey = (byte) entry.getKey().length;
byte[] actualKey = entry.getKey();
}
Now as byte can only store maximum value as 127, we are seeing for some of our record lengthOfKey is coming as negative while we read the file as shown below:
Program which is reading the file:
byte keyType = dis.readByte();
// for some record this is coming as negative. For example -74
byte lengthOfKey = dis.readByte();
Now my question is : Is there any way I can find out what was the actual length of key because of that it got converted to -74 while writing it. I mean it should be greater than 127 and that's why it got converted to -74 but what was the actual value?
I think question would be how to convert negative byte value to either short or integer? I just wanted to verify to see what was the actual length of key because of that it got converted to negative value.
If the original length from entry.getKey().length is greater than 255, then the actual length information is lost. However, if the original length was between 128 and 255, then it can be retrieved.
The narrowing conversion of casting to byte keeps only the least significant 8 bits, but the 8th bit is now interpreted as -128 instead of 128.
You can perform a bit-and operation with 0xFF, which will retain all bits, but that implicitly widens the value back to an int.
int length = lengthOfKey & 0xFF;
lengthOfKey (byte = -74): 10110110
Widening it to an int, with sign extension:
lengthOfKey (int = -74): 11111111 11111111 11111111 10110110
Masking out the last 8 bits as an int:
length (int = 182): 00000000 00000000 00000000 10110110
That will convert a negative byte back to a number between 128 and 255.
If you use Guava, it provides a number of unsigned math utilities, including UnsignedBytes.
UnsignedBytes.toInt(lengthOfKey);
Notice their implementation of toInt() is exactly what #rgettman suggests.
To convert an assumed unsigned byte to an int.
int value = (byteValue >= (byte) 0) ? (int) byteValue : 256 + (int) byteValue;

Categories