确保浮点数小于精确值

Ensure float to be smaller than exact value

我想在 C++ 中计算以下形式的总和

float result = float(x1)/y1+float(x2)/y2+....+float(xn)/yn

xi,yi 都是整数。结果将是实际值的近似值。这个近似值小于或等于实际值是至关重要的。我可以假设我所有的价值观都是有限的和积极的。 我试过在这个代码片段中使用 nextf(,0)。

cout.precision( 15 );
float a = 1.0f / 3.0f * 10; //3 1/3
float b = 2.0f / 3.0f * 10; //6 2/3
float af = nextafterf( a , 0 );
float bf = nextafterf( b , 0 );
cout << a << endl;
cout << b << endl;
cout << af << endl;
cout << bf << endl;
float sumf = 0.0f;
for ( int i = 1; i <= 3; i++ )
{
    sumf = sumf + bf;
}
sumf = sumf + af;
cout << sumf << endl;

可以看出正确的解决方案是 3*6,666... +3.333.. = 23,3333... 但作为输出我得到:

3.33333349227905
6.66666698455811
3.33333325386047
6.66666650772095
23.3333339691162

即使我的加数小于它们应表示的值,但它们的总和却不是。在这种情况下,将 nextafterf 应用于 sumf 会得到更小的 23.3333320617676。但这总是有效吗?舍入误差是否有可能变得如此之大以至于 nextafterf 仍然使我高于正确值?

我知道我可以通过对分数实施 class 并准确计算所有内容来避免这种情况。但是我很好奇是否可以用花车实现我的目标。

尝试将浮动舍入模式更改为 FE_TOWARDZERO。

参见此处的代码示例:

Change floating point rounding mode

我的第一反应是您采用的方法存在根本性缺陷。

问题在于,对于浮点数,nextafter 将采用的步长大小将取决于所涉及数字的大小。让我们考虑一个有点极端的例子:

#include <iostream>
#include <iomanip>
#include <cmath>

int main() { 
    float num = 1.0e-10f;
    float denom = 1.0e10f;

    std::cout << std::setprecision(7) << num - std::nextafterf(num, 0) << "\n";
    std::cout << std::setprecision(7) << denom - std::nextafterf(denom, 0) << "\n";
}

结果:

6.938894e-018
1024

所以,由于分子比分母小很多,增量也小很多。

结果似乎很清楚:结果应该比输入大很多,而不是结果比输入小一点。

如果要确保结果小于正确的数字,显而易见的选择是将分子向下舍入,但分母向上舍入(即 nextafterf(denom, positive_infinity)。这样,您会得到更小的分子和更大的分母,因此结果总是小于未修改的版本。

float result = float(x1)/y1+float(x2)/y2+....+float(xn)/yn有3处可能出现四舍五入。

  1. intfloat 的转换 - 它并不总是准确的。
  2. floating point x/floating point y
  3. 加法:floating point quotient + floating point quotient.

通过使用 next,(根据方程需要向上或向下),结果肯定会 less数学值。这种方法可能不会生成 float 最接近 的确切答案,但会很接近并且肯定更小。

float foo(const int *x, const int *y, size_t n) {
  float sum = 0.0;
  for (size_t i=0; i<n; i++) {  // assume x[0] is x1, x[1] is x2 ...
    float fx = nextafterf(x[i], 0.0);
    float fy = nextafterf(y[i], FLT_MAX);
    // divide by slightly smaller over slightly larger
    float q = nextafterf(fx / fy, 0.0);
    sum = nextafterf(sum + q, 0.0);
  }
  return sum;
}