Skip to content

Instantly share code, notes, and snippets.

@ahamez
Last active December 28, 2024 22:34
Show Gist options
  • Save ahamez/fa4760800d454a080cd83e5963ca5bf2 to your computer and use it in GitHub Desktop.
Save ahamez/fa4760800d454a080cd83e5963ca5bf2 to your computer and use it in GitHub Desktop.
Portable serialization of floats using frexp and ldexpr. C++17
#include <climits>
#include <cmath>
#include <fstream>
#include <iostream>
#include <limits>
#include <utility>
#include <vector>
// IEEE 754 does not specify endianess (https://en.wikipedia.org/wiki/Endianness#Floating_point).
// So, to have a portable representation, we can "decompose" the floating-point value using frexp().
// This function returns an integer, that we can easily serialize in a portable way and a double
// which is in the range (-1;-0.5], [0.5; 1). Thus, we can multiply by the maximal value of int64 and
// thus retrieve a int64 that we also easily serialize.
// We just have to perform the reverse operation (using ldexp()) when deserializing.
static constexpr auto max_long = std::numeric_limits<std::int64_t>::max();
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
template<class T>
constexpr T bswap(T i)
{
return i;
}
#else
// https://stackoverflow.com/a/36937049/21584
template<class T, std::size_t... N>
constexpr T bswap_impl(T i, std::index_sequence<N...>)
{
return (((i >> N*CHAR_BIT & std::uint8_t(-1)) << (sizeof(T)-1-N)*CHAR_BIT) | ...);
}
template<class T, class U = std::make_unsigned_t<T>>
constexpr U bswap(T i)
{
return bswap_impl<U>(i, std::make_index_sequence<sizeof(T)>{});
}
#endif
int main()
{
// Serialize
{
const auto vec = std::vector<std::pair<double, double>>{
{1.12, 2.23},
{1930.1232, 2.01},
{3.14, 1.009},
{-3.14, -1.009},
{0.000000000001, 0.}
};
auto of = std::ofstream{"./archive", std::ios::binary};
const auto sz = bswap(static_cast<std::uint32_t>(vec.size()));
of.write(reinterpret_cast<const char*>(&sz), sizeof(sz));
for (const auto& entry : vec)
{
// First member
{
auto exp = int{};
// https://en.cppreference.com/w/cpp/numeric/math/frexp
//
auto mant = static_cast<std::int64_t>(max_long * std::frexp(entry.first, &exp));
exp = bswap(exp);
mant = bswap(mant);
of.write(reinterpret_cast<const char*>(&exp), sizeof(exp));
of.write(reinterpret_cast<const char*>(&mant), sizeof(mant));
}
// Second member
{
auto exp = 0;
auto mant = static_cast<std::int64_t>(max_long * std::frexp(entry.second, &exp));
exp = bswap(exp);
mant = bswap(mant);
of.write(reinterpret_cast<const char*>(&exp), sizeof(exp));
of.write(reinterpret_cast<const char*>(&mant), sizeof(mant));
}
}
}
// Deserialize
{
auto vec = std::vector<std::pair<double, double>>{};
auto in = std::ifstream{"./archive", std::ios::binary};
auto sz = std::uint32_t{};
in.read(reinterpret_cast<char*>(&sz), sizeof(std::uint32_t));
sz = bswap(sz);
while (sz != 0)
{
const auto member1 = [&]
{
auto exp = int{};
auto mant = std::int64_t{};
in.read(reinterpret_cast<char*>(&exp), sizeof(exp));
in.read(reinterpret_cast<char*>(&mant), sizeof(mant));
exp = bswap(exp);
mant = bswap(mant);
return std::ldexp(static_cast<double>(mant) / max_long, exp);
}();
const auto member2 = [&]
{
auto exp = int{};
auto mant = std::int64_t{};
in.read(reinterpret_cast<char*>(&exp), sizeof(exp));
in.read(reinterpret_cast<char*>(&mant), sizeof(mant));
exp = bswap(exp);
mant = bswap(mant);
return std::ldexp(static_cast<double>(mant) / max_long, exp);
}();
vec.emplace_back(member1, member2);
sz -= 1;
}
std::cout << "Vector size " << vec.size() << '\n';
for (const auto& entry : vec)
{
std::cout << "<" << entry.first << ", " << entry.second << ">\n";
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment