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:
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:

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.”
02Storage 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.
From 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:
-
CPU triggersillegal opcode exception (#UD)
-
The operating system captures the exception
-
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:
-
Semantic Integrity: Floating point values are an integral whole of sign, exponent, and mantissa
-
Hardware Optimization: FPU is specifically optimized for floating point operations
-
Standard Uniformity: IEEE 754 ensures cross-platform consistency
-
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!
Thank 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~
Feel free to follow the public account, give a “Share“, “Like“, or “View“~Wishing you successful compilation and no debugging errors!