4

I have a binary file, and I want to read a double from it.

In hex representation, I have these 8 bytes in a file (and then some more after that):

40 28 25 c8 9b 77 27 c9 40 28 98 8a 8b 80 2b d5 40 ...

This should correspond to a double value of around 10 (based on what that entry means).

I have used

#include<stdio.h>
#include<assert.h>

int main(int argc, char ** argv) {
   FILE * f = fopen(argv[1], "rb");
   assert(f != NULL);
   double a;
   fread(&a, sizeof(a), 1, f);
   printf("value: %f\n", a);
}

However, that prints value: -261668255698743527401808385063734961309220864.000000

So clearly, the bytes are not converted into a double correctly. What is going on? Using ftell, I could confirm that 8 bytes are being read.

the busybee
  • 10,755
  • 3
  • 13
  • 30
j13r
  • 2,576
  • 2
  • 21
  • 28
  • 3
    Where is the code that **wrote** these "bytes" , and was it from the same platform? – WhozCraig Sep 19 '14 at 07:59
  • It looks like the original system wrote data out as big endian and you're trying to read it back as little endian - you just need to reverse the order of the 8 bytes. – Paul R Sep 19 '14 at 08:05
  • 1
    Btw, `12.0738`for the first, `12.2979` for the second, etc if you reverse the bytes, so... reverse the bytes if reading on an LE platform. I sincerely hope if you do that *all* platforms *write* the data in BE. – WhozCraig Sep 19 '14 at 08:07
  • Great! I was able to use fgetc with the union trick from https://stackoverflow.com/questions/4949144/how-to-byteswap-a-double – j13r Sep 19 '14 at 08:15

2 Answers2

4

Just like integer types, floating point types are subject to platform endianness. When I run this program on a little-endian machine:

#include <stdio.h>
#include <stdint.h>

uint64_t byteswap64(uint64_t input) 
{
    uint64_t output = (uint64_t) input;
    output = (output & 0x00000000FFFFFFFF) << 32 | (output & 0xFFFFFFFF00000000) >> 32;
    output = (output & 0x0000FFFF0000FFFF) << 16 | (output & 0xFFFF0000FFFF0000) >> 16;
    output = (output & 0x00FF00FF00FF00FF) << 8  | (output & 0xFF00FF00FF00FF00) >> 8;
    return output;
}

int main() 
{
    uint64_t bytes = 0x402825c89b7727c9;
    double a = *(double*)&bytes;
    printf("%f\n", a);

    bytes = byteswap64(bytes);
    a = *(double*)&bytes;
    printf("%f\n", a);

    return 0;
}

Then the output is

12.073796
-261668255698743530000000000000000000000000000.000000

This shows that your data is stored in the file in little endian format, but your platform is big endian. So, you need to perform a byte swap after reading the value. The code above shows how to do that.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thank you for the detailed answer. In the end I used fgetc with the union trick from stackoverflow.com/questions/4949144/how-to-byteswap-a-double – j13r Sep 19 '14 at 12:33
  • 8 years later I end up here. One sidenote: this loses precision: 36.24139221666073 -> 36.241392 I'm still looking for the "fix" to this. –  May 02 '22 at 13:58
  • @soze no, it doesn't lose precision, you have misdiagnosed – David Heffernan May 02 '22 at 17:26
2

Endianness is convention. Reader and writer should agree on what endianness to use and stick to it.

You should read your number as int64, convert endianness and then cast to double.

Community
  • 1
  • 1
GHugo
  • 2,584
  • 13
  • 14
  • 2
    Casting won't work, when you want to re-interpret the same bits. Casting will convert the (very large) integer to a very large `double`. – unwind Sep 19 '14 at 08:17
  • Pointer-casting or union-casting would work; the latter being safer in case of size mismatch, since at least you know it won't overflow on nearby variables. – Medinoc Sep 19 '14 at 08:57