Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best approach for serializing BigDecimal/BigInteger to ProtocolBuffers

I am starting to migrate a custom serialization mechanism to Protocol Buffers. One data type that will be used particularly regularly is BigDecimal.

Does anyone know of a good way of serializing this within Protocol Buffers? Our current serialization routine uses BigDecimal.toPlainString() for serialization, and new BigDecimal(String) for deserialization - I'm assuming there's a better way.

My guess is to define a BigDecimal as:

message BDecimal {
    required int32 scale = 1;
    required BInteger int_val = 2;
}

But I am not too sure how to define BigInteger - perhaps using its toByteArray() method?

like image 911
Rich Avatar asked Jun 26 '09 23:06

Rich


People also ask

What is the difference between BigDecimal and BigInteger in Java?

The main difference between the BigInteger and BigDecimal is that BigInteger supports arbitrary-precision integers and BigDecimal is for arbitrary-precision fixed-point numbers.

How accurate is BigDecimal?

This limits it to 15 to 17 decimal digits of accuracy. BigDecimal can grow to any size you need it to. Double operates in binary which means it can only precisely represent numbers which can be expressed as a finite number in binary. For example, 0.375 in binary is exactly 0.011.

What is the range of BigDecimal?

A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale.

Does BigDecimal have a limit?

The limit is 32 * 2^32-1 bits for BigInteger or about 2^(4 billion). have a string with illegal digits.


2 Answers

Yes. You should define BigInteger as BigInteger.toByteArray() .

My guess is that BigDecimal would be:


message BDecimal {
  required int32 scale = 1;
  required BInteger int_val = 2;
}

while BigInteger may be defined as


message BInteger {
  required bytes value = 1;
}

The code to handle BigInteger would be:


  BInteger write(BigInteger val) {
    BInteger.Builder builder = BInteger.newBuilder();
    ByteString bytes = ByteString.copyFrom(val.toByteArray());
    builder.setValue(bytes);
    return builder.build();
  }

  BigInteger read(BInteger message) {
    ByteString bytes = message.getValue();
    return new BigInteger(bytes.toByteArray());
  }
like image 122
notnoop Avatar answered Sep 27 '22 09:09

notnoop


I have recently had the same need as OP, and used a similar solution to the one proposed by @notnoop. Posting it here after seeing this comment from @stikkos:

How would you convert a BigDecimal to a BigInteger and scale? And back ?

According to the BigDecimal class documentation:

The value of the BigDecimal is (unscaledVal × 10-scale), rounded according to the precision and rounding mode settings.

So, a java.math.BigDecimal can be serialized considering three properties:

  • unscaledValue
  • scale
  • precision

Now, the code.

  1. Protobuf v3 message definition:

    syntax = "proto3";
    
    message DecimalValue {
      uint32 scale = 1;
      uint32 precision = 2;
      bytes value = 3;
    }
    
  2. How to serialize a java.math.BigDecimal to a Protobuf message:

    import com.google.protobuf.ByteString;
    import DecimalValue;
    
    java.math.BigDecimal bigDecimal = new java.math.BigDecimal("1234.56789");
    DecimalValue serialized = DecimalValue.newBuilder()
            .setScale(bigDecimal.scale())
            .setPrecision(bigDecimal.precision())
            .setValue(ByteString.copyFrom(bigDecimal.unscaledValue().toByteArray()))
            .build();
    
  3. How to deserialize the Protobuf message back to a java.math.BigDecimal:

    java.math.MathContext mc = new java.math.MathContext(serialized.getPrecision());
    java.math.BigDecimal deserialized = new java.math.BigDecimal(
            new java.math.BigInteger(serialized.getValue().toByteArray()),
            serialized.getScale(),
            mc);
    
  4. We can check the obtained java.math.BigDecimal yields the same original value as follows:

    assert bigDecimal.equals(deserialized);
    

Note: OP assumed there is a better way to serialize a java.math.BigDecimal than using a plain String. In terms of storage, this solution is better.

For example, to serialize the first 100 decimals of π:

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679
System.out.println("DecimalValue: " + serialized.getSerializedSize());
System.out.println("String: " + StringValue.of(pi).getSerializedSize());

This yields:

DecimalValue: 48
String: 104
like image 8
Diego Hermida Avatar answered Sep 27 '22 09:09

Diego Hermida