C++ 中的日志基础 2 精度错误

log base 2 precision error in c++

本文关键字:精度 错误 日志 C++      更新时间:2023-10-16

请解释以下给定代码的输出。对于这两种情况,我都得到了不同的 c 值,即,

情况 1:n 的值取自标准输入。情况2:n的值直接写在代码中。友情链接 : http://www.ideone.com/UjYFQd

#include <iostream>
#include <cstdio>
#include <math.h>
using namespace std;
int main()
{
    int c;
    int n;
    scanf("%d", &n);    //n = 64
    c = (log(n) / log(2));
    cout << c << endl;  //OUTPUT = 5
    n = 64;
    c = (log(n) / log(2));
    cout << c << endl;  //OUTPUT = 6
    return 0;
}

您可能会看到这一点,因为浮点数的存储方式:

double result = log(n) / log(2); // where you input n as 64
int c = (int)result; // this will truncate result.  If result is 5.99999999999999, you will get 5

对值进行硬编码时,编译器将为您优化它:

double result = log(64) / log(2); // which is the same as 6 * log(2) / log(2)
int c = (int)result;

很可能会完全替换为:

int c = 6;

因为编译器会看到您正在使用一堆编译时常量将值存储在变量中(它将继续在编译时处理值)。

如果要获取操作的整数结果,则应使用 std::round 而不是仅强制转换为int

int c = std::round(log(n) / log(2));
第一次

计算log(n)/log(2),结果非常接近6,但略少。这就是浮点计算的工作方式:log(64) 和 log(2) 在二进制浮点中都没有无限精确的表示,因此您可以预期将一个除以另一个的结果与真正的数学值略有偏差。根据实现的不同,您可以期望获得 5 或 6。

在第二个计算中:

n = 64;
c = (log(n) / log(2));

分配给c的值可以推断为编译时常量,并且可以由编译器计算。编译器在运行时在与程序不同的环境中执行计算,因此您可能会从编译时和运行时执行的计算中获得略有不同的结果。

例如,为 x86 生成代码的编译器可以选择使用使用 80 位浮点运算的 x87 浮点指令,而编译器本身使用标准的 64 位浮点运算来计算编译时常量。

检查编译器的汇编程序输出以确认这一点。使用 GCC 4.8,我从两次计算中都得到了 6

输出的差异可以用以下事实来解释:gcc在常量情况下优化了对log的调用,例如,在这种情况下:

n = 64;
c = (log(n) / log(2));

log的两个调用都是在编译时完成的,这些编译时计算可能会导致不同的结果。这记录在 gcc 手册的 GCC 提供的其他内置函数部分,其中说:

GCC 包括标准 C 库中许多函数的内置版本。以 _builtin 为前缀的版本始终被视为与 C 库函数具有相同的含义,即使您指定了 -fno 内置选项也是如此。(请参阅 C 方言选项)其中许多功能仅在某些情况下进行优化;如果它们在特定情况下未优化,则会发出对库函数的调用。

log是具有内置版本的众多函数之一。如果我使用 -fno-builtin 构建所有对 log 的四个调用,但没有它,只会发出一个对log的调用,您可以通过使用 -S 标志进行构建来检查这一点,该标志将输出gcc生成的程序集。