See Oliver Charlworth's description of why this almost works. I review the issue raised in the comments.
Since several people have pointed out this intolerance, here are a few ways to make it more portable, or at least get the compiler to tell you if this will work.
Firstly, C ++ allows you to check std::numeric_limits<float>::is_iec559 at compile time, for example in static_assert . You can also check that sizeof(int) == sizeof(float) , which will not be true if int is 64-bit, 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 overflows, and will cause a compilation error if your strange architecture does not have such an integral type. In any case, you also need static_assert() so that the types are the same size. Static statements are not time-consuming, and you should always check your premises in this way, if possible.
Unfortunately, checking whether the bits convert to float to uint32_t and the offset is big-endian, little-endian, or in no way can be calculated as an expression of a compile-time constant. Here I put a runtime check on the part of the code that depends on it, but you can put it in the initialization and do it once. In practice, both gcc and clang can optimize this test at compile time.
You do not want to use an unsafe pointer, and there are some systems that I worked on in the real world where this can lead to a program crash with a bus error. The most portable way to convert object representations is memcpy() . In my example below, I am writing a pun with union , which works with any actually existing implementation. (Lawyers object to this, but no successful compiler will ever break this multi-valued code silently.) If you need to do pointer conversion (see below), there is alignas() . But no matter how you do it, the result will be determined by the implementation, so we check the result of the conversion and change the test value.
In any case, not that you will most likely use it on a modern processor, there is a Russified version of C ++ 14 that checks these intolerable 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
The following is an alternative definition of reinterpret<T,U>() , which avoids confusion. You can also implement a quibble in modern C, where it is 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 that you are gaining a lot because you can still have undefined behavior from a hypothetical representation of the trap. In addition, clang ++ 3.9.1 -O -S is able to statically analyze a version of the Punning type, optimize the is_little_endian variable to the constant 0x1 and exclude the run-time test, but it can optimize this version only for a single instruction.
But more importantly, this code is not guaranteed to work on every compiler. For example, some older computers cannot even address exactly 32 bits of memory. But in these cases, he should not compile and tell you why. No compiler is just about to destroy a huge amount of legacy code. Although the standard technically gives permission for this and still says that it complies with C ++ 14, this will only happen in an architecture that is very different from what is expected. And if our assumptions are so invalid that some compiler is going to turn a keyword between a float and an 32-bit unsigned integer into a dangerous error, I really doubt that the logic of this code will delay if we just use memcpy() . We want this code not to work 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 C ++ Fundamentals , recommend instead of reinterpret_cast :
#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 to a folded constant. An example of Straustrap is [sic]:
Access to the reinterpret_cast result for another type from the declared object type is still undefined, but at least we can see that something complicated is happening.