James South
James South

Reputation: 10645

Mathematical operations on packed numerical values

Given the following code packing four byte values into a uint.

private static void Pack(byte x, byte y, byte z, byte w)
{
    this.PackedValue = (uint)x |
                       ((uint)y << 8) |
                       ((uint)z << 16) |
                       ((uint)w << 24);
}

Is it possible to apply mathematical operators like *, +, / and - on the value in a manner that it can be unpacked into the correct byte equivalent?

EDIT.

To clarify, if I attempt to multiply the value by another packed value

uint result  = this.PackedValue * other.PackedValue 

Then unpack using the following...

public byte[] ToBytes()
{
    return new[]
    {
        (byte)(this.PackedValue & 0xFF),
        (byte)((this.PackedValue >> 8) & 0xFF),
        (byte)((this.PackedValue >> 16) & 0xFF),
        (byte)((this.PackedValue >> 24) & 0xFF)
    };
}

I get the wrong results.

Here's a full code sample showing the expected and actual result.

void Main()
{
    uint x = PackUint(128, 128, 128, 128);
    uint y = (uint)(x * 1.5f);

    byte[] b1 = ToBytes(x);
    x.Dump(); // 2155905152
    b1.Dump(); // 128, 255, 128, 255 RIGHT!
    byte[] b2 = ToBytes(y);
    b2.Dump(); // 0, 192, 192, 192 WRONG! Should be 192, 192, 192, 192

}

// Define other methods and classes here
private static uint PackUint(byte x, byte y, byte z, byte w)
{
    return ((uint)x) |
           ((uint)y << 8) |
           ((uint)z << 16) |
           ((uint)w << 24);
}

public static byte[] ToBytes(uint packed)
{
    return new[]
    {
        (byte)(packed & 0xFF),
        (byte)((packed >> 8) & 0xFF),
        (byte)((packed >> 16) & 0xFF),
        (byte)((packed >> 24) & 0xFF)
    };
}

Upvotes: 5

Views: 159

Answers (2)

Aron
Aron

Reputation: 15772

A much nicer solution to your problem is to using Blitting.

void Main()
{
    Byte X = 0x13;
    Byte Y = 0x6A;
    Byte Z = 0xA3;
    Byte W = 0x94;

    Foo foo = new Foo(X, Y, Z, W);
    uint i = foo ;

    Foo bar = (uint)(i * 1.5d);

    Console.WriteLine(X * 1.5d == bar.X);
    Console.WriteLine(Y * 1.5d == bar.Y);
    Console.WriteLine(Z * 1.5d == bar.Z);
    Console.WriteLine(W * 1.5d == bar.W);
}

[StructLayout(LayoutKind.Explicit)]
public struct Foo
{
    [FieldOffset(0)]
    public byte X;

    [FieldOffset(1)]
    public byte Y;

    [FieldOffset(2)]
    public byte Z;

    [FieldOffset(3)]
    public byte W;

    [FieldOffset(0)]
    public uint Value;


    public Foo(byte x, byte y, byte z, byte w) : this()
    {
        X = x;
        Y = y;
        Z = z;
        W = w;
    }

    public static implicit operator Foo(uint value)
    {
        return new Foo(){ Value = value };
    }

    public static implicit operator uint(Foo foo)
    {
        return foo.Value;
    }

}

We create a new type that, instead of does bit shifting, gives you direct (type safe) access to the memory address that is inside the uint.

Upvotes: 0

Roman Starkov
Roman Starkov

Reputation: 61512

The only reason it doesn't work for 1.5f is because floats are not precise enough. Try 1.5d (for double) and your example will work. However this approach is limited to "nice" cases, i.e. those where the result in each byte is guaranteed to be a whole number. A special case is when you multiply by an integer, which will always work so long as none of the four results overflow.

It is also possible to do this for addition and subtraction provided that none of the individual bytes overflow. Obviously any overflow will mess up nearby bytes. This is particularly problematic if you wish to use 2's complement for negative bytes (-128 .. 127) because adding 3 to -2 is also an "overflow" and will mess up the next byte.

Upvotes: 5

Related Questions