округление до двойного с N значащими десятичными цифрами в безопасном режиме - PullRequest
2 голосов
/ 03 марта 2020

Мне нужна функция защиты от переполнения , которая округляет double, как std::round, кроме того, она может обрабатывать количество значащих десятичных разрядов.

fe

round(-17.747, 2) -> -17.75
round(-9.97729, 2) -> -9.98
round(-5.62448, 2) -> -5.62
round(std::numeric_limits<double>::max(), 10) ...

Моя первая попытка была

double round(double value, int precision)
{
    double factor=pow(10.0, precision);
    return floor(value*factor+0.5)/factor;
}

, но это может легко переполнить.

Предполагая IEEE, можно уменьшить вероятность переполнения, как это.

double round(double value, int precision)
{
    // assuming IEEE 754 with 64 bit representation
    // the number of significant digits varies between 15 and 17

    precision=std::min(17, precision);
    double factor=pow(10.0, precision);
    return floor(value*factor+0.5)/factor;
}

Но это все еще может переполниться.

Даже эта катастрофа производительности не работает.

double round(double value, int precision)
{
    std::stringstream ss;
    ss << std::setprecision(precision) << value;
    std::string::size_type sz;
    return std::stod(ss.str(), &sz);
}
round(std::numeric_limits<double>::max(), 2.0) // throws std::out_of_range

Примечание :

  • Я знаю о setprecision, но мне нужно округлить не только для отображения цели. Так что это не решение.
  • В отличие от этого поста Как округлить число до n десятичных знаков в Java, мой вопрос особенно о безопасности переполнения и в C ++ (ответчик в топиках c выше Java -specifi c или не обрабатываются переполнения)

1 Ответ

2 голосов
/ 03 марта 2020

Я не очень тщательно тестировал этот код:

/* expects x in (-1, 1) */
double round_precision2(double x, int precision2) {
    double iptr, factor = std::exp2(precision2);
    double y = (x < 0) ? -x : x;
    std::modf(y * factor + .5, &iptr);
    return iptr/factor * ((x < 0) ? -1 : 1);
}

double round_precision(double x, int precision) {
    int bits = precision * M_LN10 / M_LN2;
            /* std::log2(std::pow(10., precision)); */
    double iptr, frac = std::modf(x, &iptr);
    return iptr + round_precision2(frac, bits);
}

Идея состоит в том, чтобы избежать переполнения, работая только с дробной частью числа.

Мы вычисляем число двоичных биты для достижения желаемой точности. Вы должны быть в состоянии наложить на них ограничения, которые вы описываете в своем вопросе. Далее мы извлекаем дробные и целые части числа. Затем мы добавляем целую часть обратно к округленной дробной части.

Чтобы вычислить округленную дробную часть, мы вычисляем двоичный коэффициент. Затем мы извлекаем целую часть округленного числа, полученную умножением дробной части на коэффициент. Затем мы возвращаем дробь путем деления интегральной части на коэффициент.

...