2015-07-30 2 views
3
#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; 
} 

Выход:Почему isnormal() говорит, что это нормально, когда это не так?

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 

Я компиляции моего кода с помощью GCC (MinGW-w64) на Windows.

+0

В [ 'isnormal'] (http://en.cppreference.com/w/c/numeric/math/isnormal) определяется как * macro *, что делает ваш макрос в вашей реализации, который, кажется, не присутствует? Возможно, стоит посмотреть. – WhozCraig

+1

Просто догадаться: неверно ли преобразован макрос в аргумент типа double? –

+0

@JamesMcNellis судя по результату, это тоже будет моя интерпретация. – WhozCraig

ответ

3

Это работает как ожидалось на других платформах (здесь, например, ideone), поэтому, вероятно, это проблема с используемой версией библиотеки gcc/standard.

Наиболее вероятной причиной является то, что аргумент isnormal преобразуется в двойное.

+0

Интересно, разрешает ли это стандарт C (он говорит, что операции с плавающей запятой могут быть внутренне быть сделано в более высокой точности, чем фактический тип) –

+0

@matt: Я так не думаю. Формулировка в описании макросов классификации состоит в том, что выражение сначала сужается до его семантического типа, и есть сноска, объясняющая, почему это необходимо. – rici

0

Это, кажется, ошибка в MinGW-w64 с 32-битной целью.

Мой выход:

  • MinGW-w64 x86_64-4.9.2-win32-SEH-rt_v3-rev1: Правильно
  • MinGW-w64 i686-4.9.2-win32-карлик-rt_v4-rev2 : Некорректное
  • Cygwin i686-рс-Cygwin (GCC 4.9.2): Правильно

Глядя на заголовки MinGW-w64:

#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 $17664, %ax 
    cmpw $1024, %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 $17664, %ax 
    cmpw $1024, %ax 
    jne L6 
    fmuls LC4 
    fstps 24(%esp) 
    jmp L7 
L6: 
    fstp %st(0) 
    movl $0, %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 
+0

Я не эксперт x87, но мне кажется маловероятным, что FXAM поступит правильно. К тому моменту, когда эта инструкция выполняется, тот факт, что значение является одинарной точностью, потерян. – rici

+0

Cygwin работает правильно, но он ссылается на внешний '__fpclassifyf', и я не уверен, где исходный код для этого –

Смежные вопросы