提问人:Jan Schultke 提问时间:8/23/2023 最后编辑:Jan Schultke 更新时间:8/23/2023 访问量:97
如何正确地将浮点数舍入到 epsilon 溢出
How to round a float to epsilon overflow-correctly
问:
为了在不允许重复的数据结构(如二叉树、堆等)中对浮点数进行排序,我想四舍五入到一定的精度级别,如下所示:float
float round_to_epsilon(x);
bool less_than(float x, float y) {
return round_to_epsilon(x) < round_to_epsilon(y);
}
换句话说,我想将 s 量化为 的倍数,即 2-23。float
std::numeric_limits<float>::epsilon
第一个想法:乘法和地板
乍一看,您似乎可以这样做:
float round_to_epsilon(float x) {
// note: floor or ceil must be used to avoid bias around zero
return std::floor(x / std::numeric_limits<float>::epsilon());
}
但是,这会将每个大于 2105 的数字转换为正无穷大,这不是溢出正确的。
第二个思路:除法以放弃精度
另一种方法是乘以一个非常小的数字,故意造成下溢,并丢弃一些数字。但是,这使得非规范化数字的可能性很大,因此这种方法可能存在严重的性能问题。
问题
我怎样才能四舍五入到epsilon;或者换句话说,我怎样才能将数字降低到 2-23 的量级以下,而不会造成不必要的溢出或下溢?
我的直觉是,也许我可以除以 2 的幂,然后再次乘以得到避免非规范化数字,但我不确定如何做到这一点,或者这是否是一个好方法。
答:
换句话说,我想将 s 量化为
float
std::numeric_limits<float>::epsilon
我将按照所提出的问题来回答这个问题,而不讨论这是否是所述用例的最合适方法。你的第一个想法实际上是可行的,只要有人照顾好输入域的末端。
量级足够大的数字自然会被量化为 的整数倍。小于 1 的数字将四舍五入为零。对于在量化过程中四舍五入到最接近或偶数,可以使用 ,前提是当前的四舍五入模式设置为四舍五入到最接近或偶数(这是大多数环境中的默认值)。此警告是必要的,因为与 、、 和 相反,它不使用固定的舍入模式,而是使用当前有效的舍入模式。eps
eps
rint()
floor()
ceil()
trunc()
round()
rint()
float round_to_epsilon (float x)
{
float eps, cutoff, r;
eps = std::numeric_limits<float>::epsilon();
cutoff = 0.5f * eps;
if (fabsf (x) < cutoff) {
r = copysignf (0.0f, x);
} else if (fabsf (x) >= 1.0f) {
r = x;
} else {
r = eps * rintf (x / eps);
}
return r;
}
评论
return x - std::remainder(x, std::numeric_limits<float>::epsilon();
...将浮点数量化为 的倍数,即 2-23。
std::numeric_limits<float>::epsilon
虽然我不认为这是一个好主意,因为 OP 的目标似乎是解决 xy 问题,但可以通过以下方式完成:
float round_to_epsilon_muliple(float f) {
// Rounding only needed for small values as larger ones
// are already a multiple of epsilon.
if (fabs(f) < 1.0f) {
static const float one_over_epsilon =
1.0f/std::numeric_limits<float>::epsilon();
f = round(f * one_over_epsilon); // or floor(), trunc(), etc.
f /= one_over_epsilon;
}
return f;
}
@Eric Postpischil 提供了一种简化的方法:
float round_to_epsilon_muliple_EP(float f) {
if (isfinite(f)) {
return f - std::remainder(f, std::numeric_limits<float>::epsilon());
}
return f;
}
对数分布的舍入到 epsilon 的线性倍数似乎很奇怪,因为它将浮点数从浮点中取出。float
也许 OP 想降低 ?float
然后考虑提取分数/幂并操纵它们。FP frexp(FP f, int *p)
float reduce_precision(float f) {
if (isfinite(f)) {
int p;
f = float(f, &p);
// Let us say we want to lop off 4 of the least significant bits.
static const unsigned long lop = 1LU << 4;
static const float one_over_epsilon =
1.0f/std::numeric_limits<float>::epsilon();
static const float one_over_scale = one_over_epsilon / lop;
f = trunc(f * one_over_scale); // or round(), ...
f /= one_over_scale;
f = ldexp(f, p);
}
return f;
}
评论
return x - std::remainder(x, std::numeric_limits<float>::epsilon();
x
评论
abs(x) >= 1.0f
ldexp
modf
std::fmod