Building a 32-bit float out of its 4 composite bytes
You could use a memcpy
(Result)
float f;
uchar b[] = {b3, b2, b1, b0};
memcpy(&f, &b, sizeof(f));
return f;
or a union* (Result)
union {
float f;
uchar b[4];
} u;
u.b[3] = b0;
u.b[2] = b1;
u.b[1] = b2;
u.b[0] = b3;
return u.f;
But this is no more portable than your code, since there is no guarantee that the platform is little-endian or the float
is using IEEE binary32 or even sizeof(float) == 4
.
(Note*: As explained by @James, it is technically not allowed in the standard (C++ §[class.union]/1) to access the union member u.f
.)
Building 32bit Float from 4 Bytes [Binary File I/O] in C++
So with a bit of effort and Igor's comment I was able to solve the problem. The following function reads everything into a buffer vector.
vector<char> buffer;
void fill() {
string filename = "";
cout << "Please enter a filename:\n>";
getline(cin, filename);
ifstream file(filename.c_str());
if (file) {
file.seekg(0,std::ios::end);
streampos length = file.tellg();
cout<< length << endl;
file.seekg(0,std::ios::beg);
file.seekg(540,'\0');
length-=540;
buffer.resize(length);
file.read(&buffer[0],length);
}
}
Then later on I call bytesToFloat in a loop. The endian-ness of bytesToFloat was incorrect so has now been reversed and it outputs the same values as my original file (I made my random file generator output a plain text version for comparison).
Does accessing the 4 bytes of a float break C++ aliasing rules
Is this actually valid C++ code?
Potentially yes. It has some pre-conditions:
std::uint8_t
must be an alias ofunsigned char
sizeof(float)
must be 4bytes + 3
mustn't overflow a buffer.
You can add a checks to ensure safe failure to compile if the first two don't hold:
static_assert(std::is_same_v<unsigned char, std::uint8_t>);
static_assert(sizeof(float) == 4);
I'm not sure whether it violates any aliasing rules.
unsigned char
is excempted of such restrictions. std::uint8_t
, if it is defined, is in practice an alias of unsigned char
, in which case the shown program is well defined. Technically that's not guaranteed by the rules, but the above check will handle the theoretical case where that doesn't apply.
float is guaranteed to be at least 32 bits long.
It must be exactly 32 bits long for the code to work. It must also have exactly the same bit-level format as was on the system where the float was serialised. If it's standard IEE-754 single precision on both ends then you're good; otherwise all bets are off.
Convert a float to 4 uint8_t
You normally do this by casting the float to an array of uint8_t.
In C you can do it like this:
uint8_t *array;
array = (unit8_t*)(&f);
in C++ use the reinterpret_cast
uint8_t *array;
array = reinterpret_cast<uint8_t*>(&f);
Then array[0], ..., array[3] are your bytes.
Techniques for reinterpreting the bits of one type as a different type
Thanks to @geza for pointing out that the "failures" were an artifact of printf
not being able to show very small numbers in %f
format. With %e
format the correct values are displayed.
In my tests, all of the various conversion methods discussed work, even the undefined behavior. Considering simplicity, performance, and portability it seems the solution using a union
is best:
uint32_t bitPattern;
volatile float bitPatternAsFloat;
union UintFloat {
uint32_t asUint;
float asFloat;
} uintFloat;
do {
bitPattern = i; // i is loop index
uintFloat.asUint = bitPattern;
bitPatternAsFloat = uintFloat.asFloat;
An interesting question is whether the float
member in the union
should be declared volatile
: it is never explicitly written, so might the compiler cache the initial value and reuse it, failing to notice that the corresponding uint32_t
is being updated? It appears to work OK on my current compiler without the volatile
declaration, but is this a potential 'gotcha'?
P.S. I just discovered [reinterpret_cast][1]
and wonder if that's the best solution.
SSE intrinsics: Convert 32-bit floats to UNSIGNED 8-bit integers
There is no direct conversion from float to byte, _mm_cvtps_pi8
is a composite. _mm_cvtps_pi16
is also a composite, and in this case it's just doing some pointless stuff that you undo with the shuffle. They also return annoying __m64
's.
Anyway, we can convert to dwords (signed, but that doesn't matter), and then pack (unsigned) or shuffle them into bytes. _mm_shuffle_(e)pi8
generates a pshufb
, Core2 45nm and AMD processors aren't too fond of it and you have to get a mask from somewhere.
Either way you don't have to round to the nearest integer first, the convert will do that. At least, if you haven't messed with the rounding mode.
Using packs 1: (not tested) -- probably not useful, packusdw
already outputs unsigned words but then packuswb
wants signed words again. Kept around because it is referred to elsewhere.
cvtps2dq xmm0, xmm0
packusdw xmm0, xmm0 ; unsafe: saturates to a different range than packuswb accepts
packuswb xmm0, xmm0
movd somewhere, xmm0
Using different shuffles:
cvtps2dq xmm0, xmm0
packssdw xmm0, xmm0 ; correct: signed saturation on first step to feed packuswb
packuswb xmm0, xmm0
movd somewhere, xmm0
Using shuffle: (not tested)
cvtps2dq xmm0, xmm0
pshufb xmm0, [shufmask]
movd somewhere, xmm0
shufmask: db 0, 4, 8, 12, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
Related Topics
Compare Two Float Variables in C++
Function Overloading Based on Value VS. Const Reference
How to Cout a Float Number with N Decimal Places
Turn Off Eclipse Errors (That Aren't Really Errors)
How to Use Std::Sort with a Vector of Structures and Compare Function
Using Struct Keyword in Variable Declaration in C++
How to Delete a Non-New Object
Why Floating Point Value Such as 3.14 Are Considered as Double by Default in Msvc
How to Add 2 Arbitrarily Sized Integers in C++
Why Can't I Write to a String Literal While I *Can* Write to a String Object
Why Doesn't My Template Accept an Initializer List
What Is Half Open Range and Off the End Value
Code::Blocks - How to Compile Multiple Source Files
Does a C++11 Range-Based for Loop Condition Get Evaluated Every Cycle