为什么 isnormal() 说一个值是正常的,而实际上不是?
Why is isnormal() saying a value is normal when it isn't?
#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <math.h>
void PrintBytes( const float value )
{
const char* const byte = ( const char* )&value ;
for( size_t i = 0 ; i < sizeof( value ) ; i++ )
{
printf( "%02hhx" , byte[i] );
}
}
int main(void)
{
float value = FLT_MIN;
while( 1 )
{
printf( "%e %d " , value , isnormal( value ) );
PrintBytes( value );
puts( "" );
if( !isnormal( value ) )
{
break;
}
value /= 2.0F;
}
return 0;
}
输出为:
1.175494e-038 1 00008000
5.877472e-039 1 00004000
2.938736e-039 1 00002000
1.469368e-039 1 00001000
7.346840e-040 1 00000800
3.673420e-040 1 00000400
1.836710e-040 1 00000200
9.183550e-041 1 00000100
4.591775e-041 1 00800000
2.295887e-041 1 00400000
1.147944e-041 1 00200000
5.739719e-042 1 00100000
2.869859e-042 1 00080000
1.434930e-042 1 00040000
7.174648e-043 1 00020000
3.587324e-043 1 00010000
1.793662e-043 1 80000000
8.968310e-044 1 40000000
4.484155e-044 1 20000000
2.242078e-044 1 10000000
1.121039e-044 1 08000000
5.605194e-045 1 04000000
2.802597e-045 1 02000000
1.401298e-045 1 01000000
0.000000e+000 0 00000000
显然第二个值 5.877472e-039
是次正规的,因为它的指数变为 0,00004000
。
Ideone 产生正确的结果:
1.175494e-38 1 00008000
5.877472e-39 0 00004000
我正在 Windows 上使用 gcc (MinGW-w64) 编译我的代码。
这在其他平台(例如,此处,在 ideone 上)按预期工作,因此这可能是您使用的 gcc/standard 库版本的问题。
最可能的原因是 isnormal
的参数正在转换为双精度数。
这似乎是具有 32 位目标的 MinGW-w64 中的错误。
我的输出:
- MinGW-w64 x86_64-4.9.2-win32-seh-rt_v3-rev1:正确
- MinGW-w64 i686-4.9.2-win32-dwarf-rt_v4-rev2:不正确
- Cygwin i686-pc-cygwin (gcc 4.9.2): 正确
查看 MinGW-W64 headers:
#define isnormal(x) (fpclassify(x) == FP_NORMAL)
那么我们有:
#define fpclassify(x) (sizeof (x) == sizeof (float) ? __fpclassifyf (x) \
: sizeof (x) == sizeof (double) ? __fpclassify (x) \
: __fpclassifyl (x))
__fpclassifyf
函数实现为:
__CRT_INLINE int __cdecl __fpclassifyf (float x) {
#ifdef __x86_64__
__mingw_flt_type_t hlp;
hlp.x = x;
hlp.val &= 0x7fffffff;
if (hlp.val == 0)
return FP_ZERO;
if (hlp.val < 0x800000)
return FP_SUBNORMAL;
if (hlp.val >= 0x7f800000)
return (hlp.val > 0x7f800000 ? FP_NAN : FP_INFINITE);
return FP_NORMAL;
#else
unsigned short sw;
__asm__ __volatile__ ("fxam; fstsw %%ax;" : "=a" (sw): "t" (x));
return sw & (FP_NAN | FP_NORMAL | FP_ZERO );
#endif
}
因为 x64 版本似乎工作但 i686 版本不工作,我想我们应该归咎于上面的 fxam; fstsw
行。这个问题我开了个new thread;到目前为止,已经回答 fxam
确实测试了一个扩展到 80 位精度的版本。
在损坏版本中-O1
处生成的程序集是:
call ___main
flds LC1
fstps 24(%esp)
L7:
flds 24(%esp)
/APP
# 484 "F:/Prog/mingw-w64/i686-4.9.2-win32-dwarf-rt_v4-rev2/mingw32/i686-w64-mingw32/include/math.h" 1
fxam; fstsw %ax;
# 0 "" 2
/NO_APP
andw 664, %ax
cmpw 24, %ax
sete %al
movzbl %al, %eax
movl %eax, 12(%esp)
fstpl 4(%esp)
movl $LC2, (%esp)
call _printf
flds 24(%esp)
fstps (%esp)
call _PrintBytes
movl $LC3, (%esp)
call _puts
flds 24(%esp)
/APP
# 484 "F:/Prog/mingw-w64/i686-4.9.2-win32-dwarf-rt_v4-rev2/mingw32/i686-w64-mingw32/include/math.h" 1
fxam; fstsw %ax;
# 0 "" 2
/NO_APP
andw 664, %ax
cmpw 24, %ax
jne L6
fmuls LC4
fstps 24(%esp)
jmp L7
L6:
fstp %st(0)
movl [=13=], %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE41:
.section .rdata,"dr"
.align 4
LC1:
.long 8388608
.align 4
LC4:
.long 1056964608
#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <math.h>
void PrintBytes( const float value )
{
const char* const byte = ( const char* )&value ;
for( size_t i = 0 ; i < sizeof( value ) ; i++ )
{
printf( "%02hhx" , byte[i] );
}
}
int main(void)
{
float value = FLT_MIN;
while( 1 )
{
printf( "%e %d " , value , isnormal( value ) );
PrintBytes( value );
puts( "" );
if( !isnormal( value ) )
{
break;
}
value /= 2.0F;
}
return 0;
}
输出为:
1.175494e-038 1 00008000
5.877472e-039 1 00004000
2.938736e-039 1 00002000
1.469368e-039 1 00001000
7.346840e-040 1 00000800
3.673420e-040 1 00000400
1.836710e-040 1 00000200
9.183550e-041 1 00000100
4.591775e-041 1 00800000
2.295887e-041 1 00400000
1.147944e-041 1 00200000
5.739719e-042 1 00100000
2.869859e-042 1 00080000
1.434930e-042 1 00040000
7.174648e-043 1 00020000
3.587324e-043 1 00010000
1.793662e-043 1 80000000
8.968310e-044 1 40000000
4.484155e-044 1 20000000
2.242078e-044 1 10000000
1.121039e-044 1 08000000
5.605194e-045 1 04000000
2.802597e-045 1 02000000
1.401298e-045 1 01000000
0.000000e+000 0 00000000
显然第二个值 5.877472e-039
是次正规的,因为它的指数变为 0,00004000
。
Ideone 产生正确的结果:
1.175494e-38 1 00008000
5.877472e-39 0 00004000
我正在 Windows 上使用 gcc (MinGW-w64) 编译我的代码。
这在其他平台(例如,此处,在 ideone 上)按预期工作,因此这可能是您使用的 gcc/standard 库版本的问题。
最可能的原因是 isnormal
的参数正在转换为双精度数。
这似乎是具有 32 位目标的 MinGW-w64 中的错误。
我的输出:
- MinGW-w64 x86_64-4.9.2-win32-seh-rt_v3-rev1:正确
- MinGW-w64 i686-4.9.2-win32-dwarf-rt_v4-rev2:不正确
- Cygwin i686-pc-cygwin (gcc 4.9.2): 正确
查看 MinGW-W64 headers:
#define isnormal(x) (fpclassify(x) == FP_NORMAL)
那么我们有:
#define fpclassify(x) (sizeof (x) == sizeof (float) ? __fpclassifyf (x) \
: sizeof (x) == sizeof (double) ? __fpclassify (x) \
: __fpclassifyl (x))
__fpclassifyf
函数实现为:
__CRT_INLINE int __cdecl __fpclassifyf (float x) {
#ifdef __x86_64__
__mingw_flt_type_t hlp;
hlp.x = x;
hlp.val &= 0x7fffffff;
if (hlp.val == 0)
return FP_ZERO;
if (hlp.val < 0x800000)
return FP_SUBNORMAL;
if (hlp.val >= 0x7f800000)
return (hlp.val > 0x7f800000 ? FP_NAN : FP_INFINITE);
return FP_NORMAL;
#else
unsigned short sw;
__asm__ __volatile__ ("fxam; fstsw %%ax;" : "=a" (sw): "t" (x));
return sw & (FP_NAN | FP_NORMAL | FP_ZERO );
#endif
}
因为 x64 版本似乎工作但 i686 版本不工作,我想我们应该归咎于上面的 fxam; fstsw
行。这个问题我开了个new thread;到目前为止,已经回答 fxam
确实测试了一个扩展到 80 位精度的版本。
在损坏版本中-O1
处生成的程序集是:
call ___main
flds LC1
fstps 24(%esp)
L7:
flds 24(%esp)
/APP
# 484 "F:/Prog/mingw-w64/i686-4.9.2-win32-dwarf-rt_v4-rev2/mingw32/i686-w64-mingw32/include/math.h" 1
fxam; fstsw %ax;
# 0 "" 2
/NO_APP
andw 664, %ax
cmpw 24, %ax
sete %al
movzbl %al, %eax
movl %eax, 12(%esp)
fstpl 4(%esp)
movl $LC2, (%esp)
call _printf
flds 24(%esp)
fstps (%esp)
call _PrintBytes
movl $LC3, (%esp)
call _puts
flds 24(%esp)
/APP
# 484 "F:/Prog/mingw-w64/i686-4.9.2-win32-dwarf-rt_v4-rev2/mingw32/i686-w64-mingw32/include/math.h" 1
fxam; fstsw %ax;
# 0 "" 2
/NO_APP
andw 664, %ax
cmpw 24, %ax
jne L6
fmuls LC4
fstps 24(%esp)
jmp L7
L6:
fstp %st(0)
movl [=13=], %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE41:
.section .rdata,"dr"
.align 4
LC1:
.long 8388608
.align 4
LC4:
.long 1056964608