c - float 到有符号整数的转换与 float 到无符号整数的转换不匹配

标签 c type-conversion

下面的宏

#define MS_TO_TICKS(ms) ( ( (float)(ms)/ MILLISECONDS_PER_SECOND) * clkRate() )

将以毫秒为单位的值转换为正确的时钟滴答数。出于某种原因,如果我将结果存储在有符号整数中,有时会得到与存储在无符号整数中不同的值。

下面的代码说明了这个问题,并输出如下:

Milliseconds: 7
Expected val: 14 
Signed Int  : 14 //OK
Unsigned Int: 14 //Still OK
Floating Pnt: 14.0000000000000
Double Precn: 14.0000004321337
Direct Macro: 14.0000004321337


Milliseconds: 10
Expected val: 20
Signed Int  : 20 //Expected value, looks like it rounded up
Unsigned Int: 19 //Rounded Down? What?????
Floating Pnt: 20.0000000000000
Double Precn: 19.9999995529652
Direct Macro: 19.9999995529652

这是在 Core i7 处理器上运行的,并使用 gcc 编译如下:

ccpentium -g -mtune=pentium4 -march=pentium4 -nostdlib -fno-builtin -fno-defer-pop \
    -ansi  -Wall  -Werror -Wextra  -Wno-unused-parameter -MD -MP

我没有看到使用 https://ideone.com/HaJVSJ 的相同行为

这是怎么回事??

int clkRate()
{
    return 2000;
}
const int MILLISECONDS_PER_SECOND = 1000;
#define MS_TO_TICKS(ms) ( ( (float)(ms)/ MILLISECONDS_PER_SECOND) * clkRate() )


void convertAndPrint(int ms)
{
    int  ticksInt;
    unsigned ticksUint;
    double ticksDbl;
    float ticksFlt;

    ticksInt = MS_TO_TICKS(ms);
    ticksUint= MS_TO_TICKS(ms);
    ticksFlt = MS_TO_TICKS(ms);
    ticksDbl = MS_TO_TICKS(ms);

    printf("Milliseconds: %i\n", ms);
    printf("Expected val: %i\n",ms*2);
    printf("Signed Int  : %2i\n"
           "Unsigned Int: %2u\n"
           "Floating Pnt: %.13f\n"
           "Double Precn: %.13f\n"
           "Direct Macro: %.13f\n",
           ticksInt,ticksUint,ticksFlt, ticksDbl, MS_TO_TICKS(ms));
}

void weirdConversionDemo(void)
{
    convertAndPrint(7);
    convertAndPrint(10);        
}

==编辑==

根据要求,汇编作为编译器的输出。我将代码稍微简化为:

int convertToSigned(int ms)
{
    return MS_TO_TICKS(ms);
}

unsigned int convertToUnsigned(int ms)
{
    return MS_TO_TICKS(ms);
}

convertToSigned 的汇编程序(片段):

fildl   8(%ebp)
movl    MS_PER_SECOND, %eax
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fdivrp  %st, %st(1)
fstps   -4(%ebp)
call    clkRate
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fmuls   -4(%ebp)
fstps   -8(%ebp)
movss   -8(%ebp), %xmm0
cvttss2si   %xmm0, %eax

和 convertToUnsigned

fildl   8(%ebp)
movl    MS_PER_SECOND, %eax
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fdivrp  %st, %st(1)
fstps   -20(%ebp)
call    clkRate
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fmuls   -20(%ebp)
fnstcw  -2(%ebp)
movzwl  -2(%ebp), %eax
movb    $12, %ah
movw    %ax, -4(%ebp)
fldcw   -4(%ebp)
fistpll -16(%ebp)
fldcw   -2(%ebp)
movl    -16(%ebp), %eax
movl    -12(%ebp), %edx

最佳答案

0.01,即 10/1000 的数学结果,不能精确地用二进制表示。对于中间浮点结果,编译器可能使用比类型(此处为 float)所需的精度更高的精度。在 C99 中,这是允许的,以精确定义的方式,只要编译器将 FLT_EVAL_METHOD 定义为 1 或 2。一些非 c99 编译器也让中间结果具有超额精度,这次没有明确的定义何时可以或不可以进行舍入。

在二进制中,0.01 的最接近表示可能在一个精度上高于 0.01,在另一个精度上低于 0.01。这将解释 19 与你的编译器和 20 与 ideone。

遵守 C99 允许超精度规则的编译器没有理由为 ticksIntticksUint 生成不同的值。但是,不遵守这些规则的编译器可能会生成导致这种情况发生的代码。

在命令行选项中添加 -std=c99 使 GCC 遵守 C99 标准关于浮点表达式超精度的规定。如果没有这个选项,GCC 关于超精度的行为(当必须有超精度时,即,当为 387 FPU 生成代码时)是非常“随意的”:结果保存在 80 位寄存器中并溢出到 64-或堆栈上的 32 位插槽,编译器一时兴起,没有程序员的干预,导致不可预测的、不稳定的结果。

这可以完全解释您在家编译时观察到的情况:由于某些深不可测的原因,该值直接从 80 位寄存器转换为 int,但已经从 80 位寄存器中消失了当转换为 unsigned int 时,到 32 位槽。

如果这是正确的解释,您的解决方案是:

  • 不生成 387 代码:使用 GCC 选项 -msse2 -mfpmath=sse;

  • 使用 -std=c99,在最近的 GCC 中,可以合理解释“超精度”的含义,从而使浮点代码可预测;

    <
  • long double 类型进行所有计算。

请参阅 this answer 的“This Said”部分了解更多详情。

关于c - float 到有符号整数的转换与 float 到无符号整数的转换不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23567965/

相关文章:

c - 如何检查 DES key 的奇偶校验?

linux 元邮件包 - 如何构建

c - Linux : direct access to the hard-disk in C

java - 使用 Thymeleaf 在 EL 表达式中进行类型转换

c++ - 将指针转换为 float ?

c# - 转换类型不起作用

java - 理解二进制补码中的十六进制数到十进制数 - Java

c - Swift 3 中的 GLFW 回调签名

c - "error: too few arguments to function"

java - Short[] 到 byte[] 给出的原始数据是乱序的