我正在为数学函数编写单元测试,我希望能够“遍历”所有可能的浮点数/双精度数。
由于IEEE的恶作剧,浮动类型无法在其末端增加(++
)。有关更多详细信息,请参见this question。该答案指出:
一个只能加2 ^(n-N)的倍数
但永远不会提及n
的不足。
伟大的blog post提供了一种将所有可能值从+0.0迭代到+ infinity的解决方案。该技术涉及使用带有int
的并集来遍历float
的不同值。尽管这些属性仅对正数有效,但由于帖子中解释了以下属性,因此可以使用此功能。
- 相邻浮点数具有相邻的整数表示形式
- 递增浮点数的整数表示将移动到下一个可表示的浮点数,从零开始移走
他对于+0.0到+无限(从0.f
到std::numeric_limits<float>::max()
)的解决方案:
union Float_t {
int32_t RawExponent() const { return (i >> 23) & 0xFF; }
int32_t i;
float f;
};
Float_t allFloats;
allFloats.f = 0.0f;
while (allFloats.RawExponent() < 255) {
allFloats.i += 1;
}
是否存在-infinity到+0.0(std::numeric_limits<float>::lowest()
到0.f
)的解决方案?
我已经测试过std::nextafter
and std::nexttoward
,但无法使其正常工作。也许这是MSVC问题?
我会接受任何形式的破解,因为这是一个单元测试。谢谢!
答案 0 :(得分:4)
您可以使用32位unsigned int
的所有值来遍历所有32位的表示形式。然后,您将真正遍历正负的所有所有表示形式,包括空值(有两个)和所有 not number 表示形式(NaN)。您可能会或可能不想过滤掉NaN表示形式,或者只过滤掉发信号的那些而留出非发信号的那些。这取决于您的用例。
示例:
for (uint32_t i = 0;;)
{
float f;
// Type punning: Force the bit representation of i into f.
// Type punning is hard because mostly undefined in C/C++.
// Using memcpy() usually avoids any type punning warning.
memcpy(&f, &i, sizeof(f));
// Use f here.
// Warning: Using signaling NaNs may throw exceptions or raise signals.
i++;
if (i == 0)
break;
}
相反,您也可以将32位int
从-2 ** 31移至+(2 ** 31-1)。这没什么区别。
答案 1 :(得分:2)
Pascal Cuoq正确指出std::nextafter
是正确的解决方案。我在代码的其他地方遇到了问题。对不起不必要的问题。
#include <cassert>
#include <cmath>
#include <limits>
float i = std::numeric_limits<float>::lowest();
float hi = std::numeric_limits<float>::max();
float new_i = std::nextafterf(i, hi);
assert(i != new_i);
double d = std::numeric_limits<double>::lowest();
double hi_d = std::numeric_limits<double>::max();
double new_d = std::nextafter(d, hi_d);
assert(d != new_d);
long double ld = std::numeric_limits<long double>::lowest();
long double hi_ld = std::numeric_limits<long double>::max();
long double new_ld = std::nextafterl(ld, hi_ld);
assert(ld != new_ld);
for (float d = std::numeric_limits<float>::lowest();
d < std::numeric_limits<float>::max();
d = std::nextafterf(
d, std::numeric_limits<float>::max())) {
// Wait a lifetime?
}
答案 2 :(得分:1)
可以通过简单地理解浮点表示来遍历所有float
值:
观察到下面的代码中的内部循环很简单:
for (; x < Limit; x += Increment)
Test(x);
这具有仅使用常规浮点算术的优点。内部循环仅包含一个加法和一个比较(以及您希望对每个数字执行的所有测试)。循环中不会调用任何库函数,也不会剖析任何表示形式或将其复制到通用寄存器中,也不会进行其他操作。没有任何阻碍性能的事情。
此代码仅浏览非负数。负数可以用相同的方法分别测试,也可以通过插入呼叫Test(-x)
来共享此代码。
#include <limits>
static void Test(float x)
{
// Insert unit test for value x here.
}
int main(void)
{
typedef float T;
static const int Radix = std::numeric_limits<T>::radix;
static const T Infinity = std::numeric_limits<T>::infinity();
/* Increment is the current distance between floating-point numbers. We
start it at distance between subnormal numbers.
*/
T Increment =
std::numeric_limits<T>::min() * std::numeric_limits<T>::epsilon();
/* Limit is the next boundary where the distance between floating-point
numbers changes. We will increment up to that limit and then adjust
the limit and increment. We start it at the top of the first set of
normals, which allows the first loop to increment first through the
subnormals and then through the normals with the lowest exponent.
(These two sets have the same step size between adjacent values.)
*/
T Limit = std::numeric_limits<T>::min() * Radix;
/* Start with zero and continue until we reach infinity.
We execute an inner loop that iterates through all the significands of
one floating-point exponent. Each time it completes, we step up the
limit and increment.
*/
for (T x = 0; x < Infinity; Limit *= Radix, Increment *= Radix)
// Increment x through all the significands with the current exponent.
for (; x < Limit; x += Increment)
// Test with the current value of x.
Test(x);
// Also test infinity.
Test(Infinity);
}
(此代码假定浮点类型具有次标准态,并且它们不会刷新为零。也可以很容易地对其进行调整以支持这些替代方式。)