Ваш код в порядке
Вы абсолютно правы, а ваш учитель не прав. Нет абсолютно никакой причины добавлять эту дополнительную сложность, поскольку это никак не влияет на результат. Это даже вносит ошибку. (См. ниже)
Во-первых, отдельная проверка, если n
ноль, очевидно, совершенно не нужна, и это очень легко реализовать. Если честно, я на самом деле подвергаю сомнению вашу компетентность учителя, если у него есть возражения по этому поводу. Но я думаю, что каждый может время от времени пукнуть. Тем не менее, я ДЕЙСТВИТЕЛЬНО думаю, что это while(n)
следует изменить, while(n != 0)
потому что это добавляет немного дополнительной ясности даже без затрат на дополнительную строку. Это незначительная вещь, хотя.
Второй более понятен, но он все еще не прав.
Вот что говорит стандарт C11 6.5.5.p6 :
Если частное a / b представимо, выражение (a / b) * b + a% b должно быть равно a; в противном случае поведение a / b и a% b не определено.
Сноска говорит это:
Это часто называют «усечением до нуля».
Усечение до нуля означает, что абсолютное значение для a/b
равно абсолютному значению (-a)/b
для всех a
и b
, что, в свою очередь, означает, что ваш код в порядке.
Модуло - легкая математика, но может быть нелогичным
Тем не менее, ваш учитель имеет смысл, что вы должны быть осторожны, потому что факт, что вы возводите в квадрат результат, на самом деле имеет решающее значение. Вычисление в a%b
соответствии с приведенным выше определением - простая математика, но она может пойти вразрез с вашей интуицией. Для умножения и деления результат является положительным, если операнды имеют знак равенства. Но когда дело доходит до модуля, результат имеет тот же знак, что и первый операнд. Второй операнд не влияет на знак вообще. Например, 7%3==1
но (-7)%(-3)==(-1)
.
Вот фрагмент, демонстрирующий это:
$ cat > main.c
#include <stdio.h>
void f(int a, int b)
{
printf("a: %2d b: %2d a/b: %2d a\%b: %2d (a%b)^2: %2d (a/b)*b+a%b==a: %5s\n",
a, b ,a/b, a%b, (a%b)*(a%b), (a/b)*b+a%b == a ? "true" : "false");
}
int main(void)
{
int a=7, b=3;
f(a,b);
f(-a,b);
f(a,-b);
f(-a,-b);
}
$ gcc main.c -Wall -Wextra -pedantic -std=c99
$ ./a.out
a: 7 b: 3 a/b: 2 a%b: 1 (a%b)^2: 1 (a/b)*b+a%b==a: true
a: -7 b: 3 a/b: -2 a%b: -1 (a%b)^2: 1 (a/b)*b+a%b==a: true
a: 7 b: -3 a/b: -2 a%b: 1 (a%b)^2: 1 (a/b)*b+a%b==a: true
a: -7 b: -3 a/b: 2 a%b: -1 (a%b)^2: 1 (a/b)*b+a%b==a: true
Итак, по иронии судьбы, ваш учитель доказал свою точку зрения, ошибаясь.
Код вашего учителя имеет недостатки
Да, это действительно так. Если вход - INT_MIN
И, архитектура - это дополнение к двум, И битовая комбинация, в которой бит знака равен 1, а все биты значения равны 0, НЕ является значением ловушки (использование дополнения к двум без значений ловушек очень распространено), тогда код вашего учителя приведет к неопределенному поведению на линии n = n * (-1)
. Ваш код - если даже немного - лучше, чем его. И если учесть небольшую ошибку, сделав код ненужным сложным и получив абсолютно нулевое значение, я бы сказал, что ваш код НАМНОГО лучше.
Другими словами, в компиляциях, где INT_MIN = -32768 (даже если результирующая функция не может получить ввод <-32768 или> 32767), допустимый ввод -32768 вызывает неопределенное поведение, потому что результат - (- - 32768i16) не может быть выражено как 16-битное целое число. (На самом деле, -32768, вероятно, не приведет к неправильному результату, потому что - (- - 32768i16) обычно оценивается как -32768i16, и ваша программа корректно обрабатывает отрицательные числа.) (SHRT_MIN может быть -32768 или -32767, в зависимости от компилятора.)
Но ваш учитель прямо заявил, что n
может быть в диапазоне [-10 ^ 7; 10 ^ 7]. 16-битное целое число слишком мало; вам придется использовать [как минимум] 32-разрядное целое число. Использование int
может показаться , чтобы сделать свой код сейф, за исключением того, что int
это не обязательно 32-разрядное целое число. Если вы компилируете для 16-битной архитектуры, оба ваших фрагмента кода имеют недостатки. Но ваш код все еще намного лучше, потому что этот сценарий повторно вводит ошибку с INT_MIN
упомянутой выше в его версии. Чтобы избежать этого, вы можете написать long
вместо int
, что является 32-разрядным целым числом в любой архитектуре. long
Гарантируется, что A может содержать любое значение в диапазоне [-2147483647; 2147483647]. Стандарт С11 5.2.4.2.1 LONG_MIN
часто-2147483648
но максимум (да, максимум, это отрицательное число) допустимое значение LONG_MIN
является 2147483647
.
Какие изменения я бы сделал в вашем коде?
Ваш код в порядке, так что это на самом деле не жалобы. Это больше похоже на то, что если мне действительно нужно что-то сказать о вашем коде, есть некоторые мелочи, которые могут сделать его чуть-чуть понятнее.
- Имена переменных могут быть немного лучше, но это короткая функция, которую легко понять, так что это не имеет большого значения.
- Вы можете изменить условие с
n
на n!=0
. Семантически это на 100% эквивалентно, но делает его немного понятнее.
- Переместите объявление
c
(которое я переименовал digit
) в цикл while, поскольку оно используется только там.
- Измените тип аргумента так, чтобы
long
он мог обрабатывать весь входной набор.
int sum_of_digits_squared(long n)
{
long sum = 0;
while (n != 0) {
int digit = n % 10;
sum += (digit * digit);
n /= 10;
}
return sum;
}
На самом деле, это может немного вводить в заблуждение, потому что - как упоминалось выше - переменная digit
может принимать отрицательное значение, но сама по себе цифра никогда не бывает ни положительной, ни отрицательной. Есть несколько способов обойти это, но это ДЕЙСТВИТЕЛЬНО придирчиво, и мне было бы наплевать на такие мелкие детали. Особенно отдельная функция для последней цифры заходит слишком далеко. По иронии судьбы, это одна из вещей, которую код ваших учителей на самом деле решает.
- Перейдите
sum += (digit * digit)
на sum += ((n%10)*(n%10))
и пропустите переменную digit
полностью.
- Измените знак,
digit
если отрицательный. Но я бы настоятельно рекомендовал не делать код более сложным, просто чтобы иметь смысл имя переменной. Это ОЧЕНЬ сильный запах кода.
- Создайте отдельную функцию, которая извлекает последнюю цифру.
int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Это полезно, если вы хотите использовать эту функцию где-то еще.
- Просто назовите его так,
c
как вы это делаете изначально. Это имя переменной не дает никакой полезной информации, но, с другой стороны, оно также не вводит в заблуждение.
Но, честно говоря, на этом этапе вы должны перейти к более важной работе. :)
n = n * (-1)
это нелепый способ писатьn = -n
; Только академик мог бы подумать об этом. Не говоря уже о добавлении лишних скобок.