большое число неверно в C, хотя оно может быть правильно представлено от 0 до 18,446,744,073,709,551,615

#c

#c

Вопрос:

Вот мой код.

 // Fibonacci function in C
#include <stdio.h>

unsigned long long int fib(int n);

unsigned long long int main()
{
    unsigned long long int n,resu<
    printf("Enter n for a_n:");
    scanf("%d",amp;n);
    result=fib(n);
    printf("a_%d is %d",n,result);
    return 0;
}

unsigned long long int fib(int n)
{
    if(n==1)
    {
        return 1;
    }
    else if(n==2)
    {
        return 1;
    }
    else
    {
        return fib(n-1) fib(n-2);
    }
}
  

Когда я ввожу 48, на выходе получается 512 559 680.
Но в python это 4,807,526,976.
Я думаю, что unsigned long достаточно долго, чтобы обозначить результат.

Комментарии:

1. Компилятор должен выдавать некоторые предупреждения об использовании %d вами формата при печати unsigned long long int .

2. scanf("%d", amp;n); — вы понимаете n это unsigned long long , верно? это неподходящий спецификатор формата для scanf извлечения такой вещи из stdin. Вы также должны получать предупреждение о fib(n) том, откуда вы передаете что-то, чего не int ожидает функция int . Короткая версия: увеличьте свои предупреждения до педантичного уровня и рассмотрите их ВСЕ как ошибки, а затем исправьте их; не игнорируйте и не скрывайте их.

3. Кстати, fib(48) == 4,807,526,976 , а не 4,294,967,295

4. @ForceBru Спасибо. Извините за опечатку. Я исправил его до правильного значения.

Ответ №1:

Результат правильный, но вы печатаете только его часть из-за %d формата здесь:

 printf("a_%d is %d",n,result);
                ^^ here
  

Правильный результат — 4807526976 или 0x11e8d0a40 в шестнадцатеричном формате. Вы получили 512559680 или 0x1e8d0a40 в шестнадцатеричном формате. Сравните их бок о бок:

 correct: 0x011e8d0a40
you got: 0x001e8d0a40
  

То есть вы напечатали последние 4 байта значения, потому %d что формат интерпретировал число как обычное целое число, ширина которого на вашем компьютере составляет 4 байта.

Чтобы исправить это, включите все предупреждения компилятора и посмотрите, какой формат он предлагает вместо %d (вероятно %llu ).


Пользовательский ввод по своей сути динамичен, а строки формата (например "a_%d is %d" ) — это просто строки, поэтому статически типизированный язык (например, C или OCaml) не может определить, является ли какой-либо спецификатор формата (например %d ) неправильным — как указано в спецификаторе signed int , но (статический!) Тип переменной, которую выпечать есть unsigned long long int . В результате я думаю, что форматирование с использованием строк формата обречено с самого начала, потому что статическая проверка типов автоматически становится динамической, обеспечиваемой внутренним механизмом printf , и вы теряете все гарантии статической проверки типов.

Похоже, что единственный способ решить эту проблему — вставить «хак» в компилятор, который будет проверять форматирование строк. Вот как ваш компилятор C со всеми включенными предупреждениями может обнаруживать ошибки:

 test.c:8:16: warning: format specifies type 'int *' but the argument has type
      'unsigned long long *' [-Wformat]
    scanf("%d",amp;n);
           ~~  ^~
           %lld
test.c:9:25: warning: format specifies type 'int' but the argument has type
      'unsigned long long' [-Wformat]
    printf("a_%d is %d",n,result);
              ~~        ^
              %llu
test.c:9:27: warning: format specifies type 'int' but the argument has type
      'unsigned long long' [-Wformat]
    printf("a_%d is %d",n,result);
                    ~~    ^~~~~~
                    %llu
  

Аналогично, в OCaml Printf.printf имеет этот странный тип:

 val printf : ('a, out_channel, unit) format -> 'a
  

… даже если в вашем коде оно вызывается со строкой формата:

 Printf.printf "a_%d is %d" 4 (fib 4)
  

Аналогично, в Rust есть этот странный тип std::fmt::Arguments , который представляет строку формата и не может быть создан во время выполнения. Итак, этот тип фактически используется для форматирования, но в коде вы пишете строки формата:

 println!("a_{} = {}", 48, 0x11e8d0a40u64)
  

Комментарии:

1. мой компилятор mingw64 . Как я могу взломать, чтобы получать предупреждения?

2. @kile, «хак» уже есть. Вы должны просто включить все предупреждения с -Wall помощью опции или что-то в этом роде. На самом деле это не хак, это скорее особенность компилятора — я называю это так, потому что, насколько я понимаю, обычный C не должен даже улавливать такие ошибки в строках формата, потому что это просто строки, и их содержимое не проверяется по типу.