Json в C++: анализировать число как строку, чтобы избежать неточностей с плавающей точкой

Я имею дело с криптовалютным RPC и получаю данные json следующим образом:

{
  ...
  "amount": 1.34000000,
  "confirmations": 230016,
  "spendable": true,
  "solvable": true
  ...
}

Используя библиотеку Jsoncpp или json11, можно получить число, проанализированное для double, Когда это происходит, результат: 1.3400000000000001, из-за двойных проблем точности. В целом это катастрофично для финансовых трансакций и неприемлемо.

У меня уже есть библиотека с фиксированной запятой, которая может принимать допустимую строку и внутренне рассматривать ее как целое число. Есть ли способ, которым я мог бы заставить Jsoncpp (или любую другую библиотеку json) принимать выбранные числовые значения json в виде строк, чтобы я мог правильно обращаться с ними с фиксированной точностью?

2 ответа

Решение

Похоже, в библиотеках json решения не существует, поэтому мне пришлось самому изменить число и заключить его в кавычки. Я применил эту функцию к ответам, чтобы сделать это.

[](std::string& jsonStr) {
        // matches "amount" field in json
        static std::regex reg(R"((\s*\"amount\"\s*:)\s*(\d*\.{0,1}\d{0,8})\s*)");
        jsonStr = std::regex_replace(jsonStr, reg, "$1\"$2\"");
    };

И теперь все работает правильно.

Мне нравится ThorsSerializer. Отказ от ответственности я написал это.

Он поддерживает то, что вы ищете.
Вы можете указать синтаксическому анализатору использовать стандартные операторы ввода / вывода для класса (который вы затем можете определить для себя).

Пример:

#include "ThorSerialize/JsonThor.h"
#include "ThorSerialize/SerUtil.h"
#include <sstream>
#include <iostream>
#include <string>
#include <map>

struct FixedPoint
{
    int     integerPart;
    int     floatPart;
    friend std::istream& operator>>(std::istream& stream, FixedPoint& data)
    {
        // This code assumes <number>.<number>
        // Change to suite your needs.
        char c;
        stream >> data.integerPart >> c >> data.floatPart;
        if (c != '.')
        {
            stream.setstate(std::ios::failbit);
        }

        return stream;
    }
};
// This declaration tells the serializer to use operator>> for reading
// and operator<< for writing this value.
// Note: The value must still conform to standard Json type
//       true/false/null/integer/real/quoted string
ThorsAnvil_MakeTraitCustom(FixedPoint);

struct BitCoin
{
    FixedPoint  amount;
    int         confirmations;
    bool        spendable;
    bool        solvable;
};
// This declaration tells the serializer to use the standard
// built in operators for a struct and serialize the listed members.
// There are built in operations for all built in types and std::Types
ThorsAnvil_MakeTrait(BitCoin, amount, confirmations, spendable, solvable);

Пример использования:

int main()
{
    using ThorsAnvil::Serialize::jsonImport;
    using ThorsAnvil::Serialize::jsonExport;

    std::stringstream file(R"(
        {
            "amount": 1.34000000,
            "confirmations": 230016,
            "spendable": true,
            "solvable": true
        }
    )");

    BitCoin     coin;
    file >> jsonImport(coin);

    std::cout << coin.amount.integerPart << " . " << coin.amount.floatPart << "\n";
}

Телосложение:

> g++ -std=c++1z 51087868.cpp -lThorSerialize17

Родное решение jsoncpp - это RTFM!!! (например, здесь: https://open-source-parsers.github.io/jsoncpp-docs/doxygen/class_json_1_1_stream_writer_builder.html)

Json::StreamWriterBuilder builder;
builder["commentStyle"] = "None";
builder["indentation"] = "   ";
builder["precision"] = 15;

Это установит для вашего писателя точность float, чтобы избежать печати небольших ошибок усечения в двойных представлениях. например, вместо поля json,

"сумма": 1.3400000000000001,

теперь вы получите

"сумма": 1,340000000000000,

по желанию.

Другие вопросы по тегам