0

I want to increase the exponent of a primitive double d, thus multiplying it by a power of two, for example 2^64.

I can do d *= math.pow(2, 64) or calculate the power in advance if known: d *= 18446744073709551616.

But I guess this is doing some expensive operations like multiplication, where we know in that case that all is needed is one addition of exponents. For example if it is possible to interpret the double as a long without converting it, we could do: (it does not work like that)

long longFromD = d;    // imagine the casting does not change any bits from d.
double dTimes2ToThe64 = longFromD + (64L << 52)   // shifting 64 to be aligned with the exponent of d

And that way we could get this multiplication with just an addition.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
talz
  • 1,004
  • 9
  • 22
  • 1
    It's possible to interpret a double as long. See `Double.doubleToLongBits()` and `Double.longBitsToDouble()` – Johannes Kuhn Feb 06 '21 at 01:09
  • 1
    reinterpreting bits is called "type punning" (especially in "unsafe" languages like C and C++ where you can do it with memcpy for example, without needing special support from the language). Re-tagged, unfortunately SO limits to only 5 tags. – Peter Cordes Feb 06 '21 at 02:52
  • 2
    note that instead of `math.pow(2, 64)` you can just use 0x1.0p64 – phuclv Feb 06 '21 at 05:02
  • 1
    If Java has `ldexp()` like C (I suspect it does not), use `ldexp(x, n)` to quickly scale by a power-of-2. – chux - Reinstate Monica Feb 06 '21 at 05:16

1 Answers1

2

With the functions from the comment by @Johannes Kuhn:

public static double multiplyByAPowerOfTwo(double d, int exponent) {
    long dInterpretedAsLong = Double.doubleToLongBits(d);
    dInterpretedAsLong += ((long) exponent) << 52;   // add exponents
    return Double.longBitsToDouble(dInterpretedAsLong)
}

Another option per @phuclv's comment, using a HexadecimalFloatingPointLiteral:

d *= 0x1.0p64;

For this method the exponent obviously has to be known in advance. This is presumably the same as d *= 18446744073709551616d;, but it is quite a lot more elegant to write. With this method we have one double multiplication which is hopefully about as efficient as just an addition of exponents (if a multiplication does happen, it's by one).

talz
  • 1,004
  • 9
  • 22
  • 1
    For a known constant `exponent`, this might be slower than an x86 `vmulsd` instruction depending on how good a job the JIT compiler does. For a variable exponent, this might be good if it avoids an actual exponential calculation that wouldn't take advantage of the number being a known power of 2. If `2^exponent` fits in a 64-bit integer, I guess you could do `d *= 1LL< – Peter Cordes Feb 06 '21 at 02:37
  • Beware that a large exponent will carry-out into the sign bit of the IEEE double, *not* saturate to +-Inf like normal IEEE overflow behaviour. – Peter Cordes Feb 06 '21 at 02:38
  • *if a multiplication does happen, it's by one* - uh, you mean in the mantissa? Modern FP mul/add/FMA units have performance that's independent of the data, so they can be fully pipelined (starting a new operation every clock cycle per execution unit - if some finished early they'd collide). All that matters for performance is that it doesn't overflow, and the inputs and output are all "normalized" doubles. (On some CPUs NaN or Inf can be slower, and often a subnormal tiny input is slower.) – Peter Cordes Feb 07 '21 at 00:56
  • Also note that this will make `0.0 * 2` = `DBL_MIN`; FP zero is an all-zero exponent and an all-zero mantissa. A non-zero exponent with a zero mantissa is some power of 2. The smallest non-zero exponent is `DBL_MIN`, the smallest-magnitude normalized value. – Peter Cordes Oct 03 '22 at 20:46