【问题标题】:How does printf for float does not print the correct value for floating point [duplicate]浮点的printf如何不打印浮点的正确值[重复]
【发布时间】:2020-02-28 09:11:06
【问题描述】:

所以我一直在尝试制作自己的 printf,但现在我卡在了 %f 上。 我遇到的问题是,当我给它一个浮点数时,我不知道 printf 在后台做什么:f = 1.4769996 它打印 1.477000。 但是当我给它f = 1.4759995 时,它会打印出值 1.475999

float f = 1.4769996;
printf("%f\n", f); // 1.477000

f = 1.4759995;
printf("%f\n", f); // 1.475999

我想到的是 printf 最后看到了 5 并且它添加了一个但在第二个示例中不起作用。

这个浮点数背后的逻辑是什么?

【问题讨论】:

  • 所以我的理解是我应该考虑使用舍入函数来解决这个问题?
  • printf 没有“看到”a 5。它只看到 1 和 0。如果要对打印输出进行四舍五入,请阅读 printf 的文档。

标签: c floating-point printf


【解决方案1】:

您的 C 实现可能对 float 和 double 使用 IEEE-754 binary32 和 binary64 格式。鉴于此,float f = 1.4769996; 导致将 f 设置为 1.47699964046478271484375,f = 1.4759995; 导致将 f 设置为 1.47599947452545166015625。

那么很容易看出,将1.47699964046478271484375四舍五入到小数点后六位得到1.477000(因为下一位是6,所以我们四舍五入),四舍五入1.475999 47452545166015625到小数点后六位得到1.475999(因为下一位是4,所以我们向下取整)。

使用浮点数时,了解每个浮点值准确地代表一个数字非常重要(除非它是非数字 [NaN] 编码)。当您在源代码中编写1.4769996 时,它会转换为double 中可表示的值。当您将其分配给float 时,它会转换为可在float 中表示的值。对浮点对象的操作表现得好像该对象具有它所代表的值,而不是它的值是您在源代码中编写的数字。

为了提供更多详细信息,C 标准要求(在 C 2018 7.21.6.1 13 中)如果请求的位数最多为 DECIMAL_DIG,则使用 f 进行格式正确舍入。 DECIMAL_DIG 是实现支持的最宽浮点格式的小数位数,以便将该格式的任何数字转换为具有DECIMAL_DIG 有效十进制数字的数字并返回浮点格式会产生原始值 ( 5.2.4.2.2 12)。 DECIMAL_DIG 必须至少为 10。如果请求的位数超过 DECIMAL_DIG,C 标准允许在舍入时有一些余地。但是,高质量的 C 实现将按照 IEEE-754 的规定正确舍入(到具有所请求位数的最接近的数字,平局有利于偶数位)。

【讨论】:

    【解决方案2】:

    如果您尝试编写自己的printf,并且卡在%f,则需要了解三四件事:

    1. 当调用像printf 这样的“varargs”函数时,float 类型的参数总是隐式提升为double 类型。因此,当您在格式字符串中看到%f,并且您使用va_arg() 从列表中提取下一个参数时,您需要提取double 类型的参数,而不是float。 (这也意味着您只需处理一种情况,而不是两种。在printf 内部,您根本不必担心处理类型float。)

    2. 打印double 的整数部分很容易;这与打印int 或多或少是相同的问题,我猜你已经知道了,如果你有%d 工作。为了做一个简单明了的打印小数部分的工作,重复乘以 10 通常效果很好。也就是说,如果你想打印123.456,你已经得到了123处理完部分后,您可以继续打印其余部分,方法是获取小数部分 0.456,乘以 10 得到 4.56,然后截断得到 4,然后获取新的小数部分 0.56 并重复。

    3. 没有1.4769996 这样的号码。 (也没有像我刚刚使用的 123.456 这样的数字。)当我们写像 1.4769996 和 123.456 这样的数字时,我们正在考虑小数,但大多数计算机(包括您正在使用的计算机)在内部使用 binary 分数,并且您不能完全用二进制表示像 1.4769996 和 123.456 这样的小数,所以实际数字总是与您预期的有点不同,这就是为什么您当您预期 000 时,通常会出现轻微的“舍入误差”,或者在末尾出现额外的 999。
    4. 在这些东西上做正确的工作真的非常非常困难。如果您正在尝试编写自己的printf,并且您已经到了%f,并且如果您可以让它在大多数情况下运行良好,那么认为自己很幸运,并且收工。不要陷入最后一位数字的困境——或者如果你被束缚并决心在每种情况下都正确地得到最后一位数字(这当然是一个崇高的目标),做一些研究并留出一些时间,因为你'我会在这方面工作一段时间。

    【讨论】:

    • 非常感谢您的回答,非常有帮助。我在第 4 部分,是的,这真的很难做到,我会尽力而为:D
    猜你喜欢
    • 1970-01-01
    • 2019-12-16
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 2011-03-14
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多