Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to represent System.Decimal in Protocol Buffers?

Following on from this question, what would be the best way to represent a System.Decimal object in a Protocol Buffer?

like image 814
Cameron MacFarland Avatar asked Dec 16 '08 15:12

Cameron MacFarland


People also ask

What is protocol buffer format?

Protocol buffers are a combination of the definition language (created in . proto files), the code that the proto compiler generates to interface with data, language-specific runtime libraries, and the serialization format for data that is written to a file (or sent across a network connection).

What are the numbers in Protobuf?

In the binary format, the field number is combined with a type identifier. Field numbers from 1 to 15 can be encoded with their type as a single byte. Numbers from 16 to 2,047 take 2 bytes. You can go higher if you need more than 2,047 fields on a message for any reason.

How do I set default value in Protobuf?

For bool s, the default value is false. For numeric types, the default value is zero. For enums , the default value is the first value listed in the enum's type definition. This means care must be taken when adding a value to the beginning of an enum value list.

What is the difference between proto2 and Proto3?

Proto3 is the latest version of Protocol Buffers and includes the following changes from proto2: Field presence, also known as hasField , is removed by default for primitive fields. An unset primitive field has a language-defined default value.


4 Answers

Well, protobuf-net will simply handle this for you; it runs off the properties of types, and has full support for decimal. Since there is no direct way of expressing decimal in proto, it won't (currently) generate a decimal property from a ".proto" file, but it would be a nice tweak to recognise some common type ("BCL.Decimal" or similar) and interpret it as decimal.

As for representing it - I had a discussion document on this (now out of date I suspect) in the protobuf-net wiki area; there is now a working version in protobuf-net that simply does it for you.

No doubt Jon and I will hammer this out more later today ;-p

The protobuf-net version of this (in .proto) is something like (from here):

message Decimal {
  optional uint64 lo = 1; // the first 64 bits of the underlying value
  optional uint32 hi = 2; // the last 32 bis of the underlying value
  optional sint32 signScale = 3; // the number of decimal digits, and the sign
}
like image 155
Marc Gravell Avatar answered Sep 22 '22 03:09

Marc Gravell


Marc and I have very vague plans to come up with a "common PB message" library such that you can represent pretty common types (date/time and decimal springing instantly to mind) in a common way, with conversions available in .NET and Java (and anything else anyone wants to contribute).

If you're happy to stick to .NET, and you're looking for compactness, I'd possibly go with something like:

message Decimal {

    // 96-bit mantissa broken into two chunks
    optional uint64 mantissa_msb = 1;
    optional uint32 mantissa_lsb = 2;

    required sint32 exponent_and_sign = 3;
}

The sign can just be represented by the sign of exponent_and_sign, with the exponent being the absolute value.

Making both parts of the mantissa optional means that 0 is represented very compactly (but still differentiating between 0m and 0.0000m etc). exponent_and_sign could be optional as well if we really wanted.

I don't know about Marc's project, but in my port I generate partial classes, so you can the put a conversion between System.Decimal and Protobuf.Common.Decimal (or whatever) into the partial class.

like image 44
Jon Skeet Avatar answered Sep 21 '22 03:09

Jon Skeet


A slightly simpler to implement approach than Jon or Marc's is to store it as 4 sint32 values, which conveniently maps trivially to the output of Decimal.GetBits().

The proto file will look like:

message ProtoDecimal {
    sint32 v1 = 1;
    sint32 v2 = 2;
    sint32 v3 = 3;
    sint32 v4 = 4;
}

And the converter will be:

public decimal ConvertToDecimal(AbideDecimal value)
{
    return new decimal(new int[] { value.V1, value.V2, value.V3, value.V4 });
}

public ProtoDecimal ConvertFromDecimal(decimal value)
{
    var bits = decimal.GetBits(value);
    return new ProtoDecimal 
    {
        V1 = bits[0],
        V2 = bits[1],
        V3 = bits[2],
        V4 = bits[3]
    }
}

This might not be as simple in other languages, but if you only have to target C# then it will take up the same maximum of 16 bytes that the other approach will (although values like 0 might not be as compactly stored - I don't know enough about the intricate details of how protobuf stores ints), while being much clearer to dumb-dumb programmers like me :)

Obviously you will have to race the horses if you want to test performance but I doubt there is much in it.

like image 25
RB. Avatar answered Sep 20 '22 03:09

RB.


When you know you have a limited number of decimals, you can use the smallest possible unit as an integer value. For example, when handling money one don't require a decimal type but instead can define to use the unit cents. Then an integer with value 2 would refer to 0.02 in whatever currency is used.

like image 44
Bruno Zell Avatar answered Sep 23 '22 03:09

Bruno Zell