Encode integer value to 4 bytes in a file


I'm trying to encode data into a file in binary mode.

It should be 4 bytes with an integer value.

Say the value is 5, when opened in a hex editor the file should read:

0500 0000

= 5 (0005)

for 7592..

a81d 0000

= 7592 (1da8)
Last edited on
1
2
3
4
5
6
7
8
#include <fstream>

int main() {
	std::ofstream ofs("numtest.dat");
	const int a { 7592 };

	ofs.write((char*)&a, sizeof(a));
}


However whether this saves the data as 0xa81d0000 or 0x00001da8 depends upon byte ordering and whether binary data is stored as little-endian or big-endian.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bit>
#include <fstream>
#include <iostream>

std::ostream & write_word( std::ostream & outs, long long value, int nbytes, std::endian endianness )
{
  if (endianness == std::endian::little)
  {
    while (nbytes --> 0)
    {
      outs.put( value );
      value >>= 8;
    }
  }
  else
  {
    while (nbytes --> 0)
    {
      outs.put( value >> (nbytes * 8) );
    }
  }
  return outs;
}

Untested, but I think that should work.
I don’t think I have introduced any sign-bit problems...

Examples:

1
2
  std::ofstream f( "data.dat", std::ios::binary ); 
  write_word( f, 0x1234, 4, std::endian::big );

The file should contain the bytes 0x01, 0x02, 0x03, 0x04.

1
2
  std::ofstream f( "data.txt", std::ios::binary ); 
  write_word( f, 0x1234, 4, std::endian::little );

The file should contain the bytes 0x04, 0x03, 0x02, 0x01.

For your example values:
1
2
  write_word( f, 5,    4, std::endian::big );
  write_word( f, 7592, 4, std::endian::big );


Hope this helps.

Let me know if I goofed up. This is a rare (these days) off-the-top-of-my-head answer that I haven’t taken the time to actually test (yet).
Thanks guys for the solutions.

@seeplus
I'll take note of this, it worked ok.

@Duthomhas
I'm getting a no file or directory in #include <bit>
Last edited on
What is your compiler, ruzip?

The <bit> header was introduced in C++20.
https://en.cppreference.com/w/cpp/header/bit

Your compiler is likely not 100% C++20 compliant, the only one currently that is fully C++20 compliant is Visual Studio (2019 & 2022).

Weirder and weirder, GCC and Clang are supposed to have std::endian available, it's the first item listed in the C++20 library features.
https://en.cppreference.com/w/cpp/compiler_support/20

Maybe they have it in the <numeric> header instead?

It is just a simple enum with VS. enum class endian { little = 0, big = 1, native = little };

[ETA:] Try changing the include in the code Duthomas gave from <bit> to <type_traits>.
https://stackoverflow.com/a/38141476
Last edited on
The cppreference page for std::endian (https://en.cppreference.com/w/cpp/types/endian)has a code snippet that you could try, ruzip, so you know if your OS is little or big endian.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fstream>
#include <iostream>

enum class endian { little, big, native = (*(char*)&1U)^1 };

std::ostream & write_word( std::ostream & outs, long long value, int nbytes, endian endianness )
{
  if (endianness == endian::little)
  {
    while (nbytes --> 0)
    {
      outs.put( value );
      value >>= 8;
    }
  }
  else
  {
    while (nbytes --> 0)
    {
      outs.put( value >> (nbytes * 8) );
    }
  }
  return outs;
}

I think I got the endianness trick right...

[edit]
The endian::native is really only useful to tell you what endianness the machine your compiler ran on is...
Last edited on
It's not usually necessary to test your machine's byte order, because it's usually possible to write code in a way that lets the compiler take care of the differences.

First decide the byte order of your data stream, ahead of time. Then, use the functions insert_XX_YY from this forum thread:
https://cplusplus.com/forum/lounge/279954/2/#msg1211823

Here is one of the functions from that post:
1
2
3
4
5
6
7
inline constexpr void insert_u32_le(u8* p, u32 n) noexcept
{
  p[0] = (n & 0x000000ff) >> 0;
  p[1] = (n & 0x0000ff00) >> 8;
  p[2] = (n & 0x00ff0000) >> 16;
  p[3] = (n & 0xff000000) >> 24;
}

It always puts the four bytes of n into the buffer pointed to by p in little-endian byte order. There's also extract_u32_le that takes n back out again.

The interface of these functions are supposed to work best in cases where formatting and writing (or reading and parsing) are separate operations. But if you don't care about that, you can just modify the code:
1
2
3
4
5
6
7
8
std::ostream& insert_u32_le(std::ostream& s, u32 n)
{
  s.put((n & 0x000000ff) >> 0);
  s.put((n & 0x0000ff00) >> 8);
  s.put((n & 0x00ff0000) >> 16);
  s.put((n & 0xff000000) >> 24);
  return s;
}
Last edited on
Topic archived. No new replies allowed.