6
votes

Is the IEEE 754 floating point format well defined across platforms? In terms of both bit format and endianness?

I am willing to add the following to my code (for an initial version):

static_assert(std::numeric_limits<float>::is_iec559, "Only support IEC 559 (IEEE 754) float");
static_assert(sizeof(float) * CHAR_BIT == 32, "Only support float => Single Precision IEC 559 (IEEE 754)");

static_assert(std::numeric_limits<double>::is_iec559, "Only support IEC 559 (IEEE 754) double");
static_assert(sizeof(float) * CHAR_BIT == 64, "Only support double => Double Precision IEC 559 (IEEE 754)");

static_assert(std::numeric_limits<long double>::is_iec559, "Only support IEC 559 (IEEE 754) long double");
static_assert(sizeof(float) * CHAR_BIT == 128, "Only support long double  => Exteneded Precision IEC 559 (IEEE 754)");
//  More asserts if required.
//  I noticed my current system has a sizeof(long double) => 128
//  But numeric_limits<long double>::digits  => 63
//  So we are not storing quad precision floats only extended.

If I write my float/double/long double in binary format can these be transported between systems without further interpretation. ie...

void write(std::ostream& stream, double value)
{
     stream.write(reinterpret_cast<char const*>(&value), 8);
}

....

double read(std::istream& stream)
{
     double   value;
     stream.read(reinterpret_cast<char*>(&value), 8);
     return value;
}

Or do I need to break the double up into integer components for transport (as suggested by this answer):

The difference here is I am willing to limit my supported representation to IEEE-754 will this basically solve my binary storage of floating point values or do I need to take further steps?

Note: For non conforming platforms (when I find them) I am willing to special case the code so that they read/write IEEE-754 into local representation. But I want to know if the bit/endian is well enough defined cross platform to support storage/transport.

3
Endianness is not specified. So you'll want to convert everything to either big- or little-endian. - tmyklebu
@tmyklebu: Do you have a reference I can read that says that? Everybody else I talk to says it is (though they have not shown me the standard either). - Martin York
I'm confused. You want a reference that says that another reference doesn't say something? - tmyklebu
@tmyklebu: Sorry that came out wrong (I also would err on the side of "unless explicitly specified we can not assume", so currently I am of the mind I must take it into account). But I want to understand why you say that (as I keep hearing that endianness is not a factor) which is why I am asking the question. I want to get some explicit references to how I can expect to see this in memory. As you see currently the only answer is implying it is not an issue (and has no down votes, though 1 up vote not a ringing endorsement either). - Martin York
Well, I'm saying this because printing out the bytes of the double 42.0 on a SPARC gives you 40 45 00 00 00 00 00 00 while doing it on an x86_64 machine gives you 00 00 00 00 00 00 45 40. These are different because SPARC is big-endian and x86_64 is little-endian. - tmyklebu

3 Answers

4
votes

Bit format is well-defined, but not all machines are little-endian. The IEEE standard does not require floating-point numbers to be a certain endian, either. You can run the following program to see the byte pattern of the double 42.0:

#include <stdio.h>
#include <numeric>
#include <limits>
using namespace std;

int main() {
  double d = 42;
  printf("%i\n", std::numeric_limits<double>::is_iec559);
  for (char *c = (char *)&d; c != (char *)(&d+1); c++) {
    printf("%02hhx ", *c);
  }
  printf("\n");
}

On an old, unmaintained Sun machine using g++ 3.4.5, this prints

1
40 45 00 00 00 00 00 00

On an x86_64 machine running a much more recent g++:

1
00 00 00 00 00 00 45 40
1
votes

First of all, you may want to change your code such that it properly checks for the type sizes...

static_assert(std::numeric_limits<float>::is_iec559, "Only support IEC 559 (IEEE 754) float");
static_assert(sizeof(float) * CHAR_BIT == 32, "Only support float => Single Precision IEC 559 (IEEE 754)");

static_assert(std::numeric_limits<double>::is_iec559, "Only support IEC 559 (IEEE 754) double");
static_assert(sizeof(double) * CHAR_BIT == 64, "Only support double => Double Precision IEC 559 (IEEE 754)");

static_assert(std::numeric_limits<long double>::is_iec559, "Only support IEC 559 (IEEE 754) long double");
static_assert(sizeof(long double) * CHAR_BIT == 128, "Only support long double  => Exteneded Precision IEC 559 (IEEE 754)");

The thing is, that IEEE-754 does not require long double to be 128 bit long. Depending on the compiler and platform, the length of such type may vary. It does however specify binary128, which may be or may be not supported by the compiler, depending on the platform and the implementation (gcc has a non-standard __float128 type for that). The standard only requires long double to be at least as precise, as double, making it usually 80 bit long (gcc) or 64 (VS).

If you limit your supported representation to IEEE-754, you should not run into any problems.

1
votes

To read and write IEEE 754 portably, use these routines. If the platform is not IEEE 754, you might lose a couple of bits, but you will still get the closest possible representation.

https://github.com/MalcolmMcLean/ieee754