Преобразование типа с printf операторами под MAC OSX и Linux

У меня есть некоторая часть кода, который ведет себя по-другому под MAC OSX и Linux (Ubuntu, Fedora...). Это расценивает преобразование типа в арифметических операциях в printf операторах. Код компилируется с gcc/g ++.

Следующее

#include <stdio.h>
int main () {
  float days = (float) (153*86400) / 86400.0;
  printf ("%f\n", days);
  float foo = days / 30.6;
  printf ("%d\n", (int) foo);
  printf ("%d\n", (int) (days / 30.6));
  return 0;
}

генерирует на Linux

153.000000
5
4

и на MAC OSX

153.000000
5
5

Почему?

К моему удивлению это здесь работает и над MAC OSX и над Linux

printf ("%d\n", (int) (((float)(153 * 86400) / 86400.0) / 30.6));
printf ("%d\n", (int) (153 / 30.6));
printf ("%.16f\n",    (153 / 30.6));

Почему? У меня нет подсказки вообще. СПАСИБО.

5
задан Jonathan Leffler 1 January 2010 в 21:45
поделиться

10 ответов

попробуйте это:

#include <stdio.h>
int main () {
  float days = (float) (153*86400) / 86400.0;
  printf ("%f\n", days);
  float foo = days / 30.6;
  printf ("%d\n", (int) foo);
  printf ("%d\n", (int) (days / 30.6));
  printf ("%d\n", (int) (float)(days / 30.6));
  return 0;
}

Заметили, что происходит? Двойное преобразование в плавающее является виновником. Помните, что float всегда конвертируется в double в функции varargs. Хотя я не уверен, почему маки могут отличаться. Лучшая (или худшая) реализация арифметики IEEE?

.
5
ответ дан 18 December 2019 в 07:54
поделиться

Я ожидаю, что ответ каким-то образом связан с 32-битным присваиванием переменной float, которое затем преобразуется в double перед выводом на печать, оставляя меньше значащих битов, чем можно было бы ожидать, если бы вы передали полный double - как во втором выражении. Становится сложным иметь дело с арифметикой с плавающей точкой. Мне до сих пор нравится цитата из классической книги Кернигана и Пайка "Элементы стиля программирования", где говорится:

Один мудрый программист однажды сказал: "Числа с плавающей точкой - это как маленькие кучи песка; каждый раз, когда ты двигаешь одну из них, ты теряешь немного песка и подбираешь немного грязи"

Это иллюстрация к точке. Она также показывает, почему десятичная арифметика с плавающей точкой, как в пересмотренном стандарте IEEE 754, является хорошей идеей.

.
4
ответ дан 18 December 2019 в 07:54
поделиться

Для начала прочитайте Что должен знать каждый компьютерный специалист по арифметике с плавающей точкой .

Поскольку 30.6 не точно отображается в IEEE 754 с плавающей точкой, точные результаты, которые вы получите, не будут точно корректными. Поэтому перед приведением к целому числу вы можете получить чуть меньше 5.0, а затем целое число округляется до 4.

Точный результат, который вы получите, зависит от множества факторов, например, от того, как вычисляются промежуточные результаты. Ваш компилятор может генерировать код для использования стека с плавающей точкой x87, который использует 80-битные регистры для промежуточных вычислений. В качестве альтернативы можно использовать SSE2 (по умолчанию на Mac OS X), который использует 128-битные векторные регистры, разбитые либо на 4x32-битные, либо на 2x64-битные. Проверьте на ассемблере компилятора тип используемых операций с плавающей точкой. Смотрите на этой странице список опций командной строки GCC, которые можно использовать для управления типом используемых инструкций с плавающей точкой, в частности, опцию -mfpmath.

.
3
ответ дан 18 December 2019 в 07:54
поделиться

Ваше значение для дней является вычислением с плавающей точкой, которое с бесконечной точностью выдаст точно 5.0.

Если, с вашей ограниченной точностью, вы получите результат, который немного меньше , чем 5.0, то ваше приведение к int выдаст int меньше 5.

Проблема здесь заключается в неправильном использовании арифметики с плавающей точкой. Скорее всего, вам следует произвести округление.

Причина в том, что вы видите расхождение либо в том, что процессор работает с плавающей точкой, либо (скорее всего) в том, что оптимизаторы ваших компиляторов различаются в настройках того, какие вычисления выполняются во время выполнения и в каком порядке.

.
3
ответ дан 18 December 2019 в 07:54
поделиться

Hmmmmmmm... Я подозреваю, что есть некоторая разница из-за того, что машина Linux - 32-битная (я прав?) и Mac - 64-битная машина. На моей 64-битной Linux машине я получаю второй набор результатов.

1
ответ дан 18 December 2019 в 07:54
поделиться

Одно вычисление - как float и одно - как double, а в Linux они должны быть округлены по-разному. Почему я не знаю такого поведения MacOSX, особенно потому, что вы не утруждаетесь уточнением чего-либо о компьютере MacOSX. Это настоящий Mac? PPC или Intel?

Никогда, никогда не полагайтесь на вычисления с плавающей точкой, чтобы выйти именно так, как вы хотите. Всегда округляйтесь до int, никогда не усекайтесь.

1
ответ дан 18 December 2019 в 07:54
поделиться

Оба выполняются на одном процессоре? Думаю, это связано с эндианностью платформы, а не операционной системы. Также попробуйте (int) ((float) (days / 30.6)) вместо (int) (days / 30.6).

Другое, на что стоит обратить внимание, это то, что версии компилятора одинаковые.

Сомневаюсь, что это связано с printf, попробуйте так:

#include <iostream>
int main () {
  float days = (float) (153*86400) / 86400.0;
  std::cout << days << std::endl;
  float foo = days / 30.6;
  std::cout << (int) foo << std::endl;
  std::cout << (int) (days / 30.6) << std::endl;
  return 0;
}

И напишите результат в комментариях, пожалуйста.

.
1
ответ дан 18 December 2019 в 07:54
поделиться

Вот пример кода, который должен иллюстрировать, что ват происходит немного лучше, чем исходный:

#include <stdio.h>

int main(void)
{
    volatile double d_30_6 = 30.6;
    volatile float f_30_6 = 30.6;

    printf("%.16f\n", 153 / 30.6);   // compile-time
    printf("%.16f\n", 153 / d_30_6); // double precision
    printf("%.16f\n", 153 / f_30_6); // single precision

    return 0;
}

Переменные volatile заставляют компилятор не оптимизировать вычисления вне зависимости от уровня оптимизации...

1
ответ дан 18 December 2019 в 07:54
поделиться

Я начинаю задаваться вопросом о вопросах представления с плавающей точкой. Я не верю, что 30.6 имеет точное представление IEEE. Может быть, тебе "везёт" с проблемами округления.

Другая возможность - это другая обработка компилятором этой строки:

float days = (float) (153*86400) / 86400.0;

где как человек я вижу, что секунды в день бит отменяется, но компилятор может упустить шанс сделать постоянное сгибание, если он рассматривает одно как находящееся в целочисленном контексте, а другое - как находящееся в последующем контексте с плавающей точкой. Любые стандартные гуру хотят взвешивать наличие или отсутствие точек следования?

.
1
ответ дан 18 December 2019 в 07:54
поделиться

Числа с плавающей точкой доказывают существование Бога путем отрицания, так как они, скорее всего, являются делом рук дьявола...


Через день после долгого дня бега Вселенной Бог и Сатана собрались за пивом и начали вспоминать друг друга. "Эй, помнишь, что мы натянули на того парня Иова?" сказал Бог.

"Да, - ответил Сатана, - это были те дни, да?" Много посягательств и проклятий на вечную погибель..."

"Не говоря уже обо всех пророчествах, язвах и прочем", - сказал Бог. "Да - это были те дни". Бог вздохнул. "Скажи, я знаю, что прошло много времени, но как ты относишься к тому, чтобы сделать что-то подобное снова?"

"Забавно, что ты упомянул об этом", - гладко сказал Сатана. "Ты знаешь, я всегда интересовался технологиями..."

"Конечно", сказал Бог, "много возможностей заставить людей продать свои души за еще один раунд финансирования, а?" Он хихикал. "Ну, что у тебя на уме?"

"Ну", - сказал Сатана, - "Я положил на это своих лучших демонов. Они работали, как дьявол, чтобы придумать что-то новое, и я думаю, они это поняли. Это то, что мы называем "плавающей точкой"..."

0
ответ дан 18 December 2019 в 07:54
поделиться