以溢出安全的方式用 N 个有效的十进制数字舍入双精度数

round double with N significant decimal digits in an overflow-safe manner

我想要一个 overflow-safe 函数,它可以像 std::round 那样舍入 double,此外它还可以处理有效的小数位数。

f.e.

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

:

我还没有对这段代码进行大量测试:

/* 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);
}

这个想法是通过只对数字的小数部分进行操作来避免溢出。

我们计算二进制位数以达到所需的精度。您应该能够根据您在问题中描述的限制来限制它们。 接下来,我们提取数字的小数部分和整数部分。 然后我们将整数部分加回四舍五入的小数部分。

为了计算四舍五入的小数部分,我们计算二进制因子。然后我们提取小数部分乘以该因数得到的四舍五入数的整数部分。然后我们 return 通过将整数部分除以因数来得到分数。