GLSL unpackDouble2x32: Decoding Double Floats

The GLSL function unpackDouble2x32 decodes a 64-bit double-precision floating-point scalar into two 32-bit unsigned integers. This article explains how the function operates at the bit level, how it maps IEEE 754 double-precision fields into a two-component vector (uvec2), and the practical scenarios where this bit-level unpacking is required in shader programming.

Function Signature and Purpose

In GLSL, the function is defined as:

uvec2 unpackDouble2x32(double v);

The function takes a single 64-bit double-precision floating-point value (double) and unpacks it into a two-component 32-bit unsigned integer vector (uvec2).

Importantly, unpackDouble2x32 does not perform a numerical type conversion (such as rounding or casting float values to integer values). Instead, it performs a bitwise reinterpretation—essentially a bitcast—treating the underlying 64 raw bits of the IEEE 754 double as two distinct 32-bit unsigned integers.

IEEE 754 Double-Precision Layout

A standard IEEE 754 64-bit float consists of three segments:

  • Sign bit: 1 bit (bit 63)
  • Exponent: 11 bits (bits 62 to 52)
  • Mantissa (Significand): 52 bits (bits 51 to 0)

Component Mapping and Bit Distribution

When unpackDouble2x32 unpacks the 64-bit value into a uvec2, it divides the bits strictly by significance:

  1. result.x (Low-order 32 bits):
    • Receives bits [31:0].
    • Contains the least significant 32 bits of the 52-bit mantissa.
  2. result.y (High-order 32 bits):
    • Receives bits [63:32].
    • Contains the sign bit (bit 63), the entire 11-bit exponent (bits 62–52), and the most significant 20 bits of the mantissa (bits 51–32).

Regardless of the host system's endianness, GLSL specifies this behavior uniformly: .x consistently holds the least significant 32 bits, and .y holds the most significant 32 bits.

Example in GLSL

#version 450

void main() {
    double myDouble = 1.0; // Bit representation: 0x3FF0000000000000
    uvec2 unpacked = unpackDouble2x32(myDouble);

    // unpacked.x = 0x00000000u (lower 32 bits)
    // unpacked.y = 0x3FF00000u (upper 32 bits: sign, exponent, and high mantissa)
}

Common Use Cases

  • Bitwise Analysis: Extracting or analyzing specific components of a double, such as manually masking out the exponent or checking raw bit patterns for special values like NaNs or infinities.
  • Data Transmission and Interoperability: Storing or passing 64-bit data through storage buffers (SSBOs), vertex attributes, or communication pipelines that only expose 32-bit integer interfaces.
  • Emulated Precision: Manipulating double-precision values on GPU architectures or shader stages that lack full native 64-bit arithmetic or bitwise instructions for doubles.

To reverse this process and reconstruct the original 64-bit float from its 32-bit halves, GLSL provides the inverse function, packDouble2x32(uvec2 v).