How should one properly handle a conversion rounding error with BigDecimal in Java:
BigDecimal -> byte[] -> BigDecimal
I have a custom datatype 32 bytes in length (yep, 32 bytes not 32 bits) and I need to decode the fractional part of BigDecimal
into byte[]
.
I understand that I will lose some accuracy. Are there any established techniques to implement such a conversion?
NOTE:
It is fixed point datatype of form MxN
, where M % 8 == N % 8 == 0
Your fixed-point fractional part can be interpreted as the numerator, n, of a fraction n/(2256). I suggest, therefore, computing the BigDecimal
value representing 1/(2256) (this is exactly representable as a BigDecimal
) and storing a reference to it in a final static
field.
To convert to a byte[]
, then, use the two-arg version of BigDecimal.divideToIntegralValue()
to divide the fractional part of your starting number by 1/(2256), using the MathContext
argument to specify the rounding mode you want. Presumably you want either RoundingMode.HALF_EVEN
or RoundingMode.HALF_UP
. Then get the BigInteger
unscaled value of the result (which should be numerically equal to the scaled value, since an integral value should have scale 0) via BigDecimal.unscaledValue()
. BigInteger.toByteArray()
will then give you a byte[]
closely related to what you're after.*
To go the other way, you can pretty much reverse the process. BigDecimal
has a constructor that accepts a byte[]
that, again, is very closely related to your representation. Using that constructor, convert your byte[]
to a BigInteger
, and thence to BigDecimal
via the appropriate constructor. Multiply by that stored 1/(2256) value to get the fractional part you want.
* The biggest trick here may involve twiddling signs appropriately. If your BigDecimal
s may be negative, then you probably want to first obtain their absolute values before converting to byte[]
. More importantly, the byte[]
s produced and consumed by BigInteger
use a two's complement representation (i.e. with a sign bit), whereas I suppose you'll want an unsigned, pure binary representation. That mainly means that you'll need to allow for an extra bit -- and therefore a whole extra byte -- when you convert. Be also aware of byte order; check the docs of BigInteger
for the byte order it uses, and adjust as appropriate.