以溢出安全的方式用 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
注:
- 我知道
setprecision
,但我需要四舍五入不仅是为了显示目的。所以这不是解决方案。
- 与此处 post 不同 How to round a number to n decimal places in Java ,我的问题尤其是关于溢出安全和 C++(上面主题中的答案是 Java 特定的或不处理溢出)
我还没有对这段代码进行大量测试:
/* 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 通过将整数部分除以因数来得到分数。
我想要一个 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
注:
- 我知道
setprecision
,但我需要四舍五入不仅是为了显示目的。所以这不是解决方案。 - 与此处 post 不同 How to round a number to n decimal places in Java ,我的问题尤其是关于溢出安全和 C++(上面主题中的答案是 Java 特定的或不处理溢出)
我还没有对这段代码进行大量测试:
/* 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 通过将整数部分除以因数来得到分数。