为什么这不会产生双下溢?
Why doesn't this produce a double underflow?
谁能解释为什么此代码段不会产生下溢异常(在 MSVC 2013 和 gcc @coliru 上)?平均函数返回的值低于 DBL_MIN
.
#include <float.h>
#include <iostream>
#include <iomanip>
#include <limits>
const size_t g_testValueCount = 10;
const double g_testValues[g_testValueCount] = { DBL_MIN, 0 };
double unsafeAverage(const double* testValues, size_t testValueCount)
{
double result = 0;
for (size_t testValueIndex = 0; testValueIndex < testValueCount; ++testValueIndex)
{
result += testValues[testValueIndex];
}
return result / testValueCount;
}
int main(int argc, char** argv)
{
std::cout << "DBL_MIN = " << std::setprecision(std::numeric_limits<double>::digits10) << DBL_MIN << std::endl;
try
{
std::cout << " AVG = " << std::setprecision(std::numeric_limits<double>::digits10) << unsafeAverage(g_testValues, g_testValueCount) << std::endl;
}
catch (...)
{
std::cout << "unsafeAverage caught an exception!" << std::endl;
}
return 0;
}
您没有捕捉到下溢异常的两个主要原因:
浮点数异常不是 C++ 异常,因此 ¹ 通常您不能用 catch(...)
.
捕获它们
MSVC 的默认浮点下溢行为(大概是 Coliru 上的 g++)是产生非正规值,即零。非正规是低于普通最小值的值,具有较少的有效位。随着有效位数变为零,您将得到一个实际的零。
对于 C++11 及更高版本,您可以通过 C99 fetestexcept
函数检查浮点错误。
这是为使用此类检查而重写的代码:
#include <float.h>
#include <iostream>
#include <iomanip>
#include <limits>
#include <limits.h>
using namespace std;
#include <fenv.h>
const size_t g_testValueCount = 10;
const double g_testValues[g_testValueCount] = { DBL_MIN, 0 };
auto unsafeAverage( const double* const testValues, int const testValueCount )
-> double
{
double result = 0;
for( int i = 0; i < testValueCount; ++i )
{
result += testValues[i];
}
return result / testValueCount;
}
auto main() -> int
{
cout << setprecision( numeric_limits<double>::digits10 );
cout << "DBL_MIN = " << DBL_MIN << endl;
try
{
feclearexcept( FE_ALL_EXCEPT );
auto const result = unsafeAverage(g_testValues, g_testValueCount);
if( fetestexcept(FE_ALL_EXCEPT ) )
{
throw std::runtime_error( "Oopsie daisy!" );
}
cout << " AVG = " << result << endl;
}
catch( ... )
{
cerr << "!unsafeAverage caught an exception" << endl;
return EXIT_FAILURE;
}
}
备注:
¹ 尽管 Visual C++ 在 1990 年代将其作为语言扩展
假设您使用的是 C++11 或更新版本,您可以使用 fetestexcept
函数测试浮点异常。通过将 FE_UNDERFLOW
常量传递给函数来测试下溢异常。
谁能解释为什么此代码段不会产生下溢异常(在 MSVC 2013 和 gcc @coliru 上)?平均函数返回的值低于 DBL_MIN
.
#include <float.h>
#include <iostream>
#include <iomanip>
#include <limits>
const size_t g_testValueCount = 10;
const double g_testValues[g_testValueCount] = { DBL_MIN, 0 };
double unsafeAverage(const double* testValues, size_t testValueCount)
{
double result = 0;
for (size_t testValueIndex = 0; testValueIndex < testValueCount; ++testValueIndex)
{
result += testValues[testValueIndex];
}
return result / testValueCount;
}
int main(int argc, char** argv)
{
std::cout << "DBL_MIN = " << std::setprecision(std::numeric_limits<double>::digits10) << DBL_MIN << std::endl;
try
{
std::cout << " AVG = " << std::setprecision(std::numeric_limits<double>::digits10) << unsafeAverage(g_testValues, g_testValueCount) << std::endl;
}
catch (...)
{
std::cout << "unsafeAverage caught an exception!" << std::endl;
}
return 0;
}
您没有捕捉到下溢异常的两个主要原因:
浮点数异常不是 C++ 异常,因此 ¹ 通常您不能用
catch(...)
. 捕获它们
MSVC 的默认浮点下溢行为(大概是 Coliru 上的 g++)是产生非正规值,即零。非正规是低于普通最小值的值,具有较少的有效位。随着有效位数变为零,您将得到一个实际的零。
对于 C++11 及更高版本,您可以通过 C99 fetestexcept
函数检查浮点错误。
这是为使用此类检查而重写的代码:
#include <float.h>
#include <iostream>
#include <iomanip>
#include <limits>
#include <limits.h>
using namespace std;
#include <fenv.h>
const size_t g_testValueCount = 10;
const double g_testValues[g_testValueCount] = { DBL_MIN, 0 };
auto unsafeAverage( const double* const testValues, int const testValueCount )
-> double
{
double result = 0;
for( int i = 0; i < testValueCount; ++i )
{
result += testValues[i];
}
return result / testValueCount;
}
auto main() -> int
{
cout << setprecision( numeric_limits<double>::digits10 );
cout << "DBL_MIN = " << DBL_MIN << endl;
try
{
feclearexcept( FE_ALL_EXCEPT );
auto const result = unsafeAverage(g_testValues, g_testValueCount);
if( fetestexcept(FE_ALL_EXCEPT ) )
{
throw std::runtime_error( "Oopsie daisy!" );
}
cout << " AVG = " << result << endl;
}
catch( ... )
{
cerr << "!unsafeAverage caught an exception" << endl;
return EXIT_FAILURE;
}
}
备注:
¹ 尽管 Visual C++ 在 1990 年代将其作为语言扩展
假设您使用的是 C++11 或更新版本,您可以使用 fetestexcept
函数测试浮点异常。通过将 FE_UNDERFLOW
常量传递给函数来测试下溢异常。