【问题标题】:C++ Float Division and PrecisionC++ 浮点除法和精度
【发布时间】:2011-05-14 16:32:37
【问题描述】:

我知道 511 除以 512 实际上等于 0.998046875。我也知道浮点数的精度是 7 位。我的问题是,当我在 C++ (GCC) 中进行此数学运算时,我得到的结果是 0.998047,这是一个四舍五入的值。我宁愿只得到 0.998046 的截断值,我该怎么做?

  float a = 511.0f;
  float b = 512.0f;
  float c = a / b;

【问题讨论】:

  • 你不能使用双精度来提高精度并截断它吗?
  • 这是游戏代码,虽然 double 可以解决上述问题,但我正在为纹理渲染进行此计算,double 可能会增加性能损失。问题是,舍入导致纹理中有一个像素偏移。
  • 是您的调试器对值进行舍入。
  • @Nick - 如果您向我们展示导致 1 像素错误的代码,我们可以为您提供帮助(作为一个单独的问题,可能......)
  • 不要太确定doubles 会导致性能下降。在许多系统上,当您使用float 时,它实际上会将所有内容转换为double,进行所有数学运算,然后再转换回float——因此,当您使用float 时,它实际上做了更多的工作。

标签: c++ math floating-point


【解决方案1】:

嗯,这是一个问题。 511/512 作为float 的值是准确的。不进行四舍五入。您可以通过询问超过七位数来检查这一点:

#include <stdio.h>
int main(int argc, char *argv[])
{
    float x = 511.0f, y = 512.0f;
    printf("%.15f\n", x/y);
    return 0;
}

输出:

0.998046875000000

float 不是存储为十进制数,而是二进制。如果您将一个数字除以 2 的幂,例如 512,则结果几乎总是准确的。发生了什么是 float 的精度不仅仅是 7 位数字,它实际上是 23 的精度。

What Every Computer Scientist Should Know About Floating-Point Arithmetic

【讨论】:

  • 24 位,因为通过保持数字标准化可以再获得一位。
  • 没错。提问者的示例中唯一出现的舍入是当他打印出值时。就像@AProgrammer 所说,它有 24 位的精度。
  • 这回答了这个问题,尽管我的原始代码中仍然存在像素偏移问题,但这是为了帮助!
  • 从数学上讲,它是 7.22 位十进制数字的精度,但是,由于数字切片,必须使用最多 9 位十进制数字来表示特定的浮点数。看我的回答here
  • @ThomasMcLeod,6.92,而不是 7.22。例如 0x1.0624d2p-10=9.99999349e-04 和 0x1.0624d4p-10=9.99999465e-04 是两个连续的浮点数,所以表示 9.999994e-04 是有问题的。
【解决方案2】:

我也知道浮点数的精度是7位。

没有。最常见的浮点格式是二进制,精度为 24 位。它介于 6 到 7 位十进制数字之间,但如果您想了解四舍五入的工作原理,就不能用十进制思考。

由于 b 是 2 的幂,c 是完全可表示的。在十进制表示的转换过程中会发生舍入。获得十进制表示的标准方法不提供使用截断而不是舍入的可能性。一种方法是再要求一位数字并忽略它。

但请注意,c 可精确表示这一事实是其值的属性。一些明显更简单的值(如 0.1)在二进制 FP 格式中没有精确的表示。

【讨论】:

  • 24 位精度不是“6 到 7 位十进制数字”,因为 0 到 2^24-1 的范围等于 0 到 16777215,所以正确答案是 7 到 8 位,因为 7 位( 9999999)明显小于16777215,8位(99999999)明显大于16777215。
  • @Olof, 0x1.0624d2p-10=9.99999349e-04 和 0x1.0624d4p-10=9.99999465e-04 是两个连续的浮点数,所以代表 9.999994e-04 是有问题的,你不会有 7 位小数精度。
  • @OlofForshell,您的分析直截了当但不正确。因为二进制值和十进制值没有精确对齐,所以即使范围更大,也可以跳过一个值。为了消除这种可能性,您认为需要的范围是您认为需要的 2 倍,因此您会损失一点。
  • @Mark Ransom:16777215 是可以表示为浮点数的最大奇数。这是因为它对应于 2^24-1,即在一行中包含二进制 1,对应于浮点有效数中的 24(23 个显式 + 1 个隐式)位。从 16777216 开始,每隔一个整数最多可以表示为 2^25-2。实际上范围是“0 to 2^24-2^0 by 2^0”,然后是“2^24 to 2^25-2^1 by 2^1”,“2^25 to 2^26-2^ 2 x 2^2" 等等。
【解决方案3】:

您的问题不是唯一的,之前已经回答过无数次了。这不是一个简单的话题,仅仅因为发布了答案并不一定意味着它们的质量很好。如果你稍微浏览一下,你会发现真正的好东西。而且它会花费您更少的时间。

我敢打赌,如果我发表评论而不回答,我会 -1。

____ 编辑 _____

理解浮点的基础是认识到一切都以二进制数字显示。因为大多数人难以理解这一点,所以他们试图从十进制数字的角度来看待它。

关于 511/512,您可以从查看值 1.0 开始。在浮点数中,这可以表示为 i.000000... * 2^0 或隐式位集(为 1)乘以 2^0 即等于 1。由于 511/512 小于 1,因此您需要从下一个开始较低的功率 -1 给出 i.000000... * 2^-1 即 0.5。请注意,唯一改变的是指数。如果我们想用二进制表示 511,我们会得到 9 个 - 111111111 或带有隐式位 i.11111111 的浮点数 - 我们可以将其除以 512 并与 -1 的指数放在一起,得到 i.1111111100... * 2^ -1.

这如何转换为 0.998046875?

首先隐式位表示0.5(或2^-1),第一个显式位0.25(2^-2),下一个显式位0.125(2^-3),0.0625,0.03125等等直到你代表第九位(第八位显式)。把它们加起来,你得到 0.998046875。从 i.11111111 我们发现这个数字代表 9 位二进制精度,巧合的是,9 位十进制精度。

如果将 511/512 乘以 512,您将得到 i1111111100... * 2^8。这里有相同的九位二进制精度,但只有三位十进制数字(对于 511)。

考虑 i.11111111111111111111111(i + 23 个)* 2^-1。我们将得到一个分数 (2^(24-1)^/(2^24)),其精度为 24 位二进制和 24 位十进制数字。给定适当的 printf 格式,将显示所有 24 个十进制数字。将其乘以 2^24,仍然有 24 位二进制精度,但只有 8 位十进制(对于 16777215)。

现在考虑 i.1111100... * 2^2 得出 7.875。 i11 是整数部分,111 是小数部分(111/1000 或 7/8ths)。 6 位二进制精度和 4 位十进制数。

在处理浮点时考虑小数对理解它是完全有害的。放飞自我!

【讨论】:

  • -1 表示可以在许多问题下逐字重复使用的文本。
  • @EvgeniSergeev:自助!这是关于浮点数学的一些复杂性。根据我在该主题上的个人经验,并考虑到或多或少地重复发布相同的问题,我会说这是一个在复杂性或感知复杂性方面远远高于平均水平的主题。那些回答的人通常似乎对分数最感兴趣,但很少有实际的学科知识可以分享——这通常也是不正确的。
  • @OlofForshell 我想这个想法是你应该是具体的,你可以在这里,通过投票和评论不正确的答案。我并不是说这取决于你一个人,但随着时间的推移,社区将把更正确和有用的答案带到顶部。这是这个网站的一个很好的特点,即使这个页面上充斥着不正确的答案,它仍然可以正常工作。
【解决方案4】:

“四舍五入”值最可能是通过某种输出方法显示的值,而不是实际存储的值。检查调试器中的实际值。

使用 iostream 和 stdio,您可以指定输出的精度。如果指定 7 位有效数字,将其转换为字符串,然后在显示前截断字符串,您将获得不四舍五入的输出。

想不出你为什么要这样做的一个原因,并且鉴于随后对应用程序的解释,你最好使用双精度,尽管这很可能只是将问题转移到其他地方。

【讨论】:

    【解决方案5】:

    如果您只对值感兴趣,您可以使用 double 然后将结果乘以 10^6 并将其取底。再除以 10^6 即可得到截断值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-04
      • 1970-01-01
      • 2011-04-18
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      • 2011-02-23
      相关资源
      最近更新 更多