package com.example.foo;
import static java.util.Arrays.asList;
import java.io.InputStream;
import java.io.OutputStream;
/**
* <p>
* This demonstrates how to convert from int to byte and vice versa, if you want to ignore the sign.
* The values then simply go from 0 to 255.
* </p>
*
* <p>
* This is needed when you want to process bytes that do not represent signed values. An example for
* this is a stream of bytes that represent US-ASCII characters (8 bits per character). Java only
* offers a type for 16 bit unsigned characters (UTF-16).
* </p>
*
* <p>
* The abstract class {@link OutputStream} also uses int to write. This is explained on the method
* write: {@link java.io.OutputStream#write(int)}<br/>
* The same is true for reading bytes using {@link InputStream}.<br/>
* I wonder why they do not just use char instead of int. It would use less memory. Then again, it
* doesn't really matter when you use single values or small buffers.
* </p>
*
* <p>
* Conversion rules are tricky. The specifications can be found here: <br/>
* <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html">JLS: Chapter 5. Conversions
* and Promotions</a>
* </p>
* <p>
* Never cast from byte to int when you process binary data. That only makes sense if you want to
* have the same value but not the same data. Negative values will remain negative, but the binary
* representation is different. <br/>
* When processing the data you must not forget that Java still interprets the bytes as signed.<br/>
* Beware of operations that result in overflows. When using an int you can simply use
* <code>(i && 0xFF)</code> to ignore the extra bits and then cast to byte. Note that
* <code>(i % 256)</code> may result in a negative value and should not be used for binary data.
* </p>
*/
public class UnsignedBytesInJava {
public static void main(final String[] args) {
// Some bytes. You could also use binary or hex.
final byte min = Byte.MIN_VALUE; // = 0b1000_0000 = 0x80
final byte negOne = -1;// = 0b1111_1111 = 0xFF
final byte zero = 0;// = 0b0000_0000 = 0x00
final byte one = 1;// = 0b0000_0001 = 0x01
final byte max = Byte.MAX_VALUE; // = 0b0111_1111 = 0x7F
// let's test all 5 values:
for (final byte b : asList(min, negOne, zero, one, max)) {
// So we can't just cast from byte to int because they are both signed.
// Instead we use the binary pattern of 0xFF.
// The & operator stands for "binary AND".
final int asInt = byteToInt(b); // = b & 0xFF = b & 0b1111_1111
// Java always creates strings in decimal, signed representation.
// The only exception is the type char, which is unsigned.
// So we get different results for byte and int, but the bits are the same.
System.out.println("byte value: " + b);
System.out.println("as int: " + asInt);
// Since the same bits are set we can convert byte to a byte.
// In this case it's ok to just cast from int to byte.
final byte b2 = intToByte(asInt); // = (byte) i
System.out.println("byte again: " + b2); // same as above!
if (b != b2) // let's check that.
throw new RuntimeException("wrong value!");
// Now let's see if the bits really are the same.
final String binaryStringByte = toBinaryString(b);
System.out.println("binary byte: " + binaryStringByte);
final String binaryStringInt = toBinaryString(asInt);
System.out.println("binary int: " + binaryStringInt);
// The word size is different (8 bits vs. 32 bits).
// But the least significant bits are the same.
// Let's check if this is true:
if (!binaryStringInt.endsWith(binaryStringByte))
throw new RuntimeException("binary representation not correct!");
System.out.println("---------------------------");
}
}
/**
* Converts a byte to an int so that the 8 least significant bits are unaltered.
*
* @see java.lang.Byte#toUnsignedInt(byte)
*/
private static int byteToInt(final byte b) {
// return b & 0xFF;
return Byte.toUnsignedInt(b);
}
/**
* Converts a byte to an int to a byte. Values from 0 to 127 are the same, while values from 128
* to 255 get mapped from -128 to -1. No other integers should be passed to this method. The 8
* least significant bits are unaltered and the resulting value is interpreted as a signed bit.
*
* @throws IllegalArgumentException
* When the value is not a byte (-128 to 127)
* */
private static byte intToByte(final int i) throws IllegalArgumentException {
if (i < 0 || i > 255)
throw new IllegalArgumentException("Thats not a byte!");
return (byte) i;
}
// If you want the method to just ignore the 24 leading bits:
// private static byte intToByte(final int i) { return (byte) (i & 0xFF); }
/**
* Converts value of byte to binary (ones and zeroes, eight in total). The returned string has the
* most significant bit on the left.
*/
private static String toBinaryString(final byte b) {
final StringBuilder result = new StringBuilder();
for (int x = 0; x < Byte.SIZE; x++) {
final int p = 1 << x;
if ((b & 0xFF & p) != 0)
result.append('1');
else
result.append('0');
if (x % 4 == 3)
result.append(' ');
}
return result.reverse().toString().trim();
}
/**
* Converts value of int to binary (ones and zeroes, 32 in total). The returned string has the
* most significant bit on the left.
*/
private static String toBinaryString(final int i) {
final StringBuilder result = new StringBuilder();
for (int x = 0; x < Integer.SIZE; x++) {
final int p = 1 << x;
if ((i & p) != 0)
result.append('1');
else
result.append('0');
if (x % 4 == 3)
result.append(' ');
}
return result.reverse().toString().trim();
}
}
Comments