查找浮点计数器的最大值



如果之前有人问过这个问题,我很抱歉,但我找不到。

我想知道是否有一种方法可以计算用作计数器的单精度浮点数将达到"最大值"的点(由于精度损失而无法再添加另一个值的点)。

例如,如果我连续地将0.1f添加到float,我最终会达到值不变的点:

const float INCREMENT = 0.1f;
float value = INCREMENT;
float prevVal = 0.0f;
do {
prevVal = value;
value += INCREMENT;
} while (value != prevVal);
cout << value << endl;

在GCC上,输出2.09715e+06

对于INCREMENT的不同值,有没有一种数学计算方法?我认为理论上应该是float的指数部分需要移位超过23位,导致尾数丢失并简单地加0。

给定一些正的y作为增量,加上y不会产生大于X的结果的最小X是不小于y的2的最小幂除以浮点格式的"epsilon"的一半。可以通过以下公式计算:

Float Y = y*2/std::numeric_limits<Float>::epsilon();
int e;
std::frexp(Y, &e);
Float X = std::ldexp(.5, e);
if (X < Y) X *= 2;

一个证明随之而来。我假设IEEE-754二进制浮点运算使用四舍五入到偶数的关系。

当在IEEE-754浮点运算中添加两个数字时,结果是精确的数学结果,四舍五入到选定方向上最接近的可表示值。

注释:source code format中的文本表示浮点值和运算。其他文本是数学文本。因此,x+yxy的精确数学和,x是浮点格式的x-x+y是浮点运算中xy相加的结果。此外,我将使用Float作为C++中的浮点类型。

给定浮点数x,请考虑使用浮点运算x+y添加正值y。在什么条件下结果会超过x

x1是大于x的下一个值,以浮点格式表示,并设xmx-x<1>之间的中点。如果x+y的数学值小于xm,则浮点计算x+y向下取整,因此它产生x。如果x+y大于xm,则它要么四舍五入并产生x1;要么它产生一些更大的数字,因为y足够大,可以将总和移动到x<1>之外。如果x+y等于xm,则结果为xx1中的哪一个具有偶数位。由于我们将看到的原因,在与此问题相关的情况下,这总是x,因此计算四舍五入。

因此,x+y产生大于x的结果,当且仅当x+y超过x-m,这意味着y超出了从x-x<1>的距离的一半。请注意,从xx1的距离是x有效位低位的值1。

在有效位中有p位的二进制浮点格式中,低位数字的位置值是高位数字位置值的2倍。例如,如果x是2e,则其有效位中的最高位表示2e,而最低位表示2e+1−p

问题是,给定yx+y不会产生大于x的结果的最小x是什么?它是y不超过x有效位低位值一半的最小x

设2ex有效位高位的位置值。然后y≤½•2e+1−p=2−p,因此y•2p≤2e。

因此,给定一些正的yx+y没有产生大于x的结果的最小x具有其前导位2e,等于或超过y•2p。事实上,它必须正好是2e,因为所有其他浮点数的前导位都有位置值2e,它们的有效位中都设置了其他位,所以它们更大。2e是前导位表示2e的最小数。

因此,x是等于或超过y•2p的二次方的最小幂。

在C++中,std::numeric_limits<Float>::epsilon()(来自<limits>标头)是从1到下一个可表示值的步骤,这意味着它是21−p。因此y•2p等于y*2/std::numeric_limits<Float>::epsilon()。(此操作是精确的,除非它溢出到∞。)

让我们将其分配给一个变量:

Float Y = y*2/std::numeric_limits<Float>::epsilon();

我们可以通过使用frexp(来自<cmath>标头)从Yldexp(也称为<cmath>)的浮点表示中提取指数,将该指数应用于新的有效位(由于frexpldexp使用的小数位数,因此.5)来找到由Y的有效位的最高位表示的位置值:

int e;
std::frexp(Y, &e);
Float X = std::ldexp(.5, e);

X是二的幂,并且它小于或等于Y。事实上,它是两个不大于Y的最大幂,因为2的下一个更大幂,2X,大于Y。然而,我们希望二的最小幂不小于Y。我们可以通过以下方式找到:

if (X < Y) X *= 2;

由此产生的X是问题所寻求的数字。

Marek的Answer非常接近,是使用程序找到它的一种不错的方法(比我最初发布的程序更高效)。然而,我不一定需要程序形式的答案,只需要数学形式的答案。

根据我的判断,答案可以归结为所使用的delta的指数和尾数位数。我们需要四舍五入到2的最近幂,这有点复杂。基本上,如果尾数是0,我们什么都不做,否则我们在指数上加1。因此,假设我们现在的delta是2的幂,表示为1.0 x 2exp,尾数为N位,则最大值为1.0 x 2(N + exp)。注意,C中的FLT_EPSILON等于1.0 x 2-N。因此,我们也可以通过将2的最近幂除以FLT_EPSILON来找到这一点。

对于0.1的增量,2的最接近幂为0.125,即1.0 x 2-3。因此,我们想要1.0 x 2(23 + (-3))1.0 x 221,其等于2097152

是的,这是可能的。存在std::numeric_limits:epsilon(),它定义了可以增加值CCD_ 44的最小值。

使用它,您可以计算任何数字的此限制。

C中有DBL_EPSILON

所以在你的情况下是这样的:

template<class T>
auto maximumWhenAdding(T delta) -> T
{
static_assert(std::is_floating_point_v<T>, "Works only for floating points.");
int power2= std::ilogb(delta);
float roudedDelta = ldexp(T { 1.0 }, power2);
if (roudedDelta != delta) {
roudedDelta *= 2;
}
return 2 * roudedDelta / std::numeric_limits<T>::epsilon();
}

C++实例

请注意,在实际测试示例中,delta无法增加maxForDelta,但减法是成功的,所以这正是您所需要的。

最新更新