4

So, I am aware that types from the stdint.h header provide standardized width integer types, however I am wondering what type or method does one uses to guarantee the size of a double or other floating point type across platforms? Specifically, this would deal with packing data in a void*

#include <stdio.h>
#include <stdlib.h>

void write_double(void* buf, double num)
{
  *(double*)buf = num;
}

double read_double(void* buf)
{
  return *(double*)buf;
}


int main(void) {
  void* buffer = malloc(sizeof(double));
  write_double(buffer, 55);
  printf("The double is %f\n", read_double(buffer));
  return 0;
}

Say like in the above program, if I wrote that void* to a file or if it was used on another system, would there be some standard way to guarantee size of a floating point type or double?

Josh Weinstein
  • 2,788
  • 2
  • 21
  • 38
  • Do you know of a real-life situation where using `sizeof(double)` in the appropriate way would not be feasible? – Jens Nov 08 '17 at 19:03
  • The code you have here doesn't actually care whether `double` and `void*` have the same size and should be extremely portable. Is there some specific case you're worried about where these sizes might matter? – templatetypedef Nov 08 '17 at 19:03
  • @Jens yes, as stated where writing binary data to a file and reading it on another system or platform. Such as for making a binary language like BSON – Josh Weinstein Nov 08 '17 at 19:04
  • 2
    `double read_double(void* buf) { return *(double*)buf; }` looks like a strict aliasing violation waiting to happen. – Andrew Henle Nov 08 '17 at 19:07
  • A better implementation of `write_double` would be `memcpy(buf, &num, sizeof num);`. No casts. – Bo Persson Nov 09 '17 at 00:12

4 Answers4

5

How to guarantee exact size of double in C?

Use _Static_assert()

#include <limits.h>

int main(void) {
  _Static_assert(sizeof (double)*CHAR_BIT == 64, "Unexpected double size");
  return 0;
}

_Static_assert available since C11. Otherwise code could use a run-time assert.

#include <assert.h>
#include <limits.h>

int main(void) {
  assert(sizeof (double)*CHAR_BIT == 64);
  return 0;
}

Although this will insure the size of a double is 64, it does not insure IEEE 754 double-precision binary floating-point format adherence.

Code could use __STDC_IEC_559__

An implementation that defines __STDC_IEC_559__ shall conform to the specifications in this annex` C11 Annex F IEC 60559 floating-point arithmetic

Yet that may be too strict. Many implementations adhere to most of that standard, yet still do no set the macro.


would there be some standard way to guarantee size of a floating point type or double?

The best guaranteed is to write the FP value as its hex representation or as an exponential with sufficient decimal digits. See Printf width specifier to maintain precision of floating-point value

Stargateur
  • 24,473
  • 8
  • 65
  • 91
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Josh, [@Christian Gibbons](https://stackoverflow.com/questions/47187604/how-to-guarantee-exact-size-of-double-in-c/47187696#comment81325057_47187755) has a beginning good idea. Store the value as "IEEE 754 double-precision binary" as a 64-bit integer_with_ network endian_. Oblige code to covert its local `double` to/from "IEEE 754 double-precision binary". With platforms that use "IEEE 754 double-precision binary", this is a simple _copy_. For others, it will be work. – chux - Reinstate Monica Nov 08 '17 at 20:41
3

The problem with floating point type is that the C standard doesn't specify how they should be represented. The use of IEEE 754 is not required.

If you're communicating between a system that uses IEEE 754 and one that doesn't, you won't be able to write on one and read on the other even if the sizes are the same.

You need to serialize the data in a known format. You can either use sprintf to convert it to a text format, or you can do some math to determine the base and mantissa and store those.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • Yeah. You could have a custom floating point format that is 64 bits but with non-standard sizes for the mantissa et al.. I would think that the way to go would be to store it in the IEEE 754 standard format and then any target system that wanted to use it would have to either support that format or have a function to convert to its native format. – Christian Gibbons Nov 08 '17 at 19:18
  • 1
    For me, it's `frexp` and `ldexp` to the rescue, usually. – Tommy Nov 08 '17 at 19:24
1

Floating point values are defined in the The IEEE Standard for Floating-Point Arithmetic (IEEE 754) and have standard sizes:

The following also exist:

This format is reused in the C11 standard, Annex F "IEC 60559 floating-point arithmetic" of ISO/IEC 9899:2011(en).

Cimbali
  • 11,012
  • 1
  • 39
  • 68
  • 3
    Does the C standard require the use of IEEE 754? – user3386109 Nov 08 '17 at 19:06
  • @user3386109 Very wise idea :-) I think you could have became a lawyer, too. :-) :-) :-) – peterh Nov 08 '17 at 19:12
  • 1
    Annex F states *"An implementation that defines `__STDC_IEC_559__` shall conform to the specifications in this annex.356)"*. And note 356 specifically says, *"Implementations that do not define `__STDC_IEC_559__` are not required to conform to these specifications."* So the standard does not require the use of IEEE 754. However, the OP *could* use an `#ifdef` on `__STDC_IEC_559__` to verify that IEEE 754 *is* being used. – user3386109 Nov 08 '17 at 19:18
  • @user3386109 fair point, I've got some trouble finding the full text right now. – Cimbali Nov 08 '17 at 19:27
0

Why use CHAR_BIT and assert at runtime? We can do this at compile time.

void write_double(void* buf, double num)
{
   char checkdoublesize[(sizeof(double) == 8)?1:-1];
   *(double*)buf = num;
}

Your code is still undefined as it doesn't gurantee IEEE or endianness but it will catch a bad double size. If your platform's new enough for htonq this will allow endianness to work

void write_double(void* buf, double num)
{
   char checkdoublesize[(sizeof(double) == 8)?1:-1];
   *(int64_t*)buf = htonq(*(volatile int64_t*)&num);
}


double read_double(void* buf)
{
   int64_t n = ntohq(*(int64_t*)buf);
   return *(volatile double*)&n;
}

Where volatile is merely the shortest way to tell the compiler the pointer cast really is defined. Usually it does the right thing anyway but after N levels of inlining maybe it won't anymore.

Joshua
  • 40,822
  • 8
  • 72
  • 132