18

I have a signed value given as a hex number, by example 0xffeb and want convert it into -21 as a "normal" Javascript integer.

I have written some code so far:

function toBinary(a) { //: String
    var r = '';
    var binCounter = 0;
    while (a > 0) {
        r = a%2 + r;
        a = Math.floor(a/2);
    }
    return r;
}

function twoscompl(a) { //: int
    var l = toBinaryFill(a).length;
    var msb = a >>> (l-1);

    if (msb == 0) {
        return a;
    }

    a = a-1;
    var str = toBinary(a);
    var nstr = '';
    for (var i = 0; i < str.length; i++) {
        nstr += str.charAt(i) == '1' ? '0' : '1';
    }
    return (-1)*parseInt(nstr);
}

The problem is, that my function returns 1 as MSB for both numbers because only at the MSB of the binary representation "string" is looked. And for this case both numbers are 1:

-21 => 0xffeb => 1111 1111 1110 1011
 21 => 0x15   =>              1 0101

Have you any idea to implement this more efficient and nicer?

Greetings, mythbu

mythbu
  • 657
  • 2
  • 7
  • 16

6 Answers6

29

Use parseInt() to convert (which just accepts your hex string):

parseInt(a);

Then use a mask to figure out if the MSB is set:

a & 0x8000

If that returns a nonzero value, you know it is negative.

To wrap it all up:

a = "0xffeb";
a = parseInt(a, 16);
if ((a & 0x8000) > 0) {
   a = a - 0x10000;
}

Note that this only works for 16-bit integers (short in C). If you have a 32-bit integer, you'll need a different mask and subtraction.

kelsny
  • 23,009
  • 3
  • 19
  • 48
Bart Friederichs
  • 33,050
  • 15
  • 95
  • 195
  • Not sure how important it is, but since you're working with hex with `parseInt`, you probably want to use `parseInt(a, 16)` – Ian Nov 20 '12 at 08:00
  • @Ian, I tested it in the Firebug console and `parseInt("0xffeb")` parses correctly. When omitting the `0x`, it needs the radix. To be safe, it should be added always. I updated my answer. – Bart Friederichs Nov 20 '12 at 08:04
  • True that it works without, but `parseInt` works on normal int strings without it as well...unless you accidentally give bad input (like expecting radix 10 parsing but providing "012" for whatever reason) and getting `10`. It seems to be a browser consistency thing too. I've just found it better to always supply the radix, even if you're working on the "normal" base 10. Just wanted to point it all out :) – Ian Nov 20 '12 at 08:07
  • Amazing clean and tiny code! The world can be so easy ... :-) – mythbu Nov 20 '12 at 08:20
  • Works a treat... Thanks! – PGallagher Dec 02 '19 at 12:37
19

I came up with this

function hexToInt(hex) {
    if (hex.length % 2 != 0) {
        hex = "0" + hex;
    }
    var num = parseInt(hex, 16);
    var maxVal = Math.pow(2, hex.length / 2 * 8);
    if (num > maxVal / 2 - 1) {
        num = num - maxVal
    }
    return num;
}

And usage:

var res = hexToInt("FF"); // -1
res = hexToInt("A"); // same as "0A", 10
res = hexToInt("FFF"); // same as "0FFF", 4095
res = hexToInt("FFFF"); // -1

So basically the hex conversion range depends on hex's length, ant this is what I was looking for. Hope it helps.

milosmns
  • 3,595
  • 4
  • 36
  • 48
6

Based on @Bart Friederichs I've come with:

function HexToSignedInt(num, numSize) {
    var val = {
        mask: 0x8 * Math.pow(16, numSize-1), //  0x8000 if numSize = 4
        sub: -0x1 * Math.pow(16, numSize)    //-0x10000 if numSize = 4
    }
    if((parseInt(num, 16) & val.mask) > 0) { //negative
        return (val.sub + parseInt(num, 16))
    }else {                                 //positive
        return (parseInt(num,16))
    }
 }

so now you can specify the exact length (in nibbles).

var numberToConvert = "CB8";
HexToSignedInt(numberToConvert, 3);
//expected output: -840
Kostynha
  • 145
  • 2
  • 10
  • 2
    I really like this solution. I had to add an extra parenthesis in the negative check to get all my tests to pass. E.g. HexToSignedInt("7d",2) if((parseInt(num, 16) & val.mask) > 0) { //negative – zaphodtx Oct 02 '19 at 06:43
  • This solution is great overall but faills for values that would should result into the min negative value: (0x80, 2) should give -128, not 128, The range for input 2 is from 0x00 to 0xFF which should go from -128 to 127, where 0x00-7F is 0-127 and 0x80-0xFF goes from -128 to -1 – Noman_1 May 14 '21 at 07:54
  • ooops, that was just an operation order problem. I've added the parenthesis @zaphodtx spoke about, – Kostynha Feb 02 '22 at 00:25
3
function hexToSignedInt(hex) {
    if (hex.length % 2 != 0) {
        hex = "0" + hex;
    }
    var num = parseInt(hex, 16);
    var maxVal = Math.pow(2, hex.length / 2 * 8);
    if (num > maxVal / 2 - 1) {
        num = num - maxVal
    }
    return num;
}

function hexToUnsignedInt(hex){
    return parseInt(hex,16);
}

the first for signed integer and the second for unsigned integer

Kitani Islam
  • 114
  • 6
0

As I had to turn absolute numeric values to int32 values that range from -2^24 to 2^24-1, I came up with this solution, you just have to change your input into a number through parseInt(hex, 16), in your case, nBytes is 2.

function toSignedInt(value, nBytes) { // 0 <= value < 2^nbytes*4, nBytes >= 1, 
  var hexMask = '0x80' + '00'.repeat(nBytes - 1);
  var intMask = parseInt(hexMask, 16);
  if (value >= intMask) {
    value = value - intMask * 2;
  }
  return value;
}

var vals = [       // expected output
  '0x00',          // 0
  '0xFF',          // 255
  '0xFFFFFF',      // 2^24 - 1 = 16777215
  '0x7FFFFFFF',    // 2^31 -1 = 2147483647
  '0x80000000',    // -2^31 = -2147483648
  '0x80000001',    // -2^31 + 1 = -2147483647
  '0xFFFFFFFF',    // -1
];
for (var hex of vals) {
  var num = parseInt(hex, 16);
  var result = toSignedInt(num, 4);
  console.log(hex, num, result);
}

var sampleInput = '0xffeb';
var sampleResult = toSignedInt(parseInt(sampleInput, 16), 2);
console.log(sampleInput, sampleResult); // "0xffeb", -21
Noman_1
  • 473
  • 1
  • 6
  • 17
0

Based on the accepted answer, expand to longer number types:

  function parseSignedShort(str) {
    const i = parseInt(str, 16);
    return i >= 0x8000 ? i - 0x10000 : i;
  }
  parseSignedShort("0xffeb");  // -21

  function parseSignedInt(str) {
    const i = parseInt(str, 16);
    return i >= 0x80000000 ? i - 0x100000000 : i;
  }
  parseSignedInt("0xffffffeb");  // -21

  // Depends on new JS feature. Only supported after ES2020
  function parseSignedLong(str) {
    if (!str.toLowerCase().startsWith("0x"))
      str = "0x" + str;
    const i = BigInt(str);
    return Number(i >= 0x8000000000000000n ? i - 0x10000000000000000n : i);
  }
  parseSignedLong("0xffffffffffffffeb");  // -21
Jianwu Chen
  • 5,336
  • 3
  • 30
  • 35