Note: I mistakenly asked about static_cast originally; this is why the top answer mentions static_cast at first.
I have some binary files with little endian float values. I want to read them in a machine-independent manner. My byte-swapping routines (from SDL) operate on unsigned integers types.
Is it safe to simply cast between ints and floats?
float read_float() {
// Read in 4 bytes.
Uint32 val;
fread( &val, 4, 1, fp );
// Swap the bytes to little-endian if necessary.
val = SDL_SwapLE32(val);
// Return as a float
return reinterpret_cast<float &>( val ); //XXX Is this safe?
}
I want this software to be as portable as possible.
Well, static_cast is "safe" and it has defined behavior, but this is probably not what you need. Converting an integral value to float type will simply attempt to represent the same integral value in the target floating-point type. I.e. 5 of type int will turn into 5.0 of type float (assuming it is representable precisely).
What you seem to be doing is building the object representation of float value in a piece of memory declared as Uint32 variable. To produce the resultant float value you need to reinterpret that memory. This would be achieved by reinterpret_cast
assert(sizeof(float) == sizeof val);
return reinterpret_cast<float &>( val );
or, if you prefer, a pointer version of the same thing
assert(sizeof(float) == sizeof val);
return *reinterpret_cast<float *>( &val );
Although this sort of type-punning is not guaranteed to work in a compiler that follows strict-aliasing semantics. Another approach would be to do this
float f;
assert(sizeof f == sizeof val);
memcpy(&f, &val, sizeof f);
return f;
Or you might be able to use the well-known union hack to implement memory reinterpretation. This is formally illegal in C++ (undefined behavior), meaning that this method can only be used with certain implementations that support it as an extension
assert(sizeof(float) == sizeof(Uint32));
union {
Uint32 val;
float f;
} u = { val };
return u.f;
In short, it's incorrect. You are casting an integer to a float, and it will be interpreted by the compiler as an integer at the time. The union solution presented above works.
Another way to do the same sort of thing as the union is would be to use this:
return *reinterpret_cast<float*>( &val );
It is equally safe/unsafe as the union solution above, and I would definitely recommend an assert to make sure float is the same size as int.
I would also warn that there ARE floating point formats that are not IEEE-754 or IEEE-854 compatible (these two standards have the same format for float numbers, I'm not entirely sure what the detail difference is, to be honest). So, if you have a computer that uses a different floating point format, it would fall over. I'm not sure if there is any way to check that, aside from perhaps having a canned set of bytes stored away somewhere, along with the expected values in float, then convert the values and see if it comes up "right".
(As others have said, a reinterpret cast, where the underlying memory is treated as though it's another type, is undefined behaviour because it's up to the C++ implementation how the float is sized/aligned/placed in memory.)
Here's a templated implementation of AnT's memcpy solution, which avoids -Wstrict-aliasing warnings.
I guess this supports implementations where the sizes aren't standard, but still match one of the templated sizes - and then fails to compile if there are no matches.
(compiling with -fstrict-aliasing -Wall that actually enables -Wstrict-aliasing)
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <type_traits>
template<size_t S> struct sized_uint;
template<> struct sized_uint<sizeof(uint8_t)> { using type = uint8_t; };
template<> struct sized_uint<sizeof(uint16_t)> { using type = uint16_t; };
template<> struct sized_uint<sizeof(uint32_t)> { using type = uint32_t; };
template<> struct sized_uint<sizeof(uint64_t)> { using type = uint64_t; };
template<size_t S> using sized_uint_t = typename sized_uint<S>::type;
template<class T> sized_uint_t<sizeof(T)> bytesAsUint(T x)
{
sized_uint_t<sizeof(T)> result;
// template forces size to match. memcpy handles alignment
memcpy(&result, &x, sizeof(x));
return result;
}
template<size_t S> struct sized_float;
template<> struct sized_float<sizeof(float)> { using type = float; };
template<> struct sized_float<sizeof(double)> { using type = double; };
template<size_t S> using sized_float_t = typename sized_float<S>::type;
template<class T> sized_float_t<sizeof(T)> bytesAsFloat(T x)
{
sized_float_t<sizeof(T)> result;
memcpy(&result, &x, sizeof(x));
return result;
}
// Alt for just 'float'
//template<class T> std::enable_if_t<sizeof(T) == sizeof(float), float> bytesAsFloat(T x)
//{
// float result;
// memcpy(&result, &x, sizeof(x));
// return result;
//}
float readIntAsFloat(uint32_t i)
{
// error: no matching function for call to 'bytesAsFloat(uint16_t)'
//return bytesAsFloat((uint16_t)i);
return bytesAsFloat(i);
}
void printFloat(float f) {
// warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
//printf("Float %f: %x", f, reinterpret_cast<unsigned int&>(f));
printf("Float %f: %x", f, bytesAsUint(f));
}
(godbolt)
Related
Which way in below tests is the most preferred in terms of dealing with undefined behavior, auto-vectorization (for struct of arrays) and portability (clang,gcc,msvc,icc)?
Is there another way of doing same operation?
#include <iostream>
#include <cstring>
union trick1
{
float fvar;
int ivar;
};
struct trick2
{
float fvar;
int ivar()
{
int result;
std::memcpy(&result,&fvar,sizeof(float));
return result;
}
};
struct trick3
{
float fvar;
int ivar()
{
int result=0;
asm ("mov %0,%0"
: "=r" (result)
: "0" (fvar));
return result;
}
};
struct trick4
{
float fvar;
int ivar()
{
int result;
result = *reinterpret_cast<int*>(&fvar);
return result;
}
};
int main()
{
trick1 test1;
test1.fvar = 3.14f;
// 1078523331
std::cout<<test1.ivar<<std::endl;
trick2 test2;
test2.fvar = 3.14f;
// 1078523331
std::cout<<test2.ivar()<<std::endl;
trick3 test3;
test3.fvar = 3.14f;
// 1078523331
std::cout<<test3.ivar()<<std::endl;
trick4 test4;
test4.fvar = 3.14f;
// 1078523331
std::cout<<test4.ivar()<<std::endl;
return 0;
}
For example, is memcpy ok for converting array of floats to array of integers bitwise?
trick1 (union): Undefined behaviour in ISO C++, unlike ISO C99.
The C++ compilers you mentioned support it as an extension in C++.
trick2 (std::memcpy) is your best choice before C++20: Well defined with the precondition that sizeof(int) == sizeof(float), but not as simple as std::bit_cast. Mainstream compilers handle it efficiently, not actually doing an extra copy of anything (effectively optimizing it away), as long as the copy size is a single primitive type and writes all the bytes of the destination object.
trick3 (inline asm): Non-standard; not portable (neither CPU arch nor compiler). Seriously hinders optimisation, including auto-vectorization.
trick4 (deref a reinterpret_cast pointer): Undefined behaviour in ISO C++, and in practice on many real compilers (notably GCC and Clang), unless you compile with gcc -fno-strict-aliasing.
I recommend C++20 std::bit_cast when applicable. It's as efficient as memcpy, and cleaner syntax:
return std::bit_cast<int>(fvar);
I found a rather strange but working square root approximation for floats; I really don't get it. Can someone explain me why this code works?
float sqrt(float f)
{
const int result = 0x1fbb4000 + (*(int*)&f >> 1);
return *(float*)&result;
}
I've test it a bit and it outputs values off of std::sqrt() by about 1 to 3%. I know of the Quake III's fast inverse square root and I guess it's something similar here (without the newton iteration) but I'd really appreciate an explanation of how it works.
(nota: I've tagged it both c and c++ since it's both valid-ish (see comments) C and C++ code)
(*(int*)&f >> 1) right-shifts the bitwise representation of f. This almost divides the exponent by two, which is approximately equivalent to taking the square root.1
Why almost? In IEEE-754, the actual exponent is e - 127.2 To divide this by two, we'd need e/2 - 64, but the above approximation only gives us e/2 - 127. So we need to add on 63 to the resulting exponent. This is contributed by bits 30-23 of that magic constant (0x1fbb4000).
I'd imagine the remaining bits of the magic constant have been chosen to minimise the maximum error across the mantissa range, or something like that. However, it's unclear whether it was determined analytically, iteratively, or heuristically.
It's worth pointing out that this approach is somewhat non-portable. It makes (at least) the following assumptions:
The platform uses single-precision IEEE-754 for float.
The endianness of float representation.
That you will be unaffected by undefined behaviour due to the fact this approach violates C/C++'s strict-aliasing rules.
Thus it should be avoided unless you're certain that it gives predictable behaviour on your platform (and indeed, that it provides a useful speedup vs. sqrtf!).
1. sqrt(a^b) = (a^b)^0.5 = a^(b/2)
2. See e.g. https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Exponent_encoding
See Oliver Charlesworth’s explanation of why this almost works. I’m addressing an issue raised in the comments.
Since several people have pointed out the non-portability of this, here are some ways you can make it more portable, or at least make the compiler tell you if it won’t work.
First, C++ allows you to check std::numeric_limits<float>::is_iec559 at compile time, such as in a static_assert. You can also check that sizeof(int) == sizeof(float), which will not be true if int is 64-bits, but what you really want to do is use uint32_t, which if it exists will always be exactly 32 bits wide, will have well-defined behavior with shifts and overflow, and will cause a compilation error if your weird architecture has no such integral type. Either way, you should also static_assert() that the types have the same size. Static assertions have no run-time cost and you should always check your preconditions this way if possible.
Unfortunately, the test of whether converting the bits in a float to a uint32_t and shifting is big-endian, little-endian or neither cannot be computed as a compile-time constant expression. Here, I put the run-time check in the part of the code that depends on it, but you might want to put it in the initialization and do it once. In practice, both gcc and clang can optimize this test away at compile time.
You do not want to use the unsafe pointer cast, and there are some systems I’ve worked on in the real world where that could crash the program with a bus error. The maximally-portable way to convert object representations is with memcpy(). In my example below, I type-pun with a union, which works on any actually-existing implementation. (Language lawyers object to it, but no successful compiler will ever break that much legacy code silently.) If you must do a pointer conversion (see below) there is alignas(). But however you do it, the result will be implementation-defined, which is why we check the result of converting and shifting a test value.
Anyway, not that you’re likely to use it on a modern CPU, here’s a gussied-up C++14 version that checks those non-portable assumptions:
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <limits>
#include <vector>
using std::cout;
using std::endl;
using std::size_t;
using std::sqrt;
using std::uint32_t;
template <typename T, typename U>
inline T reinterpret(const U x)
/* Reinterprets the bits of x as a T. Cannot be constexpr
* in C++14 because it reads an inactive union member.
*/
{
static_assert( sizeof(T)==sizeof(U), "" );
union tu_pun {
U u = U();
T t;
};
const tu_pun pun{x};
return pun.t;
}
constexpr float source = -0.1F;
constexpr uint32_t target = 0x5ee66666UL;
const uint32_t after_rshift = reinterpret<uint32_t,float>(source) >> 1U;
const bool is_little_endian = after_rshift == target;
float est_sqrt(const float x)
/* A fast approximation of sqrt(x) that works less well for subnormal numbers.
*/
{
static_assert( std::numeric_limits<float>::is_iec559, "" );
assert(is_little_endian); // Could provide alternative big-endian code.
/* The algorithm relies on the bit representation of normal IEEE floats, so
* a subnormal number as input might be considered a domain error as well?
*/
if ( std::isless(x, 0.0F) || !std::isfinite(x) )
return std::numeric_limits<float>::signaling_NaN();
constexpr uint32_t magic_number = 0x1fbb4000UL;
const uint32_t raw_bits = reinterpret<uint32_t,float>(x);
const uint32_t rejiggered_bits = (raw_bits >> 1U) + magic_number;
return reinterpret<float,uint32_t>(rejiggered_bits);
}
int main(void)
{
static const std::vector<float> test_values{
4.0F, 0.01F, 0.0F, 5e20F, 5e-20F, 1.262738e-38F };
for ( const float& x : test_values ) {
const double gold_standard = sqrt((double)x);
const double estimate = est_sqrt(x);
const double error = estimate - gold_standard;
cout << "The error for (" << estimate << " - " << gold_standard << ") is "
<< error;
if ( gold_standard != 0.0 && std::isfinite(gold_standard) ) {
const double error_pct = error/gold_standard * 100.0;
cout << " (" << error_pct << "%).";
} else
cout << '.';
cout << endl;
}
return EXIT_SUCCESS;
}
Update
Here is an alternative definition of reinterpret<T,U>() that avoids type-punning. You could also implement the type-pun in modern C, where it’s allowed by standard, and call the function as extern "C". I think type-punning is more elegant, type-safe and consistent with the quasi-functional style of this program than memcpy(). I also don’t think you gain much, because you still could have undefined behavior from a hypothetical trap representation. Also, clang++ 3.9.1 -O -S is able to statically analyze the type-punning version, optimize the variable is_little_endian to the constant 0x1, and eliminate the run-time test, but it can only optimize this version down to a single-instruction stub.
But more importantly, this code isn’t guaranteed to work portably on every compiler. For example, some old computers can’t even address exactly 32 bits of memory. But in those cases, it should fail to compile and tell you why. No compiler is just suddenly going to break a huge amount of legacy code for no reason. Although the standard technically gives permission to do that and still say it conforms to C++14, it will only happen on an architecture very different from we expect. And if our assumptions are so invalid that some compiler is going to turn a type-pun between a float and a 32-bit unsigned integer into a dangerous bug, I really doubt the logic behind this code will hold up if we just use memcpy() instead. We want that code to fail at compile time, and to tell us why.
#include <cassert>
#include <cstdint>
#include <cstring>
using std::memcpy;
using std::uint32_t;
template <typename T, typename U> inline T reinterpret(const U &x)
/* Reinterprets the bits of x as a T. Cannot be constexpr
* in C++14 because it modifies a variable.
*/
{
static_assert( sizeof(T)==sizeof(U), "" );
T temp;
memcpy( &temp, &x, sizeof(T) );
return temp;
}
constexpr float source = -0.1F;
constexpr uint32_t target = 0x5ee66666UL;
const uint32_t after_rshift = reinterpret<uint32_t,float>(source) >> 1U;
extern const bool is_little_endian = after_rshift == target;
However, Stroustrup et al., in the C++ Core Guidelines, recommend a reinterpret_cast instead:
#include <cassert>
template <typename T, typename U> inline T reinterpret(const U x)
/* Reinterprets the bits of x as a T. Cannot be constexpr
* in C++14 because it uses reinterpret_cast.
*/
{
static_assert( sizeof(T)==sizeof(U), "" );
const U temp alignas(T) alignas(U) = x;
return *reinterpret_cast<const T*>(&temp);
}
The compilers I tested can also optimize this away to a folded constant. Stroustrup’s reasoning is [sic]:
Accessing the result of an reinterpret_cast to a different type from the objects declared type is still undefined behavior, but at least we can see that something tricky is going on.
Update
From the comments: C++20 introduces std::bit_cast, which converts an object representation to a different type with unspecified, not undefined, behavior. This doesn’t guarantee that your implementation will use the same format of float and int that this code expects, but it doesn’t give the compiler carte blanche to break your program arbitrarily because there’s technically undefined behavior in one line of it. It can also give you a constexpr conversion.
Let y = sqrt(x),
it follows from the properties of logarithms that log(y) = 0.5 * log(x) (1)
Interpreting a normal float as an integer gives INT(x) = Ix = L * (log(x) + B - σ) (2)
where L = 2^N, N the number of bits of the significand, B is the exponent bias, and σ is a free factor to tune the approximation.
Combining (1) and (2) gives: Iy = 0.5 * (Ix + (L * (B - σ)))
Which is written in the code as (*(int*)&x >> 1) + 0x1fbb4000;
Find the σ so that the constant equals 0x1fbb4000 and determine whether it's optimal.
Adding a wiki test harness to test all float.
The approximation is within 4% for many float, but very poor for sub-normal numbers. YMMV
Worst:1.401298e-45 211749.20%
Average:0.63%
Worst:1.262738e-38 3.52%
Average:0.02%
Note that with argument of +/-0.0, the result is not zero.
printf("% e % e\n", sqrtf(+0.0), sqrt_apx(0.0)); // 0.000000e+00 7.930346e-20
printf("% e % e\n", sqrtf(-0.0), sqrt_apx(-0.0)); // -0.000000e+00 -2.698557e+19
Test code
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
float sqrt_apx(float f) {
const int result = 0x1fbb4000 + (*(int*) &f >> 1);
return *(float*) &result;
}
double error_value = 0.0;
double error_worst = 0.0;
double error_sum = 0.0;
unsigned long error_count = 0;
void sqrt_test(float f) {
if (f == 0) return;
volatile float y0 = sqrtf(f);
volatile float y1 = sqrt_apx(f);
double error = (1.0 * y1 - y0) / y0;
error = fabs(error);
if (error > error_worst) {
error_worst = error;
error_value = f;
}
error_sum += error;
error_count++;
}
void sqrt_tests(float f0, float f1) {
error_value = error_worst = error_sum = 0.0;
error_count = 0;
for (;;) {
sqrt_test(f0);
if (f0 == f1) break;
f0 = nextafterf(f0, f1);
}
printf("Worst:%e %.2f%%\n", error_value, error_worst*100.0);
printf("Average:%.2f%%\n", error_sum / error_count);
fflush(stdout);
}
int main() {
sqrt_tests(FLT_TRUE_MIN, FLT_MIN);
sqrt_tests(FLT_MIN, FLT_MAX);
return 0;
}
The C++ standard doesn't require exact sizes of the integral types, which can sometimes lead to very unexpected results. Some smart people then introduced the <cstdint> header containing (optional) typedefs like int64_t for types with exactly 64-bit width. This is not what want.
Is there an (possibly optional) integral type with the property sizeof(mysterious_type) == 2 for whatever system is defined on?
Reasoning: I'm trying to figure out the system's endianess. For this, after reviewing many questions on this board, I though I would just define an integral type with size 2, assign one to it and check endianess like this:
enum endianess { LITTLE_ENDIAN, BIG_ENDIAN };
typedef utwowitdhtype test_t; // some unsigned type with width 2
endianess inspectSystem() {
static_assert(sizeof(twowitdhtype) == 2, "twowitdhtype is not size 2??!?!!");
test_t integral = 0x1;
return *reinterpret_cast<char*>(&integral) == 0 ? BIG_ENDIAN : LITTLE_ENDIAN;
}
While this is a reasoning why I would be interested in such a type, finding such a type is not to solve the problem but out of curiousity.
If you're on the machine that has a char size != 8 bits, you will have bigger portability issues you'll have to tackle - it's simpler to just do static_assert(CHAR_BIT == 8, "assuming 8-bit chars") than do weird things in the name of false portability - if you can't test it, how can you say you did it right?
Although for finding endianess it won't help at all, you can get such type by using type traits.
#include <type_traits>
template<typename T>
struct Identity
{
typedef T type;
};
template<typename... Args>
struct ShortTypeFinder;
template<>
struct ShortTypeFinder<>
{
};
template<typename Head, typename... Tail>
struct ShortTypeFinder<Head, Tail...>
{
typedef typename std::conditional<sizeof(Head) == 2, Identity<Head>, ShortTypeFinder<Tail...>>::type::type type;
};
typedef ShortTypeFinder<short, int, long, long long>::type integral_type_with_sizeof_two;
int main()
{
integral_type_with_sizeof_two x = 0;
static_assert(sizeof x == 2, "sizeof(x) must be 2");
static_assert(std::is_same<integral_type_with_sizeof_two, short>::value, "it's short on my machine");
}
If such type doesn't exist, the ShortTypeFinder<TYPES>::type will fail to compile.
As long as a byte consists of 8 bits, sizeof(int16_t) will always be 2. There are also types requiring at least 16 bits of size and some other interesting types.
http://en.cppreference.com/w/cpp/types/integer
We excessively use templates and we cannot always tell the signedness of a type at hand, so we need techniques to hide warnings that will be eventually optimized out. I have a simple ASSERT(condition) macro which throws something if the condition not evalutes to true.
The goal is to check the range of a T typed count value. We need it to be at least zero, at most the max of size_t.
template<typename SomeIntegral>
SomeIntegral ZERO()
{
return SomeIntegral(0);
}
template<typename T>
class C
{
public:
void f(T count)
{
std::vector<std::string> ret;
ASSERT(count>=ZERO<T>()); // Check #1
ASSERT(count<std::numeric_limits<size_t>::max()); // Check #2
ret.reserve(size_t(count)); // Needs check #1 and #2 to succeed.
// ...
}
// ...
};
The #1 check compiles without warning, but the #2 check says comparison between signed and unsigned integer expressions, because in this particular case the count has a signed type. If I could say ASSERT((unsigned T) count < std::numeric_limits<size_t>::max()) or similar somehow... Converting to unsigned T is safe in this situation, because we know from check #1 that it is at least zero.
... or what other compiler agnostic approach can I use?
I think you can use std::make_signed or std::make_unsigned. Whichever fits the need.
Here is a custom implementation.
namespace internal {
#define MK_MAKESIGNED(T,V) \
template<> struct make_signed<T> { \
public: \
typedef V type; \
};
template<typename T>
struct make_signed {
typedef T type;
};
MK_MAKESIGNED(unsigned int, signed int);
MK_MAKESIGNED(unsigned char, signed char);
// .... can convert anything to anything.
#undef MK_MAKESIGNED
};
internal::make_signed<unsigned char>::type c;
Apply static_cast<unsigned long> to both left hand and right hand side expressions in your second assert:
ASSERT(static_cast<unsigned long>(count)
< static_cast<unsigned long>(std::numeric_limits<size_t>::max()));
(I presume that your count is integer, not float - if I am wrong, go for a largest float type).
I want to convert float numbers from little endian to big endian but am not able to do it .
I have succesfuly converted endianess of int numbers but can somebody help with float numbers please
#include <cstring> // for std::memcpy
#include <algorithm> // for std::reverse
#include <iterator> // For C++11 std::begin() and std::end()
// converting from float to bytes for writing out
float f = 10.0;
char c[sizeof f];
std::memcpy(c,&f,sizeof f);
std::reverse(std::begin(c),std::end(c)); // begin() and end() are C++11. For C++98 say std::reverse(c,c + sizeof f);
// ... write c to network, file, whatever ...
going the other direction:
char c[] = { 41, 36, 42, 59 };
static_assert(sizeof(float) == sizeof c,"");
std::reverse(std::begin(c),std::end(c));
float f;
std::memcpy(&f,c,sizeof f);
The representation of floating point values is implementation defined, so the values resulting from this could be different between different implementations. That is, 10.0 byte swapped could be 1.15705e-041, or something else, or it might not be a valid floating point number at all.
However any implementation which uses IEEE 754 (which most do, and which you can check by seeing if std::numeric_limits<float>.is_iec559 is true), should give you the same results. (std::numeric_limits is from #include <limits>.)
The above code converts a float to bytes, modifies the bytes, and then converts those bytes back to float. If you have some byte values that you want to read as a float then you could set the values of the char array to your bytes and then use memcpy() as shown above (by the line after std::reverse()) to put those bytes into f.
Often people will recommend using reinterpret_cast for this sort of thing but I think it's good to avoid casts. People often use them incorrectly and get undefined behavior without realizing it. In this case reinterpret_cast can be used legally, but I still think it's better to avoid it.
Although it does reduce 4 lines to 1...
std::reverse(reinterpret_cast<char*>(&f),reinterpret_cast<char*>(&f) + sizeof f);
And here's an example of why you shouldn't use reinterpret_cast. The following will probably work but may result in undefined behavior. Since it works you probably wouldn't even notice you've done anything wrong, which is one of the least desirable outcomes possible.
char c[] = { 41, 36, 42, 59 };
static_assert(sizeof(float) == sizeof c,"");
float f = *reinterpret_cast<float*>(&c[0]);
The correct way to do such things is to use a union.
union float_int {
float m_float;
int32_t m_int;
};
That way you can convert your float in an integer and since you already know how to convert your integer endianess, you're all good.
For a double it goes like this:
union double_int {
double m_float;
int64_t m_int;
};
The int32_t and int64_t are usually available in stdint.h, boost offers such and Qt has its own set of definitions. Just make sure that the size of the integer is exactly equal to the size of the float. On some systems you also have long double defined:
union double_int {
long double m_float;
int128_t m_int;
};
If the int128_t doesn't work, you can use a struct as this:
union long_double_int {
long double m_float;
struct {
int32_t m_int_low;
int32_t m_int_hi;
};
};
Which could make you think that in all cases, instead of using an int, you could use bytes:
union float_int {
float m_float;
unsigned char m_bytes[4];
};
And that's when you discover that you don't need all the usual shifts used when doing such a conversion... because you can also declare:
union char_int {
int m_int;
unsigned char m_bytes[4];
};
Now your code looks very simple:
float_int fi;
char_int ci;
fi.m_float = my_float;
ci.m_bytes[0] = fi.m_bytes[3];
ci.m_bytes[1] = fi.m_bytes[2];
ci.m_bytes[2] = fi.m_bytes[1];
ci.m_bytes[3] = fi.m_bytes[0];
// now ci.m_int is the float in the other endian
fwrite(&ci, 1, 4, f);
[...snip...]
fread(&ci, 1, 4, f);
// here ci.m_int is the float in the other endian, so restore:
fi.m_bytes[0] = ci.m_bytes[3];
fi.m_bytes[1] = ci.m_bytes[2];
fi.m_bytes[2] = ci.m_bytes[1];
fi.m_bytes[3] = ci.m_bytes[0];
my_float = fi.m_float;
// now my_float was restored from the file
Obviously the endianess is swapped in this example. You probably also need to know whether you indeed need to do such a swap if your program is to be compiled on both LITTLE_ENDIAN and BIG_ENDIAN computers (check against BYTE_ENDIAN.)