在 C++ 中从 1 中减去极小的数
Subtract extremely small number from one in C++
我需要从 1 中减去极小的 double 数 x 即计算 1-x 在 C++ 中为 0。由于足够小的机器精度限制 f x 我总是会得到 1-x=1。简单的解决方案是从双精度转换为更精确的格式,如 long。但是由于某些限制,我无法切换到更精确的数字格式。
获得 1-x 准确值的最有效方法是什么,其中 x 是一个非常小的 double 如果我可以的话'不使用更精确的格式并且我需要将减法的结果存储为双精度数?在实践中,我想避免百分比误差大于 1%(在 1-x 的双重表示与其实际值之间)。
P.S. 我正在使用 Rcpp 通过 qnorm 计算标准正态分布的分位数 函数。此函数围绕 0.5 对称,对于接近 0 的值更加准确。因此,而不是 qnorm(1-(1e-30)) 我想计算 -qnorm(1e-30) 但推导 1e-30 from 1-(1e-30) 我需要处理一个精度问题。对 double 的限制是因为据我所知,在 Rcpp 中使用更精确的数字格式是不安全的。请注意,我对 qnorm 的输入可能是外生的,因此我无法从 x[= 推导出 1-x 39=] 在一些初步计算中。
Simple solution is to switch from double to some more precise format like long [presumably, double]
那你就没有办法了。 long double
是所有现代机器上 double
的别名。 我纠正了,gcc
和 icc
仍然支持它,只有 cl
有很长一段时间都放弃了对它的支持。
所以你有两个解决方案,它们并不相互排斥:
使用任意精度库而不是 built-in 类型。它们慢了几个数量级,但如果那是您的算法可以使用的最佳效果,那就是这样。
使用更好的算法,或者至少重新排列你的方程变量,从一开始就没有这个需要。使用分配和取消规则来完全避免该问题。如果没有对您的问题进行更深入的描述,我们无法为您提供帮助,但我可以肯定地告诉您,double
足以让我们在世界任何地方模拟飞机 AI 和飞行参数。
而不是求助于任意精确解决方案(正如其他人所说,可能非常慢),你可以只需创建一个 class,将 double
类型的固有精度扩展(大约)两倍。然后你只需要实现你实际需要的操作:在你的情况下,这可能只是减法(也可能是加法),这两者 合理地 很容易实现。这样的代码仍然比使用本机类型慢得多,但可能比使用不必要精度的库快得多。
这样的实现在 QD_Real class 中可用(如 open-source),由 Yozo Hida(我相信当时是博士生)创建。
链接的存储库包含 很多 代码,其中大部分可能对您的 use-case 来说是不必要的。下面,我展示了一个非常 trimmed-down 的版本,它允许创建具有所需精度的数据,显示了所需 operator-()
的实现和一个测试用例。
#include <iostream>
class ddreal {
private:
static inline double Plus2(double a, double b, double& err) {
double s = a + b;
double bb = s - a;
err = (a - (s - bb)) + (b - bb);
return s;
}
static inline void Plus3(double& a, double& b, double& c) {
double t3, t2, t1 = Plus2(a, b, t2);
a = Plus2(c, t1, t3);
b = Plus2(t2, t3, c);
}
public:
double x[2];
ddreal() { x[0] = x[1] = 0.0; }
ddreal(double hi) { x[0] = hi; x[1] = 0.0; }
ddreal(double hi, double lo) { x[0] = Plus2(hi, lo, x[1]); }
ddreal& operator -= (ddreal const& b) {
double t1, t2, s2;
x[0] = Plus2(x[0], -b.x[0], s2);
t1 = Plus2(x[1], -b.x[1], t2);
x[1] = Plus2(s2, t1, t1);
t1 += t2;
Plus3(x[0], x[1], t1);
return *this;
}
inline double toDouble() const { return x[0] + x[1]; }
};
inline ddreal operator-(ddreal const& a, ddreal const& b)
{
ddreal retval = a;
return retval -= b;
}
int main()
{
double sdone{ 1.0 };
double sdwee{ 1.0e-42 };
double sdval = sdone - sdwee;
double sdans = sdone - sdval;
std::cout << sdans << "\n"; // Gives zero, as expected
ddreal ddone{ 1.0 };
ddreal ddwee{ 1.0e-42 };
ddreal ddval = ddone - ddwee; // Can actually hold 1 - 1.0e42 ...
ddreal ddans = ddone - ddval;
std::cout << ddans.toDouble() << "\n"; // Gives 1.0e-42
ddreal ddalt{ 1.0, -1.0e-42 }; // Alternative initialization ...
ddreal ddsec = ddone - ddalt;
std::cout << ddsec.toDouble() << "\n"; // Gives 1.0e-42
return 0;
}
请注意,我故意忽略了 error-checking 和更通用的实现所需的其他开销。此外,我展示的代码已经 'tweaked' 在 x86/x64 CPU 上更优化地工作,因此如果您需要其他支持,您可能需要深入研究链接 GitHub 处的代码平台。 (但是,我认为我展示的代码适用于任何严格符合IEEE-754标准的平台。)
我已经在我用来生成和显示 Mandelbrot 集(和相关分形)的代码中广泛地测试了这个实现 非常 深度缩放级别,其中使用原始double
类型完全失败。
请注意,尽管您可能想 'optimize' 一些 看似 无意义的操作,但这样做会破坏系统。此外,此 必须 使用 /fp:precise
(或 /fp:strict
)标志(使用 MSVC)或其他编译器的等效标志进行编译;使用 /fp:fast
将完全破坏代码。
我需要从 1 中减去极小的 double 数 x 即计算 1-x 在 C++ 中为 0
获得 1-x 准确值的最有效方法是什么,其中 x 是一个非常小的 double 如果我可以的话'不使用更精确的格式并且我需要将减法的结果存储为双精度数?在实践中,我想避免百分比误差大于 1%(在 1-x 的双重表示与其实际值之间)。
P.S. 我正在使用 Rcpp 通过 qnorm 计算标准正态分布的分位数 函数。此函数围绕 0.5 对称,对于接近 0 的值更加准确。因此,而不是 qnorm(1-(1e-30)) 我想计算 -qnorm(1e-30) 但推导 1e-30 from 1-(1e-30) 我需要处理一个精度问题。对 double 的限制是因为据我所知,在 Rcpp 中使用更精确的数字格式是不安全的。请注意,我对 qnorm 的输入可能是外生的,因此我无法从 x[= 推导出 1-x 39=] 在一些初步计算中。
Simple solution is to switch from double to some more precise format like long [presumably, double]
那你就没有办法了。 我纠正了,long double
是所有现代机器上 double
的别名。gcc
和 icc
仍然支持它,只有 cl
有很长一段时间都放弃了对它的支持。
所以你有两个解决方案,它们并不相互排斥:
使用任意精度库而不是 built-in 类型。它们慢了几个数量级,但如果那是您的算法可以使用的最佳效果,那就是这样。
使用更好的算法,或者至少重新排列你的方程变量,从一开始就没有这个需要。使用分配和取消规则来完全避免该问题。如果没有对您的问题进行更深入的描述,我们无法为您提供帮助,但我可以肯定地告诉您,
double
足以让我们在世界任何地方模拟飞机 AI 和飞行参数。
而不是求助于任意精确解决方案(正如其他人所说,可能非常慢),你可以只需创建一个 class,将 double
类型的固有精度扩展(大约)两倍。然后你只需要实现你实际需要的操作:在你的情况下,这可能只是减法(也可能是加法),这两者 合理地 很容易实现。这样的代码仍然比使用本机类型慢得多,但可能比使用不必要精度的库快得多。
这样的实现在 QD_Real class 中可用(如 open-source),由 Yozo Hida(我相信当时是博士生)创建。
链接的存储库包含 很多 代码,其中大部分可能对您的 use-case 来说是不必要的。下面,我展示了一个非常 trimmed-down 的版本,它允许创建具有所需精度的数据,显示了所需 operator-()
的实现和一个测试用例。
#include <iostream>
class ddreal {
private:
static inline double Plus2(double a, double b, double& err) {
double s = a + b;
double bb = s - a;
err = (a - (s - bb)) + (b - bb);
return s;
}
static inline void Plus3(double& a, double& b, double& c) {
double t3, t2, t1 = Plus2(a, b, t2);
a = Plus2(c, t1, t3);
b = Plus2(t2, t3, c);
}
public:
double x[2];
ddreal() { x[0] = x[1] = 0.0; }
ddreal(double hi) { x[0] = hi; x[1] = 0.0; }
ddreal(double hi, double lo) { x[0] = Plus2(hi, lo, x[1]); }
ddreal& operator -= (ddreal const& b) {
double t1, t2, s2;
x[0] = Plus2(x[0], -b.x[0], s2);
t1 = Plus2(x[1], -b.x[1], t2);
x[1] = Plus2(s2, t1, t1);
t1 += t2;
Plus3(x[0], x[1], t1);
return *this;
}
inline double toDouble() const { return x[0] + x[1]; }
};
inline ddreal operator-(ddreal const& a, ddreal const& b)
{
ddreal retval = a;
return retval -= b;
}
int main()
{
double sdone{ 1.0 };
double sdwee{ 1.0e-42 };
double sdval = sdone - sdwee;
double sdans = sdone - sdval;
std::cout << sdans << "\n"; // Gives zero, as expected
ddreal ddone{ 1.0 };
ddreal ddwee{ 1.0e-42 };
ddreal ddval = ddone - ddwee; // Can actually hold 1 - 1.0e42 ...
ddreal ddans = ddone - ddval;
std::cout << ddans.toDouble() << "\n"; // Gives 1.0e-42
ddreal ddalt{ 1.0, -1.0e-42 }; // Alternative initialization ...
ddreal ddsec = ddone - ddalt;
std::cout << ddsec.toDouble() << "\n"; // Gives 1.0e-42
return 0;
}
请注意,我故意忽略了 error-checking 和更通用的实现所需的其他开销。此外,我展示的代码已经 'tweaked' 在 x86/x64 CPU 上更优化地工作,因此如果您需要其他支持,您可能需要深入研究链接 GitHub 处的代码平台。 (但是,我认为我展示的代码适用于任何严格符合IEEE-754标准的平台。)
我已经在我用来生成和显示 Mandelbrot 集(和相关分形)的代码中广泛地测试了这个实现 非常 深度缩放级别,其中使用原始double
类型完全失败。
请注意,尽管您可能想 'optimize' 一些 看似 无意义的操作,但这样做会破坏系统。此外,此 必须 使用 /fp:precise
(或 /fp:strict
)标志(使用 MSVC)或其他编译器的等效标志进行编译;使用 /fp:fast
将完全破坏代码。