I hope someone has some experience with left shift (<<)
and 2's Complement
in SSJS
and can help me to confirm my assumption and suggest a better approach for that problem.
Assumption
- SSJS is not using 2's Complement resulting in
Arithmetic operation overflow
when dealing with negative numbers. - SSJS left shift operator
<<
is using the logical approach ofx * ( 2 ** y )
irrespectively of a 32 bit size. This results in a larger number as there are no excess bits and it will continue to shift with zeros from the right.
Question
- How can I overcome the missing 2's Complement in SSJS?
- Is there a way to limit the size of the shift to 32 bit in SSJS?
- My sample code below works but I am interested if there is a better solution.
Deep dive
I believe I understand how shifting bits in a signed 32 bit integer works but there are still a few grey areas so please be gentle if anything below is not accurate.
Shifting the current bits to the left by the specific number of bits. Excess bits shifted off to the left are discarded. Zero bits are shifted in from the right.
<script runat="server">
Platform.Load("Core", "1.1.5");
var i = 1073741824,
i_bit = i.toString(2); // 0100 0000 0000 0000 0000 0000 0000 0000
// shifting i by 1 bit to the left
var i_shifted = (i << 1); // 2147483648
i_shifted_bit = i_shifted.toString(2); // 1000 0000 0000 0000 0000 0000 0000 0000
Write(i+'<br/>');
Write(i_bit+'<br/>');
Write(i_shifted+'<br/>');
Write(i_shifted_bit+'<br/>');
</script>
Here is the first difference between SSJS and JS. JS is using 2's Complement and uses the last position as a negative indicator. This seems to be different in SSJS as it does not allow negative numbers for shifting at all.
SSJS:
268435456 | 16777216 | 1048576 | 65536 | 4096 | 256 | 16 | 1 (32 bit)
1... | 0... | 0... | 0... | 0... | 0... | 0... | ...0 = 268435456
JS Shifting with 2's Complement is using the highest bit as -:
-268435456 | 16777216 | 1048576 | 65536 | 4096 | 256 | 16 | 1 (32 bit)
1... | 0... | 0... | 0... | 0... | 0... | 0... | ...0 = -268435456
If you try -1 << 1
in JS your result will be -2
.
In SSJS this throws an Arithmetic operation overflow
Error.
The other difference is that the left shift operator (<<
) yields different results in SSJS than in JS. I assume it is just a quick implementation of the algorithm x * ( 2 ** y )
. This results that the excess bits are not dropped but the number is extended beyond the 32 bit integer.
This will not allow me to convert the integer into a binary string as it exceed the allowance of Uint32.
<script runat="server">
Platform.Load("Core", "1.1.5");
var i = 1073741824,
i_bit = i.toString(2); // 0100 0000 0000 0000 0000 0000 0000 0000
// shifting i by 1 bit to the left
var i_shifted = (i << 2) // 4294967296
// System.OverflowException: Value was either too large or too small for a UInt32
// i_shifted_bit = i_shifted.toString(2); // 0001 0000 0000 0000 0000 0000 0000 0000 0000
Write(i+'<br/>');
Write(i_bit+'<br/>');
Write(i_shifted+'<br/>');
Write(i_shifted_bit+'<br/>');
</script>
The expected value is 0
and not 4294967296
as the leading 01
is shifted out of the 32 bit window as we define a left shift of 2.
My take to solve this
As mentioned above, I was wondering if there is a better way of solving this in SSJS. Here is my code:
<script runat="server">
Platform.Load("Core", "1.1.5");
try {
function leftShift(a,b) {
var d = a<<b,
r = [],
i = 0,
n = (d<0)?true:false;
d = (n)?d*-1:d
if ( d == 0) {
return 0;
}
// Decimal integers to binary: Algorithm
// Step 1. Divide the integer by 2, while noting the quotient and remainder.
// Step 2. Then, divide the quotient again by 2, and record the 3rd quotient and remainder.
// Step 3. Like this, keep dividing each successive quotient by 2 until you get a quotient of zero.
// Step 4. After this, just write all the remainders in reverse order to get the binary representation of the integer.
while (d !== 0) {
r[i++] = (d % 2); // store remainder
d = Math.floor(d/2); // store quotient
}
// if it exceed 32 drop the overflow
if (r.length > 32) {
r = r.slice(0,32)
}
// reverse the binary array join each element and parseInt
r = parseInt(r.reverse().join(''), 2);
return (n)?-1*r:r;
}
Write(leftShift(1073741824,1)+'<br/>'); // -2147483648
Write(leftShift(1073741824,2)+'<br/>'); // 0
Write(leftShift(5,3)+'<br/>'); // 20
Write(leftShift(-1,1)+'<br/>'); // -2
} catch(e) {
Write(e)
}
</script>