Truncate 64Bit Integer to 32Bit and simulate value

0

I'm trying to write an algorithm to convert a 64Bit integer to 32Bit via truncation, and return a value accurately representing a 32Bit value. The obvious problem is that integers in PHP are only 64Bit (barring 32Bit systems).

I first tried the following:

$x = $v & 0xFFFFFFFF

So if $v = PHP_INT_MAX, $x is 4294967295, when really I want -1. I know the reason for this is that when represented as a 64Bit integer, PHP prepends the zeros to the last 32 bits, and thats why I get a positive number.

My solution so far is this:

function convert64BitTo32Bit(int $v): int
{
    $v &= 0xFFFFFFFF;

    if ($v & 0x80000000) {
        return $v | 0xFFFFFFFF00000000;
    }

    return $v;
}

And I'm pretty sure its right. The problem I have with this is that it requires that if statement to inspect whether the truncated number is negative. I was really hoping for a bitwise only solution. It may not be possible, but I thought I'd ask.

EDIT:

The problem can be simplified to just part of the solution. i.e. if the first bit is 1, make all bits 1, if first bit is 0 make all bits 0.

# example assumes input is the LSBs of an 8Bit integer.
# scale for 64Bit and the same solution should work.

op(1010) = 1111
op(0101) = 0000

op(0000) = 0000
op(1000) = 1111
op(0001) = 0000

I would be able to use the LSBs to derive this value, then mask it onto the MSBs of the 64Bit integer. This is what I'm trying to figure out now, though I'm trying to avoid creating a monster equation.

php
type-conversion
integer
bit-manipulation
bitwise-operators
asked on Stack Overflow Aug 1, 2019 by Flosculus • edited Aug 1, 2019 by Flosculus

2 Answers

2

There's probably a more elegant/efficient way using bitwise operations, but you can also force the conversion with pack() and unpack(). Eg: Pack as unsigned, unpack as signed.

function overflow32($in) {
    return unpack('l', pack('i', $in & 0xFFFFFFFF))[1];
}

var_dump( overflow32(pow(2,33)-1) ); // int(-1)

I'm curious what you're applying this to, because I had to break a hash function with this same thing some years ago when I moved an app from a 32 bit machine to a 64 bit machine, but I can't remember what it was.

Edit: Wow for some reason I remembered Two's Complement being hard. Literally just invert and add one.

function overflow32($in) {
    if( $in & 0x80000000 ) {
        return ( ( ~$in & 0xFFFFFFFF) + 1 ) * -1;
    } else {
        return $in & 0xFFFFFFFF;
    }
}

var_dump( kludge32(pow(2,33)-1) );

Edit 2: I saw that you want to extend this to arbitrary bit lengths, in which case you just need to calculate the masks instead of explicitly setting them:

function overflow_bits($in, $bits=32) {
    $sign_mask = 1 << $bits-1;
    $clamp_mask = ($sign_mask << 1) - 1;

    var_dump(
        decbin($in),
        decbin($sign_mask),
        decbin($clamp_mask)
    );

    if( $in & $sign_mask ) {
        return ( ( ~$in & $clamp_mask) + 1 ) * -1;
    } else {
        return $in & $clamp_mask;
    }
}

var_dump(
    overflow_bits(pow(2, 31), 32),
    overflow_bits(pow(2, 15), 16),
    overflow_bits(pow(2, 7), 8)
);

I left in the debug var_dump()s for output flavor:

string(32) "10000000000000000000000000000000"
string(32) "10000000000000000000000000000000"
string(32) "11111111111111111111111111111111"
string(16) "1000000000000000"
string(16) "1000000000000000"
string(16) "1111111111111111"
string(8) "10000000"
string(8) "10000000"
string(8) "11111111"
int(-2147483648)
int(-32768)
int(-128)
answered on Stack Overflow Aug 1, 2019 by Sammitch • edited Aug 1, 2019 by Sammitch
0

I think I've cracked it:

function overflow32Bit(int $x): int
{
    return ($x & 0xFFFFFFFF) | ((($x & 0xFFFFFFFF) >> 31) * ((2 ** 32) - 1) << 32);
}

var_dump(overflow32Bit(PHP_INT_MAX));     // -1          correct
var_dump(overflow32Bit(PHP_INT_MAX - 1)); // -2          correct
var_dump(overflow32Bit(PHP_INT_MIN));     //  0          correct
var_dump(overflow32Bit((2 ** 31) - 1));   //  2147483647 correct
var_dump(overflow32Bit((2 ** 31)));       // -2147483647 correct
var_dump(overflow32Bit(0xFFFFFFFF));      // -1          correct
var_dump(overflow32Bit(0x7FFFFFFF));      //  2147483647 correct

The solution was actually staring me in the face. Get the value of the first bit, then multiply it by the max value of unsigned 32bit integer.

If someone can come up with a better or shorter solution, I'll also accept that.

PS. This is only for 32Bit, i also intend to use this proof for 16Bit and 8Bit.


Expanded, $x is input, $z is output.

$x = PHP_INT_MAX;
$y = (2 ** 32) - 1;
$z = ($x & $y) | ((($x & $y) >> 31) * ($y << 32));

var_dump($z);
answered on Stack Overflow Aug 1, 2019 by Flosculus • edited Aug 1, 2019 by Flosculus

User contributions licensed under CC BY-SA 3.0