2's complement hex number to decimal in java
Asked Answered
I

3

15

I have a hex string that represents a 2's complement number. Is there an easy way (libraries/functions) to translate the hex into a decimal without working directly with its bits??

E.G. This is the expected output given the hex on the left:

"0000" => 0
"7FFF" => 32767 (max positive number)
"8000" => -32768 (max negative number)
"FFFF" => -1

Thanks!

Indulge answered 14/7, 2011 at 20:4 Comment(2)
If you know how to obtain an unsigned number then just subtract 65536 if the value is bigger than 32767Fransiscafransisco
At first I said Integer.parseInt but then realized its 2's compliment. So I removed my answer.Fivefold
G
23

This seems to trick java into converting the number without forcing a positive result:

Integer.valueOf("FFFF",16).shortValue(); // evaluates to -1 (short)

Of course this sort of thing only works for 8, 16, 32, and 64-bit 2's complement:

Short.valueOf("FF",16).byteValue(); // -1 (byte)
Integer.valueOf("FFFF",16).shortValue(); // -1 (short)
Long.valueOf("FFFFFFFF",16).intValue(); // -1 (int)
new BigInteger("FFFFFFFFFFFFFFFF",16).longValue(); // -1 (long)

Example here.

Gourde answered 14/7, 2011 at 20:45 Comment(7)
very interesting! do you know what's actually happening here though? :|Indulge
I'm pretty sure that converting to a lower-precision type simply keeps the lowest bits, so what was a 0000FFFF int becomes a FFFF short. Similarly Integer.valueOf("A7FFFF",16).shortValue(); would also result in -1 -- the higher bits are just cut off.Gourde
You're probably right. Still, I don't fully understand why the sign switches to negative when converting to a lower precision... It probably depends on how numbers are stored internally, I might need to do some research on this..Indulge
Actually it's specified in java.sun.com/docs/books/jls/second_edition/html/…Gourde
Particularly "A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T. In addition to a possible loss of information about the magnitude of the numeric value, this may cause the sign of the resulting value to differ from the sign of the input value."Gourde
The BigInteger documentation also states that it follows the same convention. So it looks like this behavior is guaranteed.Gourde
Yes, I'm pretty sure this will work all the times. It still feels a little bit as java magic though since the sign information is usually stored in the highest bit and this would certainly be truncated in the conversion. I guess numbers are probably stored/encoded as 2's complement internally even after a narrowing conversion. Integer FFFFFF gets truncated to FFFF (65536) that is way too big for a short (max positive value: 2^15-1=32767). At this point Java just follows the 2's complement encoding and produces -1. Thanks!Indulge
C
9

Just write a utility method:

 public static Integer twosComp(String str) throws java.lang.Exception {
       Integer num = Integer.valueOf(str, 16);
       return (num > 32767) ? num - 65536 : num;
 }

Tests:

 twosComp("7FFF") -> 32767
 twosComp("8000") -> -32768
 twosComp("FFFF") -> -1
Cloister answered 14/7, 2011 at 20:8 Comment(2)
+1 for writing ur own, but it doesn't work Long or larger values does it?Fivefold
+1 Looks like it would be easy enough to roll into a BigInteger implementation for ultimate scalability.Meshed
A
5

This seems to work reasonably well. It can be fooled by passing it non-standard length strings: "FFF" maps to -1. Zero padding will correct the fault.

You are not clear on what type return you want, so I have returned Number, at whatever size is appropriate.

public Number hexToDec(String hex)  {
   if (hex == null) {
      throw new NullPointerException("hexToDec: hex String is null.");
   }

   // You may want to do something different with the empty string.
   if (hex.equals("")) { return Byte.valueOf("0"); }

   // If you want to pad "FFF" to "0FFF" do it here.

   hex = hex.toUpperCase();

   // Check if high bit is set.
   boolean isNegative =
      hex.startsWith("8") || hex.startsWith("9") ||
      hex.startsWith("A") || hex.startsWith("B") ||
      hex.startsWith("C") || hex.startsWith("D") ||
      hex.startsWith("E") || hex.startsWith("F");

   BigInteger temp;

   if (isNegative) {
      // Negative number
      temp = new BigInteger(hex, 16);
      BigInteger subtrahend = BigInteger.ONE.shiftLeft(hex.length() * 4);
      temp = temp.subtract(subtrahend);
   } else {
      // Positive number
      temp = new BigInteger(hex, 16);
   }

   // Cut BigInteger down to size.
   if (hex.length() <= 2) { return (Byte)temp.byteValue(); }
   if (hex.length() <= 4) { return (Short)temp.shortValue(); }
   if (hex.length() <= 8) { return (Integer)temp.intValue(); }
   if (hex.length() <= 16) { return (Long)temp.longValue(); }
   return temp;
}

Sample output:

"33" -> 51
"FB" -> -5
"3333" -> 13107
"FFFC" -> -4
"33333333" -> 53687091
"FFFFFFFD" -> -3
"3333333333333333" -> 3689348814741910323
"FFFFFFFFFFFFFFFE" -> -2
"33333333333333333333" -> 241785163922925834941235
"FFFFFFFFFFFFFFFFFFFF" -> -1
Adjoint answered 14/7, 2011 at 22:32 Comment(1)
Using BigInteger to convert works great without the 'isNegative' logic you have. All I had to do was to create the BigInteger like you did, then call the byteValue/shortValue/intValue/longValue as appropriate for my desired width, and I got the same results you did. Except...I think your value for "33333333" should be 858993459. That's what I got through Java, and the Windows calculator. Try System.out.println(new BigInteger("33333333", 16).intValue()+" -> "+53687091);Bosk

© 2022 - 2024 — McMap. All rights reserved.