为什么使用 std::pow 会得到意外的计算结果?

问题描述 投票:0回答:1

我正在运行以下代码来计算 -1.9 * 10。

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <math.h>

using namespace std;

int main() {
    float x = -1.9;
    int value = x * (int) std::pow(10, 1);
    cout << value << endl;
    return 0;
}

预期结果应该是-19,但我得到的是-18。 原因是什么?如何才能永远解决它?

我尝试过将 float 转换为 int 并用其他方法替换 std::pow 。 两者都解决了问题,为什么?

c++ floating-point
1个回答
2
投票

发生这种情况是因为您的 C++ 实现在评估浮点运算时使用了额外的精度,这是 C++ 标准允许的。 C++ 2020 草案 N4849 7.1 [expr.pre] 6 表示“浮点操作数的值和浮点表达式的结果可以用比类型所需的更高的精度和范围来表示;类型不会因此改变。”

首先,我们观察到,如果您的 C++ 实现仅使用

float
算术,则不会发生这种情况。我假设您正在使用一种常见的 C++ 实现,该实现使用 IEEE-754 二进制 32 表示
float
(使用二进制 64 表示
double
),并舍入到最接近的值,连到偶数:

  • float x = 1.9;
    给出
    x
    的值 -1.89999997615814208984375,这是
    float
    中可表示的最接近 -1.9 的值。 (实际上是先四舍五入到
    double
    ,再四舍五入到
    float
    ,最终结果是-1.89999997615814208984375。)
  • 即使数学结果可以浮点格式表示,
    pow
    的某些实现也会产生带有轻微错误的结果。因此,我们考虑
    std::pow(10, 1)
    返回略低于 10 的值的可能性。这不可能发生,因为
    (int) std::pow(10, 1)
    将产生 9,而
    x * 9
    将产生接近 -17.1 的值,并且输出将为“-17” ”,而不是“-18”。
  • 所以我们知道
    x * 10
    已被评估。 使用实数算术,
    x * 10
    正好是 -18.9999997615814208984375。这无法用
    float
    来表示,最接近的可表示值为 -19。
  • 因此,如果使用
    float
    算术,输出将为“-19”。既然不是,我们得出结论,没有使用
    float
    算术。

如果使用

double
来计算算术,则输出为“-18”:

  • x
    与上面的值相同 -1.89999997615814208984375。 (虽然表达式中可以使用额外的精度,但 C++ 需要赋值和强制转换才能将值转换为其标称类型。)
  • 使用
    x
    精度将
    double
    乘以 10 会产生 -18.9999997615814208984375。
  • 将其分配给
    value
    隐式地将其转换为
    int
    ,它会被截断,产生 -18。

您可以通过使用以下代码打印

FLT_EVAL_METHOD
(C++ 标准继承自 C 标准)的值来检查 C++ 实现用于计算浮点表达式的方法:

#include <iostream>
#include <cfloat>


int main(void)
{
    std::cout << FLT_EVAL_METHOD << '\n';
}

0 意味着实现使用其名义类型评估所有操作,我们知道您的 C++ 实现并非如此。

1 意味着

float
double
使用
double
进行评估。 (
long double
将使用 `long double。)

2 表示使用

long double

−1 表示实现未指定。

© www.soinside.com 2019 - 2024. All rights reserved.