В конце обновления!
У меня есть небольшая история.
Я хотел вычислить машинный эпсилон (самый большой эпсилон> 0, удовлетворяющий условию 1.0 + эпсилон = 1.0) в программе на C, скомпилированной MS Visual Studio 2008 (работающей в Windows 7 на 64-битном ПК). Поскольку я знаю, что double и float имеют разную точность, я хотел увидеть ответ для обоих. По этой причине я построил следующую программу:
#include <stdio.h>
typedef double float_type;
int main()
{
float_type eps = 1.0;
while ((float_type) 1.0 + eps / (float_type) 2.0 > (float_type) 1.0)
eps = eps / (float_type) 2.0;
printf("%g\n", eps);
return 0;
}
Я был очень удивлен, увидев, что он дал одинаковый ответ для обоих типов double и float: 2.22045e-16. Это было странно, поскольку double занимает вдвое больше памяти, чем float, и должен быть более точным. После этого я заглянул в Википедию и взял оттуда пример кода:
#include <stdio.h>
int main(int argc, char **argv)
{
float machEps = 1.0f;
do {
machEps /= 2.0f;
} while ((float)(1.0 + (machEps/2.0)) != 1.0);
printf( "\nCalculated Machine epsilon: %G\n", machEps );
return 0;
}
Я был еще более удивлен, когда он работал правильно! После некоторых попыток понять принципиальное различие между двумя программами я выяснил следующий факт: моя программа (первая) начинает давать правильный ответ для float (1.19209e-07), если я изменяю условие цикла на
while ((float_type) (1.0 + eps / (float_type) 2.0) > (float_type) 1.0)
Ну, это тайна, которую вы бы сказали. О, настоящая тайна заключается в следующем. Сравните:
while ((float) (1.0 + eps / 2.0f) > 1.0f)
, который дал правильный ответ (1.19209e-07) и
while ((float) (1.0f + eps / 2.0f) > 1.0f)
, который дал ответ, неправильный для числа с плавающей запятой и правильный для двойного (2.22045e-16).
На самом деле это совершенно неправильно, результат должен был быть противоположным. Это связано с тем, что по умолчанию такие константы, как 1.0, обрабатываются компилятором как двойные (в соответствии со стандартом), а если они присутствуют в арифметическом выражении, то все остальные операнды переводятся в двойные. И наоборот, когда я пишу 1.0f, все операнды являются плавающими, и никакого продвижения не должно происходить. И все же я получаю совершенно другой результат.
После всех этих тестов я попытался скомпилировать и запустить программы на Linux с помощью gcc. Не удивительно, это напечатало точно, что я ожидал (правильные ответы). Итак, теперь я думаю, что это ошибка Visual Studio. Чтобы вы все рассмеялись (если есть люди, которые до этого момента читали мой пост, что сомнительно ^ _ ^), я приведу еще одно сравнение:
float c = 1.0;
while ((float) (c + eps / 2.0f) > 1.0f)
Это не работает должным образом в VS, но ...
const float c = 1.0;
дает правильный ответ 1.19209e-07.
Пожалуйста, кто-нибудь, скажите мне, если я прав, что причиной проблемы является глючный компилятор VS 2008 (можете ли вы подтвердить ошибку на ваших машинах?). Я был бы также признателен, если бы вы протестировали корпус в более новой версии: MS VS 2010. Спасибо.
UPDATE.
С MS Visual Studio 2013 первая упомянутая мной программа работает без неожиданных результатов - она дает соответствующие ответы для float и double. Я проверил это на всех моделях с плавающей запятой (точных, строгих и быстрых), и ничего не изменилось. Так что действительно кажется, что в этом случае VS 2008 глючил.