我有两个整数n和d。这些可以用双dn(n)和双dd(d)精确表示。有没有一种可靠的方法在C ++中检查是否
double result = dn/dd
包含舍入错误?如果它只是一个整数除法检查(n/d) * d==n
是否可行,但使用双精度算法执行此操作可能会隐藏舍入错误。
编辑:发布后不久,让我感到震惊的是,将舍入模式更改为round_down会使(n/d)*d==n
测试工作为double。但如果有一个更简单的解决方案,我仍然希望听到它。
答案 0 :(得分:3)
如果忽略溢出和下溢(除非表示d
和n
的整数类型非常宽,否则你应该能够做到),那么(二进制)浮点除法{{1确切地说,iff dn/dd
是d
乘以2的幂的除数。
检查此问题的算法可能如下所示:
n
如果你希望舍入模式在其余时间“最接近”,这比改变FPU舍入模式便宜,并且可能存在可以加速提取最大奇数除数的比特诡计。 assert(d != 0);
while (d & 1 == 0) d >>= 1; // extract largest odd divisor of d
int exact = n % d == 0;
。
答案 1 :(得分:3)
如果硬件FMA可用,那么,在大多数情况下(预计n
不小的情况下,如下),最快的测试可能是:
#include <cmath>
…
double q = dn/dd;
if (std::fma(-q, dd, dn))
std::cout << "Quotient was not exact.\n";
如果 nd - q • dd 如此之小,它可能会失败,它会四舍五入为零,这发生在舍入到最近的位置如果其幅度小于最小可表示正值的一半(通常为2 -1074 ),则为-even-even模式。只有当dn
本身很小时才会发生这种情况。如果需要,我希望我可以计算dn
上的一些限制,并且,假设dn
= n
且n
是整数,则不应该发生。
忽略指数边界,测试可分性的有效位数的方法是:
#include <cfloat>
#include <cmath>
…
int sink; // Needed for frexp argument but will be ignored.
double fn = std::ldexp(std::frexp(n, &sink), DBL_MANT_DIG);
double fd = std::frexp(d, &sink);
if (std::fmod(fn, fd))
std::cout << "Quotient will not be exact.\n";
鉴于 n 和 d 是在浮点类型中可以完全表示的整数,我想我们可以证明他们的指数不能是这样的,上面的测试会失败。有些情况下 n 是一个小整数且 d 很大(从2 1023 到2 1024 的值我需要考虑-2 972 ,包括在内。
答案 2 :(得分:3)
在C ++中是否有可靠的方法来检查
double result = dn/dd
是否包含舍入错误?
如果您的系统允许访问各种FP标志,请在分割后测试FE_INEXACT
。
如果FP代码很昂贵,那么至少可以使用此代码来检查仅整数解决方案。
遵循C解决方案,(我无法访问兼容的C ++编译器以立即进行测试)
#include <fenv.h>
// Return 0: no rounding error
// Return 1: rounding error
// Return -1: uncertain
#pragma STDC FENV_ACCESS ON
int Rounding_error_detection(int n, int d) {
double dn = n;
double dd = d;
if (feclearexcept(FE_INEXACT)) return -1;
volatile double result = dn/dd;
(void) result;
int set_excepts = fetestexcept(FE_INEXACT);
return set_excepts != 0;
}
测试代码
void Rounding_error_detection_Test(int n, int d) {
printf("Rounding_error_detection(%d, %d) --> %d\n",
n, d, Rounding_error_detection(n,d));
}
int main(void) {
Rounding_error_detection_Test(3, 6);
Rounding_error_detection_Test(3, 7);
}
输出
Rounding_error_detection(3, 6) --> 0
Rounding_error_detection(3, 7) --> 1
答案 3 :(得分:0)
如果商q=dn/dd
是精确的,它将dn精确划分dd次
由于你有dd是整数,你可以用整数除法测试精确度
而不是测试商数乘以dd和(dn/dd)*dd==dn
,其中四舍五入错误可以补偿,你应该测试余数。
确实std:remainder
总是确切的:
if(std:remainder(dn,dn/dd)!=0)
std::cout << "Quotient was not exact." << std::endl;