如何以最小增量(或者接近最小增量)更改浮点数?
我有一个double
值f
,并且想将其微调到更大(或者更小)以获得一个新值,该值将尽可能接近原始值,但仍然严格大于(或者小于)原本的。
不必紧迫地抓住最后一个问题,更重要的是,我所做的任何更改都可以保证产生不同的值,而不会四舍五入到原始值。
解决方案
检查math.h文件。如果幸运的话,我们可以定义nextafter
和nextafterf
函数。它们以可移植且独立于平台的方式完全满足要求,并且是C99标准的一部分。
另一种方法(可能是一种后备解决方案)是将浮点分解为尾数和指数部分。增量很容易:只需在尾数上添加一个即可。如果出现溢出,则必须通过增加指数来处理。递减工作原理相同。
编辑:正如注释中指出的那样,仅增加其二进制表示形式中的浮点数就足够了。尾数溢出将增加指数,这正是我们想要的。
简而言之,就是接下来的事情。
但是,这将不是完全可移植的。我们将不得不面对局限性,以及并非所有机器都具有IEEE浮动的事实(好吧,最后一个原因是更具学术性)。
同样,处理NAN和无限可能会有些棘手。我们不能简单地增加它们的数量,因为它们的定义不是数字。
u64 &x = *(u64*)(&f); x++;
是的,认真的。
编辑:正如某人指出的那样,这不能正确处理-ve数字,Inf,Nan或者溢出。上面的一个更安全的版本是
u64 &x = *(u64*)(&f); if( ((x>>52) & 2047) != 2047 ) //if exponent is all 1's then f is a nan or inf. { x += f>0 ? 1 : -1; }
绝对而言,我们可以添加到浮点值以形成新的不同值的最小数量取决于该值的当前大小;它是该类型的机器epsilon乘以当前指数。
查看IEEE规范中的浮点表示法。最简单的方法是将值重新解释为整数类型,加1,然后(如果我们愿意)通过检查符号和指数位来检查是否没有翻转符号或者生成NaN。
或者,我们可以使用frexp获取当前的尾数和指数,从而计算要添加的值。
我在一段时间前发现了这段代码,也许它将确定可以将其向上推的最小值,然后将其递增该值。不幸的是,我不记得此代码的参考:
#include <stdio.h> int main() { /* two numbers to work with */ double number1, number2; // result of calculation double result; int counter; // loop counter and accuracy check number1 = 1.0; number2 = 1.0; counter = 0; while (number1 + number2 != number1) { ++counter; number2 = number2 / 10; } printf("%2d digits accuracy in calculations\n", counter); number2 = 1.0; counter = 0; while (1) { result = number1 + number2; if (result == number1) break; ++counter; number2 = number2 / 10.0; } printf("%2d digits accuracy in storage\n", counter ); return (0); }
我需要做完全相同的事情,并想出了这段代码:
double DoubleIncrement(double value) { int exponent; double mantissa = frexp(value, &exponent); if(mantissa == 0) return DBL_MIN; mantissa += DBL_EPSILON/2.0f; value = ldexp(mantissa, exponent); return value; }
就其价值而言,标准++递增停止起作用的值是9,007,199,254,740,992.
这可能不完全是我们想要的,但是我们仍然可以找到使用中的numeric_limits。特别是成员min()和epsilon()。
我不相信像mydouble + numeric_limits :: epsilon()这样的东西会做我们想要的,除非mydouble已经接近epsilon。如果是这样,那么我们很幸运。