Can Bitwise AND Operator & Be Used on Floating Point Numbers in C? (Part 1) The Mystery of IEEE 754 Standard and Floating Point Representation

In C language development, when we attempt to use the bitwise AND operator & on floating point numbers, the compiler will throw an error without mercy. This phenomenon is rooted in the basic principles of number representation, base conversion, and floating point structure in computer science. By delving deeper into this issue, we can uncover how the underlying mechanisms of how computers handle numerical values are “interfering”. This article will gradually reveal the mysteries of numeral systems and floating point representation, and will provide an in-depth analysis of the storage structure of floating point numbers.

01ExampleAt the end of a previous article on C language operators, a question was posed: Can & be used on floating point numbers? Why? Let’s first look at the actual output results, with the code on the left and the output on the right:Can Bitwise AND Operator & Be Used on Floating Point Numbers in C? (Part 1) The Mystery of IEEE 754 Standard and Floating Point Representation

The image above shows the compilation error in the GCC/CLang environment, which can be translated to: Error: invalid operands of binary operator & (have ‘float’ and ‘float’)

If we were using MSVC, the error would look something like this:Can Bitwise AND Operator & Be Used on Floating Point Numbers in C? (Part 1) The Mystery of IEEE 754 Standard and Floating Point RepresentationCan Bitwise AND Operator & Be Used on Floating Point Numbers in C? (Part 1) The Mystery of IEEE 754 Standard and Floating Point Representation

Key Point: The C language standard (ISO/IEC 9899:2011 §6.5.10) clearly states:

“The operands of the bitwise AND operator & shall have integer type.”

Can Bitwise AND Operator & Be Used on Floating Point Numbers in C? (Part 1) The Mystery of IEEE 754 Standard and Floating Point Representation02Storage Structure of IEEE 754Floating point numbers are stored using the IEEE 754 standard, which defines various floating point formats including single precision (32 bits), double precision (64 bits), and extended precision, with single and double precision being the most commonly used.Single precision floating point (32 bits) anatomy:

// Single precision floating point memory layout
31  30           23 22                           0
┌───┬──────────────┬─────────────────────────────┐
│ S │  Exponent    │          Mantissa           │
│ 1 │     8 bits   │           23 bits           │
└───┴──────────────┴─────────────────────────────┘
// Single precision floating point: consists of 1 sign bit (S), 8 exponent bits (E), and 23 mantissa bits (M).
// The sign bit indicates the sign of the value, 0 for positive and 1 for negative;
// The exponent uses a biased representation with a bias of 127 to indicate the magnitude of the value;
// The mantissa stores the fractional part of the value, implicitly assuming a leading 1 (in normalized form), with a precision of about 7 decimal digits.

Can Bitwise AND Operator & Be Used on Floating Point Numbers in C? (Part 1) The Mystery of IEEE 754 Standard and Floating Point RepresentationFrom the diagram above, we can see that its internal structure includes the exponent and mantissa, and this binary representation fundamentally prevents direct bitwise operations, which is essentially different from the binary representation of integers:

Component Bit Count Function Characteristics
Sign Bit (S) 1 bit Determines sign (0 for positive / 1 for negative) Directly determines the direction of the value
Exponent Field (E) 8 bits Stores the biased exponent value in scientific notation Uses biased encoding (bias of 127)
Mantissa Field (M) 23 bits Stores the fractional part of the significant digits Implicit leading 1 (for normalized numbers)

Key special values defined by IEEE 754:

1. Infinity

When the exponent field is all 1s and the mantissa field is all 0s, it represents infinity. Depending on the sign bit, it can be positive infinity (sign bit is 0) or negative infinity (sign bit is 1). Infinity is often used to represent overflow or division by zero.

// Positive infinity representation: 0x7F800000
float pos_inf = 1.0f / 0.0f; 
// Bitwise operation corruption: uint32_t inf_bits = *(uint32_t*)&pos_inf;inf_bits &= 0x7FFFFFFF; // Clear sign bit → Result still positive infinity?
// Actually becomes: 0x7F800000 & 0x7FFFFFFF = 0x7F800000

2. NaN (Not a Number)

When the exponent field is all 1s and the mantissa field is not all 0s, it represents NaN. NaN is used to represent invalid operation results, such as taking the square root of a negative number or 0/0, which are undefined mathematical operations. Any operation involving NaN will also result in NaN.

// Typical NaN: 0x7FC00000
float nan = sqrt(-1.0f);
// Bitwise operation corruption: uint32_t nan_bits = *(uint32_t*)&nan;nan_bits &= 0xFFBFFFFF; // Modify mantissa field
// Result may become silent NaN or signaling NaN, behavior is unpredictable

3. The Duality of Zero

There are two types of zero: positive zero (sign bit is 0, exponent and mantissa fields are all 0) and negative zero (sign bit is 1, exponent and mantissa fields are all 0). In certain computational scenarios, positive zero and negative zero can yield different results, such as 1/positive zero being positive infinity and 1/negative zero being negative infinity.

// +0.0: 0x00000000
// -0.0: 0x80000000
// Bitwise operation confusion: float pos_zero = 0.0f;float neg_zero = -0.0f;uint32_t* p1 = (uint32_t*)&pos_zero;uint32_t* p2 = (uint32_t*)&neg_zero;
*p1 &= *p2; // 0x00000000 & 0x80000000 = 0x00000000
// Mathematically, (-0) & (+0) should be? Actual result is still +0

03How Bitwise Operations Can Corrupt Floating Point StructureLet’s look at another case, first defining a floating point number:

// Case demonstration: floating point representation of 6.625
float f = 6.625f; // Binary: 0x40D40000
// Memory layout analysis:// 0    10000001    10101000000000000000000// S    Exponent    Mantissa// ↓        ↓           ↓// 0    129-127=2   1.10101 → 110.101 = 6.625

Assuming we perform a bitwise AND operation on the exponent field:

// Dangerous operation: attempting to preserve the exponent field
uint32_t* ptr = (uint32_t*)&f;
*ptr &= 0xFF800000; // Preserve sign and exponent, clear mantissa

Result analysis:

  • Original value: 0x40D40000 (6.625)

  • After operation: 0x40800000

  • New value calculation:<span><span>(-1)^0 × (1 + 0) × 2^(129-127) = 1 × 1 × 4 = 4.0</span></span>

    Precision completely lost!

04From Multiple Perspectives: How CPUs Handle Floating Point NumbersModern CPU’s floating point units (FPU) mainly consist of:

Component Function Bitwise Operation Support
Register File Stores floating point operands ❌ Not supported
Adder Handles floating point addition and subtraction
Multiplier Handles floating point multiplication
Divider/Squarer Handles complex operations

From the perspective of instruction set differences:

// x86 integer bitwise operation AND EAX, EBX  // Valid instruction
// x86 floating point bitwise operation - does not exist! // No FAND instruction

From the perspective of hardware exception mechanisms:

When attempting to perform bitwise operations on floating point registers:

  1. CPU triggersillegal opcode exception (#UD)

  2. The operating system captures the exception

  3. Usually translates toSIGILL signal (Unix) or exception code 0xC000001D (Windows)

05Analysis of the “Example” and Alternative SolutionsNow let’s revisit our “example”, & is the bitwise AND operator, which requires operands to be of integer type (such as int, char, etc.), while floating point numbers are stored in memory in IEEE 754 standard format, which includes exponent and mantissa parts, and this structure does not support bitwise operations. Therefore, based on this characteristic, if we use the & operator on floating point numbers, the compiler will throw an error.So what alternative solutions can perform bitwise operations?1. Union Method (Recommended in C99)

typedef union {    float f_val;    uint32_t u_val;} FloatInt;
FloatNum converter;converter.f_val = -12.375f;
// Safe access to bit pattern
uint32_t sign_bit = converter.u_val >> 31;

2. Memory Copy Method (Safest)

#include<string.h>float sensor_data = get_value();uint32_t bits;// Avoid direct pointer conversion
memcpy(&bits, &sensor_data, sizeof(float)); // After operating bits, if needed to write back
memcpy(&sensor_data, &bits, sizeof(float));

06Conclusion

The deep reasons for disabling bitwise operations on floating point numbers are:

  1. Semantic Integrity: Floating point values are an integral whole of sign, exponent, and mantissa

  2. Hardware Optimization: FPU is specifically optimized for floating point operations

  3. Standard Uniformity: IEEE 754 ensures cross-platform consistency

  4. Safety Protection: Prevents the generation of illegal values leading to undefined behavior

When the compiler rejects the & operation on floating point numbers, it is not limiting your freedom, but guarding the order of the numerical world.

Thought Question:If executing <span>float f = 0.1f + 0.2f;</span>, what is its precise binary representation?Feel free to share your calculation process in the comments!Can Bitwise AND Operator & Be Used on Floating Point Numbers in C? (Part 1) The Mystery of IEEE 754 Standard and Floating Point RepresentationThank you for your attention and support. I will periodically update various useful content. If you have any questions or different opinions, feel free to comment or message, let’s learn and exchange together~Can Bitwise AND Operator & Be Used on Floating Point Numbers in C? (Part 1) The Mystery of IEEE 754 Standard and Floating Point RepresentationFeel free to follow the public account, give a “Share“, “Like“, or “View“~Wishing you successful compilation and no debugging errors!

Leave a Comment