DulcetAirman icon

Unsigned Bytes in Java

DulcetAirman | PRO | 12/23/13 04:38:20 PM UTC | 0 ⭐ | 861 👁️ | Never ⏰ | []
Java |

5.93 KB

|

None

|

0 👍

/

0 👎

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