Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++17 std::to_chars and std::from_chars portability

Tags:

c++

std

c++17

According to cppreference, "The guarantee that std::to_chars/std::from_chars can recover every floating-point value formatted by std::from_chars/std::to_chars exactly is only provided if both functions are from the same implementation."

What kind of trouble might this cause if one of them runs on different implementations? What values of floats, both decimal and hex, would cause trouble? Can they both at least be trusted to work with, well, say 'most' values, or is that simply a do-not-trust no-go?

[ Followup edit: ] I stuck with strtod and snprintf. To handle the required forcing to / reverting from "C" locale I used the per-thread uselocale et al. which does not touch global locale. It seems fast enough with 1000s of ops although no formal tests. I am wondering if the locale calls are a big speed hit? I also learned "0x" is forbidden in to/from_chars. I need that.

like image 873
MusicMaster Avatar asked Dec 06 '25 18:12

MusicMaster


1 Answers

This is a good question. Portability to subsequent compiler versions as well as interchange of data between systems requires consistency. So I coded this quick program to generate 100,000 random 32 bit values. Convert to floats, then run them through std::to_chars and std::from_chars. I generated a CRC16 value from all the chars generated. If these match across compilers or future compilers then one can expect that the data will most likely be portable. Also tested is that the to/from conversion produces the same floats though that's a requirement in the standard.

The following code produces a crc=931c in MSVC, CLang and GCC

#include <random>
#include <charconv>
#include <string>
#include <bit>
#include <iostream>

//#define CRC16
//#include "crc.h"

struct CRC16 {
    uint16_t state{ 0xffff };
    static constexpr uint16_t poly = 0x1021;
    uint16_t clk(char c)
    {
        state ^= c & 255;
        for (int i = 0; i < 8; i++)
        {
            if (state & 0x8000)
                state = (state << 1) ^ poly;
            else
                state = (state << 1);
        }
        return state;
    }
};


int main()
{
    CRC16 crc;
    constexpr uint32_t mask{ 0x7f800000 };
    std::mt19937 g(1);
    std::uniform_int_distribution<uint32_t> dist;

    // run 100,000 random bit float patterns and test for conversion consistency
    for (int i = 0; i < 100000; i++)
    {
        char result[100]{};
        uint32_t x = dist(g);
        // do not test NaN or De-normalized floats
        if (((x & mask) != mask) && (x & mask) != 0)
        {
            float f = std::bit_cast<float>(x);
            float fret;
            auto res1 = std::to_chars(&result[0], &result[100], f);
            for (char* p = &result[0]; p < res1.ptr; p++)
                crc.clk(*p);
            auto res2 = std::from_chars(&result[0], res1.ptr, fret);
            if (f != fret)
                throw "oops: mismatch";
        }
    }
    std::cout << std::hex << crc.state << '\n';
}

Note that this only tests floats. Minor modifications would also test doubles. Also, if it's critical, testing the complete set of floats is doable but will take a while to run. Doubles would not be feasible to test the entire state space.

like image 157
doug Avatar answered Dec 08 '25 06:12

doug



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!