我正在写一个算法,处理Logit-正态分布变换,它的特点是,有足够高的sigmas/means,值可以非常紧密地“压缩”到0和1的边缘。我遇到了应该不同的值碰撞的问题,因为C双精度不能代表足够的数字。例如,我将sigmoid/logistic函数应用于两个足够高但不同的值,如果双精度可以保持更高的精度,结果是相同的。
我可以通过聚合碰撞到相同值上的样本来解决这个问题。但问题是,在1左右,碰撞发生了,因为数字是这样表示的:例如:0。999999999953。但在0附近,它没有发生,因为C开始用科学记数法表示值,像这样:0.12949384e-300。后者可以实现更高的精度,因此这里不会发生碰撞。但我需要对0和1周围的值进行对称行为,以确保两边的计算结果相同。
C中是否有这样的类型在表示极小的值时不会自动切换到科学符号?所以我可以在0附近实现与1附近相同的行为?
您可以使用类型long double
。它通常具有x86 80位精度,这是8087协处理器的原始精度。
请注意,Microsoft很久以前就放弃了对它的支持。
GCC支持实现IEEE754四重精度的类型__float128
。这很好,因为如果您使用Linux它可能已经存在。
Boost多精度为上述提供了128位跨平台外观。但是,您通常会将boost库依赖项带入您的应用程序,这在某些情况下可能令人望而生畏。更新:boost多精度的float128无法在MSVC/Windows上编译。
示例:
#include <iostream>
#include <limits>
#include <cmath>
#include "boost/format.hpp"
#include <boost/multiprecision/float128.hpp>
namespace mp = boost::multiprecision;
template< typename T >
[[gnu::noinline]] T calc(T a,T b,T c) {
return T(1) - (T(1)/a + T(1)/b + T(1)/c );
}
int main() {
std::cout << " Epsilon Error"<< std::endl;
boost::format fmt( "%12.6g %12.6g" );
std::cout << fmt % std::numeric_limits<double>::epsilon() % calc<double>(3,2,6) << std::endl;
std::cout << fmt % std::numeric_limits<long double>::epsilon() % calc<long double>(3,2,6) << std::endl;
std::cout << fmt % double(std::numeric_limits<__float128>::epsilon()) % double(calc<__float128>(3,2,6)) << std::endl;
std::cout << fmt % std::numeric_limits<mp::float128>::epsilon() % calc<mp::float128>(3,2,6) << std::endl;
}
结果在
Program stdout
Epsilon Error
2.22045e-16 1.11022e-16
1.0842e-19 0
0 9.62965e-35
1.92593e-34 9.62965e-35
编译器资源管理器链接
但是请注意,128位精度会带来巨大的性能损失。我使用上述公式编写了一个基准测试,结果是:
-------------------------------------------------------------
Benchmark Time CPU Iterations
-------------------------------------------------------------
Calc<double> 375575 ns 375514 ns 1864
Calc<long double> 531313 ns 531228 ns 1318
Calc<__float128> 12943995 ns 12941473 ns 54
Calc<mp::float128> 13074108 ns 13071816 ns 54