我有一个问题,需要确定给定双精度变量 v
的最小值 eps
,使得:
v+eps != v
请注意,这不是典型的问题清单任务,因为
eps
取决于任意数字v
。不应该通过在for循环中寻找此值来完成。是否有快速的方法来完成这个任务,例如通过位移?独立于编译器、优化标志和平台...
感谢您的回答。
我有一个问题,需要确定给定双精度变量 v
的最小值 eps
,使得:
v+eps != v
eps
取决于任意数字v
。nextafter
是您需要的。或者使用Boost.Math的nextafter
。根据定义,这是实现定义的(它依赖于内存中double
的内部表示)。#include <cmath>
#include <cfloat>
#include <limits>
#include <iostream>
using std::cout;
#include <iomanip>
using std::setprecision;
#include <boost/math/special_functions/next.hpp>
double
epsFor( double x )
{
union
{
double d;
unsigned long long i;
} tmp;
tmp.d = x;
++ tmp.i;
return tmp.d - x;
}
void test(double d)
{
double d1 = std::nextafter(d,DBL_MAX);
double d2 = d+std::numeric_limits<double>::epsilon() * d;
double d3 = d+epsFor(d);
double d4 = boost::math::nextafter(d, DBL_MAX);
cout << setprecision(40)
<< "For value of d = " << d << '\n'
<< " std::nextafter: " << d1 << '\n'
<< " Boost solution: " << d4 << '\n'
<< " undefined beh.: " << d3 << '\n'
<< " numeric_limits: " << d2 << '\n';
}
int main()
{
test(0.1);
test(986546357654.354687);
}
exponent += 1
和 mantissa := 000000000
对吗?应该是 exponent += 1
和 mantissa := 0000001
,不是吗? - Manu343726espFor
函数给出了与 boost::nextafter
完全相同的结果。链接的“演示现场”似乎显示编译器中存在错误(或者表达式的误用——C++ 对中间结果的精度非常宽松)。 - James Kanzedouble
epsFor( double x )
{
union
{
double d;
unsigned long long i;
} tmp;
tmp.d = x;
++ tmp.i;
double results = tmp.d - x;
return results;
}
(从正式意义上讲,这是未定义行为,但在实践中,我不知道有现代编译器会失败。)
编辑:
请注意,C++允许中间表达式具有过度精度;由于我们关心的是精确结果,如果您直接在表达式中使用原始发布的函数而不是将其分配给double
,则该函数可能会给出错误的结果。 我已经添加了一个分配到函数中以避免这种情况,但请注意,很多编译器在这方面并不符合标准,至少默认情况下不符合标准。(g++是其中一个很好的例子,你需要特殊选项才能获得符合标准的行为,至少在打开优化时。如果您正在使用g++,则必须指定-ffloat-store
选项,如果要获得正确的结果。)
unint64_t
是否有用:它会导致在一些奇特的平台上(例如Unisys大型机)编译失败,这可能是一个优势。(或者不是。我不知道那些平台上的 long long
是什么,使用 uint64_t
可能会导致编译失败,尽管上述代码可以正常工作。) - James Kanzememcpy
来解决未定义的行为;标准的“意图”是reinterpret_cast
应该与类型游戏一起工作,但实际上,g++只保证使用union
(这是最常见的方法)可以实现它。由于上述内容显然仅适用于IEEE(和类似表示法,其中在尾数中使用隐式MSB),因此我只关心实际实践中发生的情况。 - James Kanze-fexcess-precions
仅适用于C,这意味着不适用于C ++。但在C++中使用它肯定也是有道理的。 - James Kanzeeps = std::numeric_limits<double>::epsilon() * v;
1.0
和下一个可表示的 double
之间的差异。对于更大的 double
,这个差距会更大。 - BoBTFishv * (1 + epsilon) - v
可以计算出ULP的一半,而不使用nextafter()或类型转换,但有一半的时间会计算出2ULPs。也许像v + (v * epsilon / 2) - v
这样的东西可以在所有情况下都起作用,但是按照现在的写法,我仍然担心它在某些情况下是错误的。 - Pascal Cuoq