在未显式转换为浮动的情况下使用 uint16_t 操作时出现意外结果

Unexpected result when operating with uint16_t without explicit casting to float

提问人:Ricard Molins 提问时间:9/7/2023 最后编辑:wohlstadRicard Molins 更新时间:9/10/2023 访问量:85

问:

我正在做一些操作,但没有找到确切的解释来解释为什么我发现特定行为。 上下文:

  • 我收到一个值 (24.2),然后计算一些偏移和增益
  • 然后通过CAN发送结果。

最小工作示例:

#include <stdio.h>
#include <stdint.h>

int main()
{
    printf("Operations with comas \n");
    
    uint16_t a = (uint16_t)((24.2 - 0)/0.1);        /* 241  Incorrect*/
    uint16_t b = (uint16_t)((24.2 - 0.0)/0.1);      /* 241 Incorrect */
    uint16_t c = (uint16_t)((float)(24.2 - 0)/0.1); /* 242 Correct */
    uint16_t d = (uint16_t)(24.2/0.1);              /* 241 Incorrect*/
    uint16_t e = (uint16_t)(242.0);                 /* 242 Correct */
    
    printf("a %u \n" , a);
    printf("b %u \n" , b);
    printf("c %u \n" , c);
    printf("d %u \n" , d);
    printf("e %u \n" , e);
    
    return 0;
}

我知道当使用浮点数时,该值无法准确表示。用实际表示的值,因此在大小写和中截断是正确的。IEEE-75424.2 is24.200000762939453125ce

为什么 case ,, 会产生意想不到的值?(我知道强制施法可以解决问题,但我想解释原因)abd

C GCC 浮点 IEEE-754

评论

1赞 JohnFilleau 9/7/2023
0.1 作为双精度的确切表示是什么?
1赞 Marco Bonelli 9/7/2023
根据 C 标准,一个无后缀的浮点常量的类型是 ,而不是 ,因此代码中所有带有点的常量都是 ,而其他所有内容都被提升为 。doublefloatdoubledouble
0赞 dbush 9/7/2023
重新。OP 意识到浮点运算的不精确性,并质疑特定表示发生了什么。
0赞 Jonathan Leffler 9/7/2023
众说纷纭——问题在于浮点运算的本质,这是“为什么浮点运算不能像我预期的那样工作”的典型问题。

答:

2赞 Marco Bonelli 9/7/2023 #1

根据 C 标准,一个无后缀的浮点常量的类型是 ,而不是 ,因此代码中所有带有点的常量都是 ,而其他所有内容都被提升为 。doublefloatdoubledouble

采用以下代码(请注意第二个表达式值的后缀):f

#include <stdio.h>
#include <inttypes.h>

int main(void) {
    uint16_t a = (uint16_t)((24.2 - 0)/0.1);
    uint16_t b = (uint16_t)((24.2f - 0)/0.1f);
    printf("%hu %hu\n", a, b);
}

输出为:

241 242

所以这里的重点是,由于使用了不同的浮点精度,所以正在发生不同的近似值。

评论

0赞 Ricard Molins 9/14/2023
之所以选择答案,是因为转换为 double 而不是 float 会导致可理解的行为
5赞 dbush 9/7/2023 #2

常量的类型为 ,值约为 24.19999999999999993。因此,除以 0.1 大约得到 241.999999999999999716,并将此值转换为整数类型将截断为 241。24.2double

当您转换为 类型 时,最接近的表示形式是 24.2000007629394531,除以 0.1 得到 242.0000076293945312,它被截断为 242。24.2float

4赞 ikegami 9/7/2023 #3

就像 1/3 是十进制的周期数,2/10 是二进制的周期数,

1/10 是二进制的周期数。
这些将需要无限存储才能准确地存储为浮点数。
因此,24.2 和 0.1 不能准确地表示为浮点数。
因此,一个接一个地潜水可能不会产生完全正确的结果。它可能略微过大或略小。
因此,截断可能会产生不良影响。

在使用 IEEE 双精度数字的计算机上,将double

24.2       = 24.199999999999999289457264239899814128875732421875
0.1        = 0.1000000000000000055511151231257827021181583404541015625
24.2 / 0.1 = 241.999999999999971578290569595992565155029296875

当您使用 a 而不是 a(浮点常量的默认值)时,您很幸运。它恰好是一个比你想要的数字略大的数字。floatdouble

24.2f       = 24.200000762939453125
0.1         = 0.1000000000000000055511151231257827021181583404541015625
24.2f / 0.1 = 242.00000762939453125

但它也很容易被低估。也许你应该使用舍入而不是截断?

0赞 Ian Abbott 9/8/2023 #4

如果实现定义,浮点运算需要符合规范的附录 F。特别应注意 C11 F.3 中的以下项目:__STDC_IEC_559__

  • 、、 和运算符提供 IEC 60559 加法、减法、乘法和除法运算。+-*/
  • 浮点类型的转换提供浮点精度之间的 IEC 60559 转换。
  • 从整数到浮点类型的转换提供了从整数到浮点的 IEC 60559 转换。
  • 从浮点型到整数型的转换提供类似于 IEC 60559 的转换,但始终向零四舍五入。

在这种情况下,编译器根据“假设”规则执行的任何优化都需要考虑上述注意事项,包括每个操作的舍入误差。它不能仅仅使用 24.2 / 0.1 = 242 的数学事实。