What's the best way to serialize a double string and back again?

I have a Java program that starts a C ++ program from ProcessBuilder

and reads the output from a C ++ application as a string. Data is a collection of data types where I serialize everything as a string through std::cout

, for example:

void write_data(int x) {
    std::cout << "int:" << x << std::endl;
}

void write_data(double x) {
    std::cout << "double:" << x << std::endl;
}

      

On the Java side I am doing something like:

String line = readLineFromSubProcess();
String[] parts = line.split(":");

if (parts[0].equals("double")) {
  return Double.parseDouble(parts[1]);
}

      

However, since I am reading sensor data, I am concerned about the loss of precision in floating point values. I have direct control over the serialization format.

How do I write a value double

from C ++ to std::cout

such that reading it from Java produces exactly the same double value?

+3


source to share


3 answers


The solution I decided to go with is reinterpret_cast

double

on int64_t

and float

on int32_t

, and with partial custom templating for just these two data types. I'm still missing the endianess conversion, but since everything is small here, I think everything will be fine:

template <typename T>
void write_data(T x) {
    std::cout << get_type_name<T>() << ":" << x << std::endl;
}

template<>
void write_data<double>(double x) {
    union {
        double d;
        int64_t i;
    } n;
    n.d = x;
    std::cout << "double_as_int64:" << n.i << std::endl; 
}

template<>
void write_data<float>(double x) {
    union {
        float f;
        int32_t i;
    } n;
    n.f = x;
    std::cout << "float_as_int32:" << n.i << std::endl; 
}

      

I don't literally do reinterpret_cast

, since my compiler (GCC) is giving me an alias warning which is related to compiler switches, re using here:

a dereferenced type-guard pointer would violate strict antialiasing rules



On the Java side I am doing:

String line = readLineFromSubProcess();
String[] parts = line.split(":");

if (parts[0].equals("double")) {
  return Double.parseDouble(parts[1]);
} else if (parts[0].equals("double_as_int64") {
  return Double.longBitsToDouble(Long.parseLong(parts[1]));
} else if (parts[0].equals("float_as_int32") {
  return Float.intBitsToFloat(Integer.parseInt(parts[1]));
}

      

What is Google Protocol Buffers work under the hood .

+2


source


Well, as you said, there are two options, if you care about readability and need to print doubles then:

Method 1: (readable)

#include <limits>

void double_to_string(double the_double, int offset_to_max)
{
        typedef std::numeric_limits<double> doublelimit;

        // ready to transfer
        std::cout.precision(doublelimit::digits10 + offset_to_max);
        std::cout << the_double << std::endl;
}

// Back to double
double cstring_to_double(const char *s)
{
        return std::atof(s);
}

int main(int argc, char *argv[]) {

        double the_double = 0.10000000000000002;
        //double the_double = 0.1; 
        int offset = 2;

        double_to_string(the_double, offset);
}

      

So the printed double letters will be anti-aliased using full precision (17). I recommend using 17 as the default will always try to round. In any case, full correct printing using this method would require a more powerful library.



Method 2: (binary)

Just memcpy value using and int64 is ok, double can store maximum 53 bits:

template<typename T, typename H = int64_t>
H write_data(T x) 
{
    H i;
    // Can we hold the data?
    if (sizeof(T) > sizeof(H)) assert(false); // or write some error procesing
    // Now take the lower size of the two.
    memcpy(&i, &x, sizeof(H) > sizeof(T) ? sizeof(T) : sizeof(H));

    return i;
}

// union version
template<typename T, typename H = int64_t>
H write_data_u(T x)
{
    // Can we hold the data?
    if (sizeof(T) > sizeof(H)) assert(false); // or write some error procesing

    union {
        T d;
        H i;
    } n;

    n.d = x;

    return n.i;
}


int main(int argc, char *argv[]) 
{
    double the_double = 0.10000000000000001;
    int64_t double_holder = write_data<double, int64_t>(the_double);

    // Binary data as decimal string: ready to transfer the string, remember to convert to big_endian first if you transfer the binary representation.
    std::cout << "double_as_int64:" << double_holder << std::endl;

    // Back to double (we know int64_t holds a double so we dont care about the remaining bits)
    double back_to_double = write_data<int64_t, double>(double_holder);
    std::cout.precision(std::numeric_limits<double>::digits10 + 2);
    std::cout << "back_to_double:" << back_to_double << std::endl;
}

      

+1


source


Did it help?

std::cout << std::setprecision(5) << f << '\n';

      

Please see examples:

http://www.cplusplus.com/reference/iomanip/setprecision/

-1


source







All Articles