【问题标题】:Ensure float to be smaller than exact value确保浮点数小于精确值
【发布时间】:2015-11-12 18:44:35
【问题描述】:

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

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

xi,yi 都是整数。结果将是实际值的近似值。该近似值小于或等于实际值至关重要。我可以假设我所有的价值观都是有限的和积极的。 我尝试在此代码 sn-p 中使用 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 仍然让我高于正确值?

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

【问题讨论】:

  • IEEE754/floats 会出现舍入和表示错误。如果您绝对需要精确值,请考虑使用分数或任意精度的数字类型。
  • 将舍入模式设置为FE_DOWNWARD,然后进行计算。或者nextafterf(foo, -1.0/0.0)你的计算2n-1次。
  • @RegretBomb:好像你连问题都懒得看……
  • @tmyklebu 略显多余的文字并不意味着我没有阅读您的问题。
  • @RegretBomb 就好像你读的太少了,你甚至没有看到提问者和告诉你你的言论无关紧要的人是不同的人。

标签: c++ floating-point rounding precision floating-point-precision


【解决方案1】:

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

在此处查看代码示例:

Change floating point rounding mode

【讨论】:

  • 这对我不起作用。一些值仍然四舍五入到无穷大。我读到#pragma STDC_FENC_ACCESS 是依赖于编译器的。这可能是这里的问题吗?我正在使用 gcc 4.8.4。
  • 将舍入模式设置为fegetround 会产生比以前不同的结果。但遗憾的是这并不影响四舍五入的结果。
  • @Ricardo 我得到总和 23.3333327770233 - 发布的代码是否与您的运行代码 100% 相同?
  • @Ricardo:默认情况下,大多数编译器会在舍入到最近的情况下进行常量折叠。您需要使用 gcc 指定 -frounding-math#pragma STDC FENV_ACCESS ON not 可以解决问题)。我什至不确定-frounding-math 是否完全有效...
  • @StillLearning:对于想要定向舍入的人,我不知道更好的解决方案。不幸的是,编译器供应商并不关心正确地实现他们的语言,而且如果你没有几个月的时间来学习编译器的内部工作以做出贡献,那么除了像我一样在互联网上抱怨之外没有真正的追索权也是不幸的。但这似乎就是现在的生活。
【解决方案2】:

我的直接反应是,您采用的方法存在根本缺陷。

问题在于,对于浮点数,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

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

结果看起来相当清晰:结果应该比输入大一点,而不是比输入略小。

如果你想确保结果小于正确的数字,显而易见的选择是将分子向下四舍五入,但分母向上(即@987654324 @. 这样,您会得到一个较小的分子和一个较大的分母,因此结果总是比未修改的版本小。

【讨论】:

  • 加一但最后提到区间算术?
  • 有趣的行为!不过,不确定我是否理解你的意思。在我的情况下,num 和 denom 始终是整数。
  • @Ricardo:我用浮点数作为一个简单的例子,但是当输入是整数时,你可以得到完全相同的情况。
【解决方案3】:

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

通过使用下一个,(根据方程式需要向上或向下),结果肯定会小于精确的数学值。这种方法可能不会生成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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-05
    • 1970-01-01
    相关资源
    最近更新 更多