下面的宏
#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 允许超精度规则的编译器没有理由为 ticksInt
和 ticksUint
生成不同的值。但是,不遵守这些规则的编译器可能会生成导致这种情况发生的代码。
在命令行选项中添加 -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/