Double и float могут иметь одинаковую точность?

Мне нужно реализовать программу, которая вычисляет машинный эпсилон для float и double.
Я написал эти функции:

int feps(){
    //machine epsilon for float
    float tmp=1;
    int d=0;
    while(1+(tmp=tmp/2)>1.0f)d++;
    return d;
}

int deps(){
    //machine epsilon for double
    double tmp=1;
    int d=0;
    while(1+(tmp=tmp/2)>1.0)d++;
    return d;
}


Примечание:
64-битный машинный компилятор gcc 4.9.1 target: x86_64-linux-gnu
32-битный машинный компилятор gcc 4.8.2 target: i686-linux-gnu

Я пробовал это на 64-битной машине и получил следующие результаты:
Float 23
Double 52
Как я и ожидал, затем я попробовал это на 32-битная виртуальная машина, и результаты были очень странными:
Float 63
Double 63
Я также пытался скомпилировать свою программу с помощью -mpc32, - mpc64 и -mpc80, и вот результат:
-mpc32 Float 23, Double 23
-mpc64 Float 52, Double 52
-mpc80 Float 63, Double 63
Я также пробовал этот вариант компиляции на 64-битной машине, но результаты всегда были 23 и 52.
Я знаю это float с одинарной точностью, а double с двойной точностью, но возможно, что компилятор моей 32-битной виртуальной машины использует binary80 как для чисел с плавающей запятой, так и для чисел двойной точности"?

Я совершенно уверен, что мой код правильный, поэтому я думаю, что проблема связана с компилятором или чем-то более тонким.
Я потратил весь день на поиск информации о плавающей запятой и кое-что прочитал о MMX / SSE инструкция, но я мало что понял, и кое-что о FPU x87, которое может создать некоторые проблемы.


Обновление:
Я хочу поблагодарить всех, кто мне помог, мне удалось получить реальное значение эпсилон для float и double на 32-битной виртуальной машине, это код:

int feps(){
    float tmp=1;
    int d=0;
    float tmp2=1;
    do{
        tmp2=1+(tmp=tmp/2);
        d++;
    }while(tmp2>1.0f);
    return d-1;
}

int deps(){
    double tmp=1;
    int d=0;
    double tmp2=1;
    do{
        tmp2=1+(tmp=tmp/2);
        d++;
    }while(tmp2>1.0);
    return d-1;
}

как вы можете видеть, нам нужно поместить промежуточный результат в переменную, чтобы мы могли предотвратить оценку 1+ (tmp = tmp / 2) как long double в тесте цикла. < br>


person LolloFake    schedule 15.03.2015    source источник
comment
Это зависит от компилятора и реализации. Прочтите floating-point-gui.de   -  person Basile Starynkevitch    schedule 16.03.2015
comment
возможно, это как-то связано с обработкой вашей виртуальной машиной инструкций с плавающей запятой   -  person M.M    schedule 16.03.2015
comment
На вашем компьютере x86 FLT_EVAL_METHOD равен 2, но на x86_64 это 0 Описание см. На связанном сайте.   -  person cremno    schedule 16.03.2015
comment
Попробуйте printf("%zu\n",sizeof(float));. Это не скажет вам, что такое эпсилон, но расскажет, сколько байтов у float на виртуальной машине.   -  person user3386109    schedule 16.03.2015
comment
@cremno назначение должно заставить результат быть преобразован в float, поэтому я не совсем понимаю, как это будет полное объяснение   -  person M.M    schedule 16.03.2015
comment
Попробуйте распечатать константы FLT_EPSILON и DBL_EPSILON, определенные в <float.h>, на всех этих платформах. float и double могут быть одного и того же типа.   -  person chqrlie    schedule 16.03.2015
comment
Я пробовал printf с% zu, и в каждом случае результат составляет 4 байта для float и 8 байтов для double.   -  person LolloFake    schedule 16.03.2015
comment
Ваш стиль отступов вводит в заблуждение ;-)   -  person chqrlie    schedule 16.03.2015
comment
@LolloFake: Как вы компилируете свой код? Какие варианты вы используете?   -  person cremno    schedule 16.03.2015
comment
@cremno gcc es3.c для 64-битной машины, для 32-битной машины я пробовал gcc -mpcXX es3.c, где XX - 32,64 или 80. Я также напечатал FLT_EPSILON и DBL_EPSILON, значения разные.   -  person LolloFake    schedule 16.03.2015
comment
Что может помочь, так это объявление tmp как volatile. Это должно предотвратить кэширование переменной в регистре и избежать проблем с избыточной точностью при вычислениях на FPU x87.   -  person njuffa    schedule 16.03.2015
comment
Вас могут ввести в заблуждение значения, хранящиеся в регистре с плавающей запятой.   -  person David Schwartz    schedule 16.03.2015


Ответы (1)


На 32-битной платформе ограничения ABI упрощают использование исторических регистров с плавающей запятой; как следствие, компилятор определяет FLT_EVAL_METHOD как 2. Вот как вы получите:

Float 63
Double 63

Короче говоря, когда FLT_EVAL_METHOD определяется компилятором равным 2, как в случае на вашей 32-разрядной виртуальной машине, выражения и константы с плавающей запятой оцениваются с точностью long double, независимо от их типов, и только присвоения lvalue и явное приведение типов округляют вычисленные значения от long double до фактического типа с плавающей запятой . На верхнем уровне выражения 1+(tmp=tmp/2) таких конструкций нет, поэтому сложение оценивается с точностью до long double.

Это два сообщения series показывает несколько примеров, на которых FLT_EVAL_METHOD имеет значение в дополнение к вашему. Поведение GCC детерминировано и согласно объяснению, изложенному Дж. С. Майерсом. Поведение Clang недетерминировано (тогда и сейчас), и разработчики мало заинтересованы в улучшении этого режима своего компилятора.

person Pascal Cuoq    schedule 16.03.2015
comment
tmp - это lvalue, поэтому tmp = tmp/2 - это присвоение lvalue, которое должно вызывать округление. Результат выражения-присваивания имеет тип левого операнда (а не исходный результат правой части) - person M.M; 18.03.2015
comment
@MattMcNabb Да, но float имеет достаточный диапазон для представления машинного эпсилона long double (и это действительно так в программе OP). Важно то, что + в 1+… - это сложение с двойным расширением между двойным расширением 1 и преобразованием в двойное расширение значения tmp. - person Pascal Cuoq; 18.03.2015
comment
@MattMcNabb Я перефразировал предложение, о котором вы говорили. - person Pascal Cuoq; 18.03.2015
comment
не могли бы вы объяснить более подробно (возможно, со стандартными ссылками), почему задание верхнего уровня отличается от другого задания? - person M.M; 18.03.2015
comment
@MattMcNabb в 1.0f + (y = e), правило, согласно которому присваивания и преобразования удаляют дополнительную точность, когда FLT_EVAL_METHOD > 0 применяется только к y = e. Добавление по-прежнему выполняется с дополнительной точностью, подразумеваемой значением FLT_EVAL_METHOD. Это все, что я имел в виду. - person Pascal Cuoq; 18.03.2015
comment
OK. Я предполагал, что tmp/2 является основной целью этого цикла, и +1 не имеет значения, но на самом деле, возможно, + также имеет значение. - person M.M; 19.03.2015