【问题标题】:How can I round a float to a given decimal precision如何将浮点数舍入到给定的小数精度
【发布时间】:2013-10-02 19:05:18
【问题描述】:

我需要将具有系统精度的浮点数转换为具有指定精度(例如 3 位小数)的浮点数以用于打印输出。 fprintf 函数不足以解决此问题,因为它无法正确舍入某些数字。我尝试过的所有其他解决方案都失败了,因为当我转换回浮点数时,它们都重新引入了不希望的精度。例如:

float xf_round1_f(float input, int prec) {

    printf("%f\t",input);
    int trunc = round(input * pow(10, prec));
    printf("%f\t",(float)trunc);
    input=(float)trunc / pow(10, prec);
    printf("%f\n",input);
    return (input);

}

此函数将输入、截断的整数和输出打印到每一行,对于一些应该截断到小数点后 3 位的数字,结果如下所示:

49.975002 49975.000000 49.974998

49.980000 49980.000000 49.980000

49.985001 49985.000000 49.985001

49.990002 49990.000000 49.990002

49.995003 49995.000000 49.994999

50.000000 50000.000000 50.000000

您可以看到第二步按预期工作 - 即使“trunc”被强制转换为浮点数以进行打印 - 但一旦我将其转换回浮点数,精度就会返回。第 1 行和第 6 行说明问题案例。

肯定有办法解决这个问题——即使第一行的结果仍然是 49.975002,格式化打印也会产生预期的效果,但在这种情况下确实存在问题。

有什么解决办法吗?

【问题讨论】:

  • 这是二进制浮点无法避免的,抱歉。
  • @Eric 抱歉一发布就删除了它,因为它也没有回答问题:-)。
  • 通常这种情况下的方法——例如货币金额,是使用整数类型并将其转换为固定精度以用于显示目的。例如。对于货币,您可以使用 int32_t 美分。一些编程语言(例如 COBOL)有特定的类型来处理这个问题。根本问题是 5 不会像 3 不会进入 10 那样进入 2,所以任何十进制数都是二进制的无限循环扩展。
  • @JWDN:您的问题可能被否决了,因为您仍然没有准确地指定问题,您没有正确使用浮点,并且您可能不应该使用它。如果您想保留十进制数字,还有其他方法可以进行算术运算。浮点专为数学、科学和工程计算而设计,具有大动态范围和良好的数学特性。您将其用于与设计目的不同的目的,因此您当然会遇到问题。您接受的答案是杂牌。

标签: c linux floating-point rounding


【解决方案1】:

二进制浮点数不能准确表示大多数十进制数字。每个二进制浮点数由整数乘以 2 的幂形成。对于float 的常见实现,IEEE-754 32 位二进制浮点,该整数必须在 (–224, 224) 中。没有整数 x 和整数 y 使得 x•2y 正好等于 49.975。因此,当您将 49975 除以 1000 时,结果必须是一个近似值。

如果您只需要格式化输出的数字,您可以使用通常的fprintf 格式说明符来执行此操作。如果您需要精确计算这些数字,您可以通过将它们缩放为可表示的值并根据您的需要以浮点或整数算术进行算术来实现。

【讨论】:

  • 我很欣赏这一点,但我当然不必求助于将每个数字转换为字符串、评估小数点后的每个数字并将其转换回数字?我希望避免这种情况(我什至不确定它会起作用)。
  • @JWDN:您似乎没有抓住重点。您不能转换回数字(float)并准确得到 49.975。用于进行舍入的方法无关紧要。 float 没有值正好是 49.975。无论您在float 中放入什么位,它都永远不会有确切的值 49.975。您的示例中似乎正确的值是幻觉,而不是 50;它们之所以这样显示,只是因为您打印了有限的位数。
  • 老实说我明白这一点。但我不是第一个提出这个问题的人,而且对于许多应用程序来说这是不可接受的——所以我仍然(也许是徒劳的)希望有一个解决方案。
  • @JWDN:解决方案是不使用二进制浮点数据类型。
  • @JWDN:你还没有真正说出你的问题。如果你的问题是“我想要二进制浮点对象中的 49.975”,答案是你不会得到它,因为这在逻辑上是不可能的。但是,如果您解释更多有关您尝试进行的更大计算的信息,可能会有解决方案。
【解决方案2】:

编辑:看来您可能只关心打印的结果。 printf 通常足够聪明,可以对您指定的位数进行适当的舍入。如果你提供"%.3f" 的格式,你可能会得到你需要的东西。


如果您唯一的问题是低于所需数字的情况,您可以通过将所有内容设置为高于所需数字来轻松解决它。不幸的是,这增加了答案的绝对误差;即使是以前精确的结果,例如50.000,现在也关闭了。

只需将此行添加到函数的末尾:

input=nextafterf(input, input*1.0001);

http://ideone.com/iHNTzs查看它的实际应用

49.975002   49975.000000    49.974998   49.975002
49.980000   49980.000000    49.980000   49.980003
49.985001   49985.000000    49.985001   49.985004
49.990002   49990.000000    49.990002   49.990005
49.995003   49995.000000    49.994999   49.995003
50.000000   50000.000000    50.000000   50.000004

【讨论】:

  • 我想说谢谢你,首先是因为我揭示了我长期以来的假设 printf 总是截断数字似乎不正确 - 这个平台依赖吗?其次,即使 printf DID 的行为与我假设的一样,这个解决方案也能解决问题。
  • @JWDN,如果7.895f 实际上变成7.89499999,那么它不会像您期望的那样四舍五入到小数点后两位。要修正我的正数和负数公式,请使用nextafterf(input, 1.1*input)
  • @JWDN re platform-dependent printf: 你可能会觉得我的文章exploringbinary.com/… 很好读。
  • nextafterf(input, input+1.0) 的另一个缺点是它可能对input 的值产生意想不到的结果,例如input == input+1.0copysignf(FLT_MAX, input)nextafterf() 更好的第二个参数。
  • @PascalCuoq,非常好的观点。我已经修改了我的答案,尽管您的建议可能会更好。
【解决方案3】:

如果您需要用小数点后三位精确表示所有小数,您可以使用千分之一。使用整数数据类型来表示所有中间结果的实际数字的一千倍。

【讨论】:

    【解决方案4】:

    固定点数。这就是您将实际数字保存为宽精度整数格式的地方,例如 long 或 long long。而且您还保留小数位数。然后您还需要按小数位缩放定点数的方法。还有一些与字符串相互转换的方法。

    您遇到问题的原因是 1/10 不能完全表示为 2 的分数幂(1/2、1/4、1/8 等)。这与 1/3 是以 10 为基数的重复小数 (0.33333...) 的原因相同。

    【讨论】:

      猜你喜欢
      • 2022-12-13
      • 2015-04-09
      • 2011-02-28
      • 1970-01-01
      • 2012-11-08
      • 1970-01-01
      • 1970-01-01
      • 2015-06-12
      • 1970-01-01
      相关资源
      最近更新 更多