I want to convert a Java double (IEEE754 Double precision 64-bit) to it's binary representation, modify the 21 least significant bits to embed some metadata into the double, convert it back to a double, and maintain 6 decimal places of precision.
Constraint: The double values I will be dealing with will always be in the range of [-180, 180].
Example:
Double value: -145.88160204733163
IEEE754 Double precision 64-bit binary:
1100 0000 0110 0010 0011 1100 0011 0110 0001 0101 0111 1111 0010 1100 0000 1000
IEEE754 Double precision 64-bit binary with 21 least significant bits modified:
1100 0000 0110 0010 0011 1100 0011 0110 0001 0101 0110 0010 1001 1000 0110 0101
Double value with 21 least significant bits modified:
-145.88160199410336
I understand 1 bit needs to be maintained for the sign, 11 bits for the exponent, and 7 bits in the mantissa for the whole number between -180 and 180. Since I need to maintain 6 decimal places of precision, I thought an additional 24 bits for the significant figures would be sufficient to maintain 6 decimal places of precision (since 3.32 bits are required per digit, my understanding here might be incorrect) so I could use the 21 least significant bits to embed the metadata.
I'd like to know where I'm misunderstanding how 64-bit doubles are represented in binary and if there's any other way to modify the bits of a double without losing the required precision.
Any input is greatly appreciated!
When OP code changes the 21 least significant bits of the IEEE754 Double precision 64-bit value, the result is a value greater or less than the original. Sometimes that change, even if only a change of 1 least significant bit in Double
is enough to change the output when printed to 6 fractional decimal places.
Consider a decimal value ddd.dddddd5, where d
are digits 0-9. We have a value half-way between two ddd.dddddd values. That value in rarely encodable exactly as a Double
*1. A nearby Double
is used, which when printed with 6 decimal places rounds to nearest 0.000001. Output is ddd.dddddd or 0.000001 more depending on if the that nearest Double
was a tad more or less than ddd.dddddd5.
If it prints as ddd.dddddd and the payload is more than the original 21 bits - even just 1 least significant bit more, the value prints 0.000001 more. Like-wise, if it prints as ddd.dddddd + 0.000001 and the payload is less, it prints as ddd.dddddd.
To achieve OP's goal, we could round values to the nearest 0.000001 to get them away from the ddd.dddddd5 boundaries. Then we can successfully replace the least 21-bits as OP has described and reasoned.
Rounding is tricky as many rounding techniques incorrectly handle the edge conditions such as poor round(x*1000000.0)/1000000.0
. The problem here is x * 1000000.0
does not always form an exact product - which is critical for edge cases.
I am not well versed in java, yet printing to 6 decimal fraction places and converting back to Double
could do the trick.
Note that this answers differs from my comment. In that case I was focused on not changing the d
digits of the value ddd.ddddddxxxxxx if printed out to many decimal places. As I see it now, OP's want the same output when printed to 6 fractional decimal places.
*1 All Double
, when expressed exactly are have a decimal fraction of 0.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, .... Not 0.1, 0.2, 0.3, 0.4, 0.6, ...