Pelles C и GCC дают разные результаты с этим тестом на первичность C
Я компилирую этот код с помощью этих компиляторов. Для числа я пишу 18446744073709551615 (2^64-1). В исполняемом файле Pelles написано "18446744073709551615 - простое число", а в исполняемом файле GCC - "18446744073709551615 - не простое число". Почему результаты разные?
#include <stdio.h>
#include <math.h>
int main(void)
{
unsigned long long number;
printf("number: ");
scanf("%llu",&number);
unsigned long trsq=truncl(sqrtl(number));
char s=1;
for(unsigned long i=2;i<=trsq;i++) {
if (number%i==0) {
s=0;
break;
}
}
if (s==1) {
printf("%llu is prime\n",number);
} else {
printf("%llu isn't prime\n",number);
}
return 0;
}
Редактировать:
Я проверил и gcc дал 12, гранулы могли бы дать 8 для sizeof(long double).
1 ответ
В определении языка Си многое не указано, включая размер числовых типов и то, что происходит в случае переполнения или потери точности. Таким образом, часто можно получить разные результаты с разными реализациями (разные компиляторы, разное оборудование, разные операционные системы), когда вы не проверяете диапазоны числовых типов или когда вы используете числа с плавающей запятой.
Учитывая информацию, предоставленную this. в двух комментариях приведено правдоподобное объяснение того, что происходит. У меня нет Pelles C, чтобы проверить.
Пеллес предупреждает:
предупреждение № 2215: преобразование из "unsigned long long int" в "long double"; возможная потеря данных. предупреждение № 2215: преобразование из long long в unsigned long int; возможная потеря данных
Гипотеза № 1: математическое значение 2^64-1 не может быть представлено точно в long double
(это вероятно, так как 2^64-1 требует 64 бита мантиссы, и лишь немногие реализации имеют столько). Он округляется до 2^64, что может быть представлено точно.
Значение number
2^64-1, и это unsigned long long
, Так как функция sqrtl
ожидает long double
аргумент, значение преобразуется в этот тип. Учитывая гипотезу № 1, sqrtl
получает значение 2^64. Результат, следовательно, 2^32. Поскольку это целое число, truncl
возвращает то же значение.
Гипотеза № 2: unsigned long
это 32-битный тип (это в значительной степени норма для 32-битных машин, а также в 64-битных версиях Windows, по крайней мере с компилятором Microsoft).
Если unsigned long
32-битный тип, значение 2 ^ 32 переполняет его. То, что происходит в случае переполнения при преобразовании значения с плавающей запятой в целочисленное значение, не определяется стандартом C, компиляторы могут делать все, что захотят.
Гипотеза № 3. В Pelles C, когда значение с плавающей запятой преобразуется в целочисленный тип, оно переносится по модулю на размер типа, как это происходит при преобразовании в меньший целочисленный тип.
В предположении № 3, пытаясь присвоить значение 2 ^ 32 trsq
, который имеет тип unsigned long
и 32-битный, устанавливает его в 0. Таким образом trsq
имеет значение 0, то for
цикл запускается 0 раз, и программа ошибочно сообщает, что число является простым.
Легко исправить, чтобы изменить trsq
быть unsigned long long
,
Обратите внимание, что ваша программа может сообщать о некоторых числах как простых, если их наибольший простой множитель очень близок к их квадратному корню (например, если число является квадратом простого числа), потому что преобразование number
до значения с плавающей точкой может округлить его вниз, так trsq
может оказаться меньше квадратного корня, даже меньше наибольшего целого, которое меньше квадратного корня.
Вы можете избежать всех этих проблем, выполнив вычисление целочисленного квадратного корня.