Reputation: 15767
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?
Upvotes: 37
Views: 40045
Reputation: 133
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.
Protobuf v3 message definition:
syntax = "proto3";
message DecimalValue {
uint32 scale = 1;
uint32 precision = 2;
bytes value = 3;
}
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();
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);
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
Upvotes: 11
Reputation: 1317
Why do you want to change it? Just because you can or is there a real need (like a profiling session confirming, that serialization/deserialization takes most of the time).
I would use a string, just because it is built in :)
The proposed byte array approach (What is the best approach for serializing BigDecimal/BigInteger to ProtocolBuffers) seems fine to me, if string representation seems to be an issue.
Upvotes: 1
Reputation: 59307
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());
}
Upvotes: 26