Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Integer.parseInt() for 32-bit signed binary string throws NumberFormatException

Tags:

java

Is this Java Api's bug?

    int i = 0xD3951892;
    System.out.println(i); // -745203566
    String binString = Integer.toBinaryString(i);
    int radix = 2;
    int j = Integer.valueOf(binString, radix );
    Assertions.assertThat(j).isEqualTo(i);

I expect it to be true without any question. But it throws below exception:

java.lang.NumberFormatException: For input string: "11010011100101010001100010010010"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:495)
at java.lang.Integer.valueOf(Integer.java:556)
at com.zhugw.temp.IntegerTest.test_valueof_binary_string(IntegerTest.java:14)

So if I have a binary String , e.g. 11010011100101010001100010010010, How can I get its decimal number(-745203566) in Java? DIY? Write code to implement below equation?

enter image description here

like image 305
zhuguowei Avatar asked Jul 12 '15 08:07

zhuguowei


2 Answers

Integer.valueOf(String, int radix) and Integer.parseInt(String, int radix) will only parse numbers of value -2 147 483 648 to 2 147 483 647, i.e. the values of 32-bit signed integers.

These functions cannot interpret two's complement numbers for binary (radix = 2), because the string being passed can be of any length, and so a leading 1 could be part of the number or the sign bit. I guess Java's developers decided that the most logical way to proceed is to never accept two's complement, rather than assume that a 32nd bit is a sign bit.

They read your input binary string as unsigned 3 549 763 730 (bigger than max int value). To read a negative value, you'd want to give a positive binary number with a - sign in front. For example for -5:

Integer.parseInt("1011", 2); // 11
    // Even if you extended the 1s to try and make two's complement of 5,
    // it would always read it as a positive binary value
Integer.parseInt("-101", 2); // -5, this is right

Solutions:

I suggest, first, that if you can store it as a positive number with extra sign information on your own (e.g. a - symbol), do that. For example:

String binString;
if(i < 0)
    binString = "-" + Integer.toBinaryString(-i);
else // positive i
    binString = Integer.toBinaryString(i);

If you need to use signed binary strings, in order to take a negative number in binary two's complement form (as a string) and parse it to an int, I suggest you take the two's complement manually, convert that into int, and then correct the sign. Recall that two's complement = one's complement + 1, and one's complement is just reverse each bit.

As an example implementation:

String binString = "11010011100101010001100010010010";
StringBuilder onesComplementBuilder = new StringBuilder();
for(char bit : binString.toCharArray()) {
    // if bit is '0', append a 1. if bit is '1', append a 0.
    onesComplementBuilder.append((bit == '0') ? 1 : 0);
}
String onesComplement = onesComplementBuilder.toString();
System.out.println(onesComplement); // should be the NOT of binString
int converted = Integer.valueOf(onesComplement, 2);
// two's complement = one's complement + 1. This is the positive value
// of our original binary string, so make it negative again.
int value = -(converted + 1);

You could also write your own version of Integer.parseInt for 32-bit two's complement binary numbers. This, of course, assumes you're not using Java 8 and can't just use Integer.parseUnsignedInt, which @llogiq pointed out while I was typing this.

EDIT: You could also use Long.parseLong(String, 2) first, then calculate the two's complement (and mask it by 0xFFFFFFFF), then downgrade the long down to int. Faster to write, probably faster code.

like image 172
Laogeodritt Avatar answered Nov 10 '22 17:11

Laogeodritt


The API docs for Integer.toBinaryString(..) explicitly state:

The value of the argument can be recovered from the returned string s by calling Integer.parseUnsignedInt(s, 8).

(as of Java 8u25) I think this is a documentation error, and it should read Integer.parseUnsignedInt(s, 2). Note the Unsigned. This is because the toBinaryString output will include the sign bit.

Edit: Note that even though this looks like it would produce an unsigned value, it isn't. This is because Java does not really have a notion of unsigned values, only a few static methods to work with ints as if they were unsigned.

like image 33
llogiq Avatar answered Nov 10 '22 16:11

llogiq