Faking a i256 with (i128, u128)?

Is there an easy way to fake a i256 with an (i128, u128) for +-*/ ?

Context: I am doing some computational geometry on i64's. I can guarantee that all intermediate values will fit in i256's.

I think it would be a lot easier to do this if you represent it as (i64, u64, u64, u64) because then you can cast each piece to u128, do the operation, and split it into two u64s.

4 Likes

For example, you can implement addition like this:

struct I256 {
    data: [u64; 4],
}

impl From<u128> for I256 {
    fn from(value: u128) -> Self {
        Self {
            data: [0, 0, (value >> 64) as u64, value as u64],
        }
    }
}

impl Add<I256> for I256 {
    type Output = I256;

    fn add(self, other: I256) -> I256 {
        let x0 = (self.data[0] as u128).wrapping_add(other.data[0] as u128);
        let v0 = x0 as u64;
        let c0 = x0 >> 64;
        
        let x1 = (self.data[1] as u128).wrapping_add(other.data[1] as u128)
            .wrapping_add(c0);
        let v1 = x1 as u64;
        let c1 = x1 >> 64;
        
        let x2 = (self.data[2] as u128).wrapping_add(other.data[2] as u128)
            .wrapping_add(c1);
        let v2 = x2 as u64;
        let c2 = x2 >> 64;
        
        let x3 = (self.data[3] as u128).wrapping_add(other.data[3] as u128)
            .wrapping_add(c2);
        let v3 = x3 as u64;
        let c3 = x3 >> 64;
    
        I256 {
            data: [v0, v1, v2, v3],
        }
    }
}

Look up the wikipedia article for two's complement to see how to implement other operations. When it comes to the operations +, - and *, their implementation is completely identical for signed and unsigned integers. The only difference is what constitutes as overflow and underflow. Division is more tricky.

2 Likes

It looks to me that your add is little endian, but your from is big endian.

1 Like

Oops, thanks for noticing.