GLSL floatBitsToInt: Inverse of intBitsToFloat

In OpenGL Shading Language (GLSL), transferring data between floating-point representations and raw binary integer formats is handled through bitwise reinterpretation rather than numerical type casting. While intBitsToFloat reconstructs an IEEE 754 32-bit floating-point value from a raw signed integer's binary layout, its direct inverse function is floatBitsToInt (along with its unsigned counterpart, floatBitsToUint). This article explains how these functions operate as exact inverses, the difference between bit-reinterpretation and numeric conversion, and the edge cases to consider when round-tripping float values in GPU shaders.

Bitwise Reinterpretation vs. Standard Casting

Standard type conversions such as float(myInt) or int(myFloat) perform numerical transformations. For instance, converting the integer 1 to a float transforms the value into 1.0, changing the underlying bit structure entirely.

In contrast, bit-casting functions preserve the exact binary pattern:

  • intBitsToFloat(int value): Interprets the raw 32 bits of a signed integer as an IEEE 754 single-precision floating-point number.
  • uintBitsToFloat(uint value): Performs the same operation using an unsigned integer bitfield.
  • floatBitsToInt(float value): The inverse operation; takes a floating-point number and returns a signed 32-bit integer containing the identical bit layout (1 sign bit, 8 exponent bits, 23 mantissa bits).
  • floatBitsToUint(float value): The unsigned inverse; returns the raw bit pattern as a 32-bit unsigned integer.

Function Signatures and Inversion Mechanics

The GLSL standard library defines these operations component-wise for scalars and vectors (vec2, vec3, vec4, and their integer equivalents):

// Converting raw integer bits back to floats
genType intBitsToFloat(genIType value);
genType uintBitsToFloat(genUType value);

// Converting floats to raw integer bits (Inverse)
genIType floatBitsToInt(genType value);
genUType floatBitsToUint(genType value);

When round-tripping a value from an integer representation to a float and back, the equality holds true:

int originalBits = 0x3F800000; // Binary representation of 1.0
float restoredFloat = intBitsToFloat(originalBits); // Value is 1.0
int recoveredBits = floatBitsToInt(restoredFloat);  // Yields 0x3F800000

Behavior with Special Floating-Point Values

When using floatBitsToInt to inspect or restore floating-point data, specific IEEE 754 behaviors apply:

Signed Zeros

Positive zero (+0.0) and negative zero (-0.0) evaluate as numerically equal under standard comparison (+0.0 == -0.0), but they have distinct bit patterns. floatBitsToInt(+0.0) produces 0x00000000, while floatBitsToInt(-0.0) produces 0x80000000 (or -2147483648). Using intBitsToFloat on either pattern preserves the sign bit correctly.

Infinities

Positive infinity (+inf) corresponds to 0x7F800000, and negative infinity (-inf) corresponds to 0xFF800000. Passing these values to intBitsToFloat reconstructs true mathematical infinity.

Not-a-Number (NaN) Handling

Any bit pattern where the exponent field consists entirely of ones and the mantissa is non-zero represents a NaN. While intBitsToFloat will reliably yield a NaN value, converting a NaN back via floatBitsToInt may not guarantee the exact preservation of the original payload bits on all GPU architectures, as some hardware canonicalizes NaNs during intermediate register moves.

Subnormals and Flush-to-Zero (FTZ)

On GPUs operating under flush-to-zero (FTZ) modes for performance reasons, subnormal numbers (numbers extremely close to zero where the exponent bits are all zero) might be truncated to 0.0. If FTZ is enforced by hardware, round-tripping subnormal bit patterns through intBitsToFloat and floatBitsToInt may collapse the pattern to 0x00000000.

Typical Use Cases

  1. GPU Data Packing and Unpacking: Storing packed vertex attributes or custom formats inside standard integer arrays (like Shader Storage Buffer Objects) and unpacking them inside shader stages without precision degradation.
  2. Bitwise Float Manipulation: Applying bitwise operations directly to floats, such as toggling sign bits with an XOR mask or manually extracting exponent bits for fast custom range-compression algorithms.
  3. Hashing and Encoding: Generating pseudo-random hashes from float values by inspecting their raw underlying bits using floatBitsToUint.