Go Protobuf Precision Decimals
Asked Answered
S

1

11

What is the correct scalar type to use in my protobuf definition file, if I want to transmit an arbitrary-precision decimal value?

I am using shopspring/decimal instead of a float64 in my Go code to prevent math errors. When writing my protobuf file with the intention of transmitting these values over gRPC, I could use:

  • double which translates to a float64
  • string which would certainly be precise in its own way but strikes me as clunky
  • Something like decimal from mgravell/protobuf-net?

Conventional wisdom has taught me to skirt floats in monetary applications, but I may be over-careful since it's a point of serialization.

Sewole answered 30/5, 2018 at 19:20 Comment(7)
my decimal approach isn't arbitrary-precision; it was just whatever I'd need for .NET's System.Decimal. For arbitrary precision with x-plat, I'd probably just go string. And yes: absolutely, avoid float/double for finance appsAlteration
How about a uint64 as amount of cents/minimal denomination. That's usually how money is done.Ukase
You may want to consider using sint32/64 if your number could be negative (say for debits).Adhere
@MarcGravell Thanks for the thoughts/feedback. Want to make an Answer?Sewole
@Ukase I have heard of that method, but it supposes that you are not in the middle of a computation and have already done your rounding. "Minimal denomination" works great as a concept until you start dividing or multiplying by some fractional percentage or something.Sewole
Add as many powers of ten as you need to make rounding errors negligible. uint64 is huge.Ukase
We might be getting a standardized decimal type soon after all: github.com/protocolbuffers/protobuf/pull/7039Abecedary
M
3

If you really need arbitrary precision, I fear there is no correct answer right now. There is https://github.com/protocolbuffers/protobuf/issues/4406 open, but it does not seem to be very active. Without built-in support, you will really need to perform the serialization manually and then use either string or bytes to store the result. Which one to use between string and bytes likely depends on whether you need cross-platform/cross-library compatibility: if you need compatibility, use string and parse the decimal representation in the string using the appropriate arbitrary precision type in the reader; if you don't need it and you're going to read the data using the same cpu architecture and library you can probably just use the binary serialization provided by that library (MarshalBinary/UnmarshalBinary) and use bytes.

On the other hand, if you just need to send monetary values with an appropriate precision and do not need arbitrary precision, you can probably just use sint64/uint64 and use an appropriate unit (these are commonly called fixed-point numbers). To give an example, if you need to represent a monetary value in dollars with 4 decimal digits, your unit would be 1/10000th of a dollar so that e.g. the value 1 represents $0.0001, the value 19900 represents $1.99, -500000 represents $-50, and so on. With such a unit you can represent the range $-922,337,203,685,477.5808 to $922,337,203,685,477.5807 - that should likely be sufficient for most purposes. You will still need to perform the scaling manually, but it should be fairly trivial and portable. Given the range above, I would suggest using sint64 is preferable as it allows you also to represent negative values; uint64 should be considered only if you need the extra positive range and don't need negative values.

Alternatively, if you don't mind importing another package, you may want to take a look at https://github.com/googleapis/googleapis/blob/master/google/type/money.proto or https://github.com/googleapis/googleapis/blob/master/google/type/decimal.proto (that incidentally implement something very similar to the two models described above), and the related utility functions at https://pkg.go.dev/github.com/googleapis/go-type-adapters/adapters

As a side note, you are completely correct that you should almost never use floating point for monetary values.

Monoatomic answered 18/7, 2021 at 4:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.