Last active
December 28, 2024 22:34
-
-
Save ahamez/fa4760800d454a080cd83e5963ca5bf2 to your computer and use it in GitHub Desktop.
Portable serialization of floats using frexp and ldexpr. C++17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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