【问题标题】:What's the best way to serialize a double to a string and back again?将双精度序列化为字符串并再次返回的最佳方法是什么?
【发布时间】:2014-12-09 02:29:06
【问题描述】:

我有一个 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]);
}

但是,由于我正在读取传感器数据,因此我担心浮点值会丢失精度。我直接控制序列化格式。

如何将 double 值从 C++ 写入 std::cout,以使从 Java 中读取它会产生完全相同的双精度值?

【问题讨论】:

  • 有没有办法以一种完整的方式获取传感器数据?否则以传感器制造商指定的精度(或准确度)对其进行序列化。
  • 数据来自一个自定义的内部数据库,有一些应用程序向这个数据库写入双精度数据,我需要将它们读回以发送给第三方监控 Java 程序。我所拥有的就是这个双重价值。到目前为止,我一直在使用幼稚的 std::cout &lt;&lt; x 方法,因为它很容易在屏幕上为我们阅读。
  • 鉴于它们来自序列化源,当您在存储中读取它们时,它们的精度是相同的,然后以相同的精度再次写出。
  • @Niall 我不知道这些值的写入精度,而且我认为这里没有人甚至可以回答这个问题......因为双精度数是 IEEE 754我更多地考虑序列化原始字节,从 Java 读取它们并再次构建原始位。
  • 您可以使用带有 jni 接口的 c++ 在 java 中读取您的双打,进行一些预处理并返回到 java 前端。如果您担心格式问题 - 失去精度,那么可能会以二进制格式写入 cout - 例如 uuencode 二进制双精度数据。但我会做一些繁重的测试,以找到失去精度的数据示例。

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


【解决方案1】:

这会有帮助吗?

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

请参阅以下示例:

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

【讨论】:

  • 感谢您的建议,以后会遵守这个规则的。
【解决方案2】:

我决定采用的解决方案是将reinterpret_castdouble 转换为int64_t 并将float 转换为int32_t,并且仅针对这两种数据类型使用部分模板特化。我仍然缺少字节序转换,但由于这里的所有内容都是小字节序,我想我会没事的:

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

我并不是真的在做 reinterpret_cast,因为我的编译器 (GCC) 给了我一个 warning about aliasing,这与我们在这里使用的编译器开关有关:

取消引用类型双关指针会破坏严格的别名规则

在 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

【讨论】:

  • 警告:虽然这个技巧通常会奏效,但在 C++ 中将联合作为一种类型存储并作为另一种类型读取是未定义的行为。这种技术在 C 中是正确的,但在 C++ 中它可能会咬你一口,很可能是由于编译器优化做出了不正确的假设。
【解决方案3】:

嗯,正如你所说,有两种选择,如果你关心可读性并且需要打印双打那么:

方法一: (可读)

#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,因为默认值总是会尝试舍入。无论哪种方式,使用此方法的完整正确打印都需要更强大的库。

方法二: (二进制)

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    • 1970-01-01
    • 2014-06-11
    • 2012-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多