【问题标题】:Round 37.1-28.75 float calculation correctly to 8.4 instead of 8.3Round 37.1-28.75 float 计算正确到 8.4 而不是 8.3
【发布时间】:2021-03-06 23:13:54
【问题描述】:

浮点舍入有问题。我想计算浮点数并将它们四舍五入到(给定)N 个小数。在这个例子中,我想四舍五入到小数点后 1 位。

计算 37.1-28.75 将得到浮点 8.349998(而不是 8.35),这将导致 printf 舍入为 8.3 而不是 8.4 小数点后 1 位。

数学上的实际结果是 37.10-28.75=8.35000000,但由于浮点不精确,它被转换为 8.349998,然后在使用 1 个小数位四舍五入时转换为 8.3 而不是 8.4。

最小可重现示例:

float a = 37.10;
float b = 28.75;
//a-b = 8.35 = 8.4
printf("%.1f\n", a - b); //outputs 8.3 instead of 8.4

在结果中添加以下内容是否有效:

float result = a - b;

if (result > 0.0f)
{
    result += powf(10, -nr_of_decimals - 1) / 2;
}
else
{
    result -= powf(10, -nr_of_decimals - 1) / 2;
}

编辑:更正我想要 1 个小数位的舍入输出,而不是 2 个小数位

EDIT2:也需要负面结果 (28.75-37.1 = -8.4)

【问题讨论】:

  • 听起来你需要roundf
  • @user3386109 抱歉,添加了示例!这实际上揭示了我的问题中的严重错误。
  • 是的,现在说得通了。该减法产生的数字 (8.35) 正好是两个可能的数字之间的一半,小数点后有一位数字(8.3 和 8.4)。所以计算中最轻微的错误都会影响数字的四舍五入方式。
  • 关于“计算 37.1-28.75”:在基于二进制的 float 格式中不可能进行这样的计算,因为 37.1 无法以该格式表示。 您的计算甚至开始之前,float a = 37.10; 导致 a 被设置为 37.09999847412109375,采用 float 常用的格式。除了以 .00、.25、.50 和 .75 结尾的数字以外,不可能在二进制浮点中用两位十进制数字表示数字,因此基于二进制的浮点数是不是用于此类工作的正确格式。

标签: c math rounding


【解决方案1】:

二进制浮点毕竟是二进制的,如果您确实如此关心正确的十进制四舍五入,那么您的选择是:

  • 十进制浮点数,或
  • 固定点。

我会说解决方案是使用定点,特别是如果你是嵌入式的,而忘记其他一切。

int32_t a = 3710;
int32_t b = 2875;

结果

a - b

将是

835

每次;然后你只需要一个简单的定点打印程序来获得所需的精度,并检查最后一位之后的下一位是否需要四舍五入。

【讨论】:

    【解决方案2】:

    如果要四舍五入到小数点后2位,可以在结果上加上0.005,然后用floorf进行偏移:

    float f = 37.10f - 28.75f;
    float r = floorf((f + 0.005f) * 100.f) / 100.f;
    
    printf("%f\n", r);
    

    输出为 8.350000

    您为什么使用floats 而不是doubles?

    关于你的问题:

    在结果中添加以下内容是否有效:

            float result = a - b;
    
            if (result > 0.0f)
            {
                result += powf(10, -nr_of_decimals - 1) / 2;
            }
            else
            {
                result -= powf(10, -nr_of_decimals - 1) / 2;
            }
    

    似乎不是这样,在我的电脑上我得到的是8.350498 而不是8.350000

    修改后:

    计算 37.1-28.75 将得到浮点 8.349998,这将导致 printf 舍入为 8.3 而不是 8.4。

    然后

    float r = roundf((f + (f < 0.f ? -0.05f : +0.05f)) * 10.f) / 10.f;
    

    就是你要找的。​​p>

    【讨论】:

    • 对不起,我问错了!现在编辑。我需要 1 个小数位输出,而不是 2 个。
    • 这似乎也输出 8.3 和 -8.3
    • @Tmas :)))) 好的:float r = roundf((f + (f &lt; 0.f ? -0.05f : +0.05f)) * 10.f) / 10.f;
    • 最新版本的问题是 37.10 - 28.79 仍然输出 8.4,即使正确舍入的值为 8.3。
    • @Tmas 我认为你会得到最好的结果是r = roundf(f * 100.f) / 100.f;
    【解决方案3】:

    在我的系统上,我确实得到了8.35。您可能必须先将舍入方向设置为“最近”,试试这个(编译例如gcc ... -lm):

    #include <fenv.h>
    #include <stdio.h>
    
    int main()
    {
      float a = 37.10;
      float b = 28.75;
      float res = a - b;
    
      fesetround(FE_TONEAREST);
    
      printf("%.2f\n", res);
    }
    

    【讨论】:

    • 对不起,我问错了!我想要 1 个小数位输出,而不是 2 个。无论如何感谢您提供的信息。
    • @Tmas:你真的想修改结果还是只修改 printf 的输出?
    • 实际上它将使用 printf 输出。这是嵌入式系统,我将使用 snprintf 将结果输出到缓冲区(四舍五入为 N 位小数)。据我所知,这些 printf 变体在舍入方面应该表现相同。
    猜你喜欢
    • 2012-09-09
    • 1970-01-01
    • 2011-04-06
    • 2010-11-07
    • 2015-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多