Boost’s lexical_cast Точность от двойной до строковой

Я работаю с библиотекой, которая, к сожалению, использует boost::lexical_cast конвертировать из double к string,

Мне нужно быть в состоянии точно отразить это поведение на моей стороне, но я прыгал, чтобы сделать это без распространения boost,

Могу ли я гарантировать идентичное поведение, используя to_string, sprintfили какая-то другая функция, содержащаяся в стандарте?

2

Решение

Код повышения заканчивается здесь:

            bool shl_real_type(double val, char* begin) {
using namespace std;
finish = start +
#if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION)
sprintf_s(begin, CharacterBufferSize,
#else
sprintf(begin,
#endif
"%.*g", static_cast<int>(boost::detail::lcast_get_precision<double>()), val);
return finish > start;
}

Вам повезло, так как точность обычно является постоянной времени компиляции (если не настроить буст BOOST_LCAST_NO_COMPILE_TIME_PRECISION).

Немного упрощая и допуская соответствующие современные стандартные библиотеки:

Mimicking Boost Lexicalcast

#include <cstdio>
#include <limits>
#include <string>

namespace {
template <class T> struct lcast_precision {
typedef std::numeric_limits<T> limits;

static constexpr bool use_default_precision  = !limits::is_specialized || limits::is_exact;
static constexpr bool is_specialized_bin     = !use_default_precision && limits::radix == 2 && limits::digits > 0;

static constexpr bool is_specialized_dec     = !use_default_precision && limits::radix == 10 && limits::digits10 > 0;
static constexpr unsigned int precision_dec  = limits::digits10 + 1U;
static constexpr unsigned long precision_bin = 2UL + limits::digits * 30103UL / 100000UL;

static constexpr unsigned value = is_specialized_bin
? precision_bin
: is_specialized_dec? precision_dec : 6;
};

std::string mimicked(double v) {
constexpr int prec = static_cast<int>(lcast_precision<double>::value);

std::string buf(prec+10, ' ');
buf.resize(sprintf(&buf[0], "%.*g", prec, v));
return buf;
}
}

Регрессионные тесты

Чтобы сравнить результаты и проверить предположения:

Жить на Колиру

#include <cstdio>
#include <limits>
#include <string>

namespace {
template <class T> struct lcast_precision {
typedef std::numeric_limits<T> limits;

static constexpr bool use_default_precision  = !limits::is_specialized || limits::is_exact;
static constexpr bool is_specialized_bin     = !use_default_precision && limits::radix == 2 && limits::digits > 0;

static constexpr bool is_specialized_dec     = !use_default_precision && limits::radix == 10 && limits::digits10 > 0;
static constexpr unsigned int precision_dec  = limits::digits10 + 1U;
static constexpr unsigned long precision_bin = 2UL + limits::digits * 30103UL / 100000UL;

static constexpr unsigned value = is_specialized_bin
? precision_bin
: is_specialized_dec? precision_dec : 6;
};

std::string mimicked(double v) {
constexpr int prec = static_cast<int>(lcast_precision<double>::value);

std::string buf(prec+10, ' ');
buf.resize(sprintf(&buf[0], "%.*g", prec, v));
return buf;
}
}

#include <cmath>
#include <iomanip>
#include <iostream>
#include <string>

#include <boost/lexical_cast.hpp>

#ifdef BOOST_LCAST_NO_COMPILE_TIME_PRECISION
#error BOOM
#endif

#define TEST(x)                                                                                                        \
do {                                                                                                               \
std::cout << std::setw(45) << #x << ":\t" << (x) << "\n";                                                      \
} while (0)

std::string use_sprintf(double v) {
std::string buf(32, ' ');
buf.resize(std::sprintf(&buf[0], "%f", v));
return buf;
}

void tests() {
for (double v : {
std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
0.0,
-0.0,
std::numeric_limits<double>::epsilon(),
M_PI })
{
TEST(v);
TEST(std::to_string(v));
TEST(use_sprintf(v));
TEST(boost::lexical_cast<std::string>(v));
TEST(mimicked(v));

assert(mimicked(v) == boost::lexical_cast<std::string>(v));
}
}

static std::locale DE("de_DE.utf8");

int main() {

tests();

std::cout << "==== imbue std::cout\n";
std::cout.imbue(DE);

tests();

std::cout << "==== override global locale\n";
std::locale::global(DE);

tests();
}

Печать

                                        v:  nan
std::to_string(v):  nan
use_sprintf(v):  nan
boost::lexical_cast<std::string>(v):  nan
mimicked(v):  nan
v:  inf
std::to_string(v):  inf
use_sprintf(v):  inf
boost::lexical_cast<std::string>(v):  inf
mimicked(v):  inf
v:  -inf
std::to_string(v):  -inf
use_sprintf(v):  -inf
boost::lexical_cast<std::string>(v):  -inf
mimicked(v):  -inf
v:  0
std::to_string(v):  0.000000
use_sprintf(v):  0.000000
boost::lexical_cast<std::string>(v):  0
mimicked(v):  0
v:  -0
std::to_string(v):  -0.000000
use_sprintf(v):  -0.000000
boost::lexical_cast<std::string>(v):  -0
mimicked(v):  -0
v:  2.22045e-16
std::to_string(v):  0.000000
use_sprintf(v):  0.000000
boost::lexical_cast<std::string>(v):  2.2204460492503131e-16
mimicked(v):  2.2204460492503131e-16
v:  3.14159
std::to_string(v):  3.141593
use_sprintf(v):  3.141593
boost::lexical_cast<std::string>(v):  3.1415926535897931
mimicked(v):  3.1415926535897931
==== imbue std::cout
v:  nan
std::to_string(v):  nan
use_sprintf(v):  nan
boost::lexical_cast<std::string>(v):  nan
mimicked(v):  nan
v:  inf
std::to_string(v):  inf
use_sprintf(v):  inf
boost::lexical_cast<std::string>(v):  inf
mimicked(v):  inf
v:  -inf
std::to_string(v):  -inf
use_sprintf(v):  -inf
boost::lexical_cast<std::string>(v):  -inf
mimicked(v):  -inf
v:  0
std::to_string(v):  0.000000
use_sprintf(v):  0.000000
boost::lexical_cast<std::string>(v):  0
mimicked(v):  0
v:  -0
std::to_string(v):  -0.000000
use_sprintf(v):  -0.000000
boost::lexical_cast<std::string>(v):  -0
mimicked(v):  -0
v:  2,22045e-16
std::to_string(v):  0.000000
use_sprintf(v):  0.000000
boost::lexical_cast<std::string>(v):  2.2204460492503131e-16
mimicked(v):  2.2204460492503131e-16
v:  3,14159
std::to_string(v):  3.141593
use_sprintf(v):  3.141593
boost::lexical_cast<std::string>(v):  3.1415926535897931
mimicked(v):  3.1415926535897931
==== override global locale
v:  nan
std::to_string(v):  nan
use_sprintf(v):  nan
boost::lexical_cast<std::string>(v):  nan
mimicked(v):  nan
v:  inf
std::to_string(v):  inf
use_sprintf(v):  inf
boost::lexical_cast<std::string>(v):  inf
mimicked(v):  inf
v:  -inf
std::to_string(v):  -inf
use_sprintf(v):  -inf
boost::lexical_cast<std::string>(v):  -inf
mimicked(v):  -inf
v:  0
std::to_string(v):  0,000000
use_sprintf(v):  0,000000
boost::lexical_cast<std::string>(v):  0
mimicked(v):  0
v:  -0
std::to_string(v):  -0,000000
use_sprintf(v):  -0,000000
boost::lexical_cast<std::string>(v):  -0
mimicked(v):  -0
v:  2,22045e-16
std::to_string(v):  0,000000
use_sprintf(v):  0,000000
boost::lexical_cast<std::string>(v):  2,2204460492503131e-16
mimicked(v):  2,2204460492503131e-16
v:  3,14159
std::to_string(v):  3,141593
use_sprintf(v):  3,141593
boost::lexical_cast<std::string>(v):  3,1415926535897931
mimicked(v):  3,1415926535897931

Обратите внимание, что mimicked а также boost::lexical_cast<std::string>(double) результат каждый раз в точности один и тот же.

3

Другие решения

Итак, после нескольких часов копания в шаблонах Boost вот что я узнал:

  1. Фактический вызов, который выполняет строковое преобразование: lexical_cast_do_cast<std::string, double>::lexical_cast_impl
  2. Это использует std::sprintf в boost::detail::lexical_stream_limited_src<char, std::char_traits<char>, false>
  3. boost::detail::lexical_stream_limited_src<char, std::char_traits<char>, true>::operator<< будет использоваться для вставки double, проходя beginуказатель на выделенный std::stringбуфер, и valвход doubleДает этот вызов: std::sprintf(begin, "%.*g", static_cast<int>(boost::detail::lcast_get_precision<double>()), val)
  4. Таким образом, поле точности здесь происходит от boost::details::lcast_precision<double>::value, который, будет использовать std::numeric_limits<double>; если это is_specialized является false, is_exact является false, radix является 2, а также digits больше, чем 0 затем boost::details::lcast_precision<double>::value будет оценивать: 2UL + std::numeric_limits<double>::digits * 30103UL / 100000UL

Таким образом, где begin это выделенный string а также val двойной вход,
boost::lexical_cast<double> дает окончательный результат, эквивалентный:

std::sprintf(begin, "%.*g", 2UL + std::numeric_limits<double>::digits * 30103UL / 100000UL, val)

Это, очевидно, сильно зависит от реализации. Но в моей системе это даст точный эквивалент.

4

По вопросам рекламы [email protected]