什么是将double序列化为字符串并再次返回的最佳方法?

时间:2014-10-14 10:20:43

标签: java c++ c++11 serialization

我有一个Java程序,用ProcessBuilder启动C ++程序,并以字符串形式读取C ++应用程序的输出。数据是数据类型的混合,我通过std::cout将所有内容序列化为字符串,如下所示:

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

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

在Java方面,我做了类似的事情:

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

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

然而,由于我正在读取传感器数据,因此我担心浮点值的精度损失。我可以直接控制序列化格式。

如何从C ++写入double值到std::cout的方式,从Java读取它会产生完全相同的double值?

3 个答案:

答案 0 :(得分:2)

我决定采用的解决方案是reinterpret_cast doubleint64_tfloatint32_t,以及仅对这两种数据类型使用部分模板专门化。我仍然错过了endianess转换,但由于这里的所有内容都是小端,我想我会没事的:

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; 
}

由于我的编译器(GCC)给了我一个warning about aliasing,我不会真正做reinterpret_cast,这与我们在这里使用的编译器开关有关:

  

取消引用类型惩罚指针将破坏严格别名规则

在Java方面,我正在做:

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]));
}

这是Google Protocol Buffers are doing under the hood

答案 1 :(得分:1)

好吧,正如你所说,如果你关心可读性并需要打印双打,有两个选择:

方法1: (可读)

#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);
}

这样,打印的双打将使用全精度(17)进行混叠。我建议使用17,因为默认将始终尝试舍入。无论哪种方式,使用此方法的完整正确打印将需要更强大的库。

方法2: (二进制)

只需memcpy值,使用和int64就可以了,double可以存储最多53位:

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;
}

答案 2 :(得分:-1)

这会有帮助吗?

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

请参阅以下示例:

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