Question Details

No question body available.

Tags

c floating-point type-conversion ieee-754

Answers (6)

January 9, 2026 Score: 1 Rep: 35,155 Quality: Medium Completeness: 80%

All of these are non-portable as they involve bit-fields and attribute((packed)).

Bit-fields are extremely implementation-dependent (bolding is mine):

An implementation may allocate any addressable storage unit large enough to hold a bit- field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.

You simply cannot rely on where bits in bit-fields map to the underlying storage. What works on one platform is literally irrelevant on another.

The bit-field definitions also rely on an unsigned int being 32 bits. That's no necessarily true. An unsigned int can be smaller. It can be bigger. And strictly speaking, uint32t is an optional type.

And finally, POSIX reserves all identifiers ending with t

January 9, 2026 Score: 1 Rep: 36,689 Quality: Low Completeness: 30%

I think method one could lead into an undefined behavior.

This is correct.
Method 1 (cast) eventually involves type punning and invokes undefined behavior.

See more here: Why is type punning considered UB?.

January 9, 2026 Score: 1 Rep: 28,979 Quality: Low Completeness: 0%

I believe method 2 is the most canonical. Method 3 would be flagged as non-portable by the linter extension we use at work.

January 9, 2026 Score: 1 Rep: 35,155 Quality: Low Completeness: 30%

They're all completely non-portable given the use of bit-fields and attribute((packed))

January 9, 2026 Score: 0 Rep: 171 Quality: Low Completeness: 60%

Thanks for the hints. I also have thought about the attribute((packed)) which is AFAIK GCC and LLVM only.

Method 4 (Hope this is more portable, but fast written):

#include 
#include 

struct ieee754bin32 { unsigned int frac; unsigned int expo; unsigned int sign; };

void float
toieee754(struct ieee754bin32 ieee, float f) { unsigned int tmp;

if (!ieee) return;

memcpy(&tmp, &f, sizeof(float)); ieee->frac = (tmp & 0x7FFFFF); ieee->expo = ((tmp & 0x7F800000) >> 23); ieee->sign = ((tmp & 0x80000000) >> 31); }

void ieee754_to_float(float
f, struct ieee754_bin32 *ieee) { unsigned int tmp;

if (!f) return;

if (!ieee) return;

tmp = ieee->frac; tmp |= ieee->expo sign
January 9, 2026 Score: 0 Rep: 87,371 Quality: Low Completeness: 40%

And finally, POSIX reserves all identifiers ending with t

Since he is writing his own libc and libm I think his code qualifies as an implementation. If the type is not exposed outside the library, he is all good. If the type is exposed outside the library and he wants to be Posix compliant, then he should use t as a suffix.