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 может оказаться меньше квадратного корня, даже меньше наибольшего целого, которое меньше квадратного корня.

Вы можете избежать всех этих проблем, выполнив вычисление целочисленного квадратного корня.

Другие вопросы по тегам