Skip to content

Add re-calibration function to ease comparison and minimize representation #280

Open
@samyonr

Description

@samyonr

In short, I want to go from a decimal, for example, 7, that is represented by 7000000000 * 10^(-9) (=decimal.New(7000000000, -9)) to 7 * 10^0 (=decimal.New(7, 0)) - these two representations are equal, but not according to reflect.DeepEqual (and the second representation seems to be computationally less expensive, although I'm not 100% sure about it). Not sure re-calibration is the most descriptive term, but I didn't find any other that will fit better.

Here's the use case:

I'm adding decimal Protobuf type based on https://github.com/googleapis/googleapis/blob/master/google/type/money.proto to my project, and implementing decimal-to-proto and proto-to-decimal conversion similar to what's described here: grpc/grpc-dotnet#424 (comment).

Here is the type:

// Example: 12345.6789 -> { units = 12345, nanos = 678900000 }
message DecimalValue {

  // The whole units of the amount.
  int64 units = 1;

  // Number of nano (10^-9) units of the amount.
  // The value must be between -999,999,999 and +999,999,999 inclusive.
  // If `units` is positive, `nanos` must be positive or zero.
  // If `units` is zero, `nanos` can be positive, zero, or negative.
  // If `units` is negative, `nanos` must be negative or zero.
  // For example -1.75 is represented as `units`=-1 and `nanos`=-750,000,000.
  sfixed32 nanos = 2;
}

Once such Protobuf is received, I'm checking its validity (Units and Nanos should be with the same sign or zero), and then create a decimal type:

const nanoFactor int32 = 1_000_000_000
const nanoExp int32 = 9

//d *DecimalValue, described in the protobuf above
dec := decimal.New((d.Units*int64(nanoFactor))+int64(d.Nanos), -nanoExp)

edit: or the following way, to avoid overflows for large numbers:

dec := decimal.NewFromInt(d.Units).Add(decimal.New(int64(d.Nanos), -nanoExp))

If this decimal type is in some struct together with many other variables, one may want to compare two such structs with reflect.DeepEqual. If one struct has a decimal of 7000000000 * 10^(-9) and another a decimal of 7 * 10^0 they won't be equal, although they are. There are of course many other possible representations.

To avoid it, together with any other consequences that can be caused be different or bigger representations, I do the following:

const nanoFactor int32 = 1_000_000_000
const nanoExp int32 = 9

//d *DecimalValue, described in the protobuf above
dec := decimal.New((d.Units*int64(nanoFactor))+int64(d.Nanos), -nanoExp)
ds := dec.String() // recalibrating the exponent, so instead of 7000000000 * 10^(-9), get 7 * 10^0

return decimal.NewFromString(ds)

This converts every representation to be the same (7 * 10^0 in the example). It seems to work fine, but I'm not sure that converting to string and back to decimal is computationally the best approach, and it feels cleaner to have a dedicated function to get the same result (minimize the representation).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions