Twój kod jest w porządku
Masz absolutną rację, a twój nauczyciel się myli. Nie ma absolutnie żadnego powodu, aby dodawać tę dodatkową złożoność, ponieważ w ogóle nie wpływa to na wynik. To nawet wprowadza błąd. (Patrz poniżej)
Po pierwsze, osobne sprawdzenie, czy n
zero jest oczywiście całkowicie niepotrzebne i bardzo łatwo to zrealizować. Szczerze mówiąc, faktycznie kwestionuję kompetencje nauczycieli, jeśli ma on zastrzeżenia. Ale chyba od czasu do czasu każdy może mieć pierdnięcie mózgu. Myślę jednak, że while(n)
należy to zmienić, while(n != 0)
ponieważ dodaje to nieco dodatkowej przejrzystości, nawet nie kosztując dodatkowej linii. To jednak drobna sprawa.
Drugi jest nieco bardziej zrozumiały, ale nadal się myli.
Tak mówi standard C11 6.5.5.p6 :
Jeżeli iloraz a / b jest reprezentatywny, wyrażenie (a / b) * b + a% b będzie równe a; w przeciwnym razie zachowanie zarówno a / b, jak i% b jest niezdefiniowane.
Przypis mówi:
Jest to często nazywane „obcięciem do zera”.
Skrócenie do zera oznacza, że wartość bezwzględna dla a/b
jest równa wartości bezwzględnej (-a)/b
dla wszystkicha
i b
, co z kolei oznacza, że kod jest w porządku.
Modulo jest łatwą matematyką, ale może być sprzeczne z intuicją
Jednak twój nauczyciel ma rację, że powinieneś być ostrożny, ponieważ fakt, że podnosisz wynik do kwadratu, jest tutaj naprawdę kluczowy. Obliczanie a%b
według powyższej definicji jest łatwą matematyką, ale może być sprzeczne z intuicją. W przypadku mnożenia i dzielenia wynik jest dodatni, jeśli operandy mają znak równości. Ale jeśli chodzi o modulo, wynik ma ten sam znak, co pierwszy operand. Drugi operand w ogóle nie wpływa na znak. Na przykład, 7%3==1
ale(-7)%(-3)==(-1)
.
Oto fragment, który to pokazuje:
$ 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
Jak na ironię, twój nauczyciel udowodnił, że się mylił.
Kod twojego nauczyciela jest wadliwy
Tak, faktycznie tak jest. Jeśli dane wejściowe to INT_MIN
ORAZ architektura jest uzupełnieniem do dwóch, ORAZ wzorzec bitów, w którym bit znaku ma wartość 1, a wszystkie bity wartości mają wartość 0, NIE jest wartością pułapki (użycie uzupełnienia do dwóch bez wartości pułapek jest bardzo częste), to kod twojego nauczyciela da niezdefiniowane zachowanie na linii n = n * (-1)
. Twój kod jest - jeśli w ogóle tak nieznacznie - lepszy niż jego. Biorąc pod uwagę wprowadzenie małego błędu, który sprawia, że kod staje się niepotrzebny, a kod staje się absolutnie zerowy, powiedziałbym, że twój kod jest DUŻO lepszy.
Innymi słowy, w kompilacjach, w których INT_MIN = -32768 (nawet jeśli wynikowa funkcja nie może otrzymać danych wejściowych <-32768 lub> 32767), prawidłowe wejście -32768 powoduje niezdefiniowane zachowanie, ponieważ wynik - (- 32768i16) nie może być wyrażony jako 16-bitowa liczba całkowita. (Właściwie -32768 prawdopodobnie nie spowodowałoby nieprawidłowego wyniku, ponieważ - (- 32768i16) zwykle ocenia na -32768i16, a twój program poprawnie obsługuje liczby ujemne.) (SHRT_MIN może być -32768 lub -32767, w zależności od kompilatora).
Ale twój nauczyciel wyraźnie stwierdził, że n
może być w przedziale [-10 ^ 7; 10 ^ 7]. 16-bitowa liczba całkowita jest za mała; musisz użyć [co najmniej] 32-bitowej liczby całkowitej. Użycie int
może sprawić, że jego kod będzie bezpieczny, ale int
niekoniecznie jest to 32-bitowa liczba całkowita. Jeśli kompilujesz dla architektury 16-bitowej, oba fragmenty kodu są wadliwe. Ale twój kod jest wciąż znacznie lepszy, ponieważ ten scenariusz przywraca błąd z INT_MIN
wyżej wspomnianą wersją. Aby tego uniknąć, możesz long
zamiast tego napisać int
, która jest 32-bitową liczbą całkowitą dla dowolnej architektury. long
Gwarantowane jest, że A jest w stanie utrzymać dowolną wartość z zakresu [-2147483647; 2147483647]. C11 Standard 5.2.4.2.1, ale maksymalna (tak, maksymalna, to liczba ujemna) dozwolona wartość dlaLONG_MIN
Często występuje-2147483648
LONG_MIN
jest 2147483647
.
Jakie zmiany wprowadziłbym do twojego kodu?
Twój kod jest w porządku, więc nie są to tak naprawdę skargi. Jest tak bardziej, jeśli naprawdę muszę powiedzieć coś o twoim kodzie, jest kilka drobnych rzeczy, które mogą sprawić, że będzie on odrobinę jaśniejszy.
- Nazwy zmiennych mogą być nieco lepsze, ale jest to krótka funkcja, która jest łatwa do zrozumienia, więc nie jest to wielka sprawa.
- Możesz zmienić warunek z
n
na n!=0
. Semantycznie jest w 100% równoważny, ale sprawia, że jest nieco jaśniejszy.
- Przenieś deklarację
c
(do której zmieniłem nazwę digit
) do wewnątrz pętli while, ponieważ jest ona używana tylko tam.
- Zmień typ argumentu,
long
aby upewnić się, że może obsłużyć cały zestaw danych wejściowych.
int sum_of_digits_squared(long n)
{
long sum = 0;
while (n != 0) {
int digit = n % 10;
sum += (digit * digit);
n /= 10;
}
return sum;
}
W rzeczywistości może to być nieco mylące, ponieważ - jak wspomniano powyżej - zmienna digit
może otrzymać wartość ujemną, ale cyfra sama w sobie nigdy nie jest ani dodatnia, ani ujemna. Jest na to kilka sposobów, ale NAPRAWDĘ jest to podstępne i nie dbałbym o tak drobne szczegóły. Szczególnie osobna funkcja dla ostatniej cyfry prowadzi za daleko. Jak na ironię, jest to jedna z rzeczy, które kod nauczyciela faktycznie rozwiązuje.
- Zmiana
sum += (digit * digit)
do sum += ((n%10)*(n%10))
i pominąć zmienną digit
całkowicie.
- Zmień znak,
digit
jeśli jest ujemny. Ale zdecydowanie odradzam rozbudowywanie kodu tylko po to, aby nazwa zmiennej miała sens. To BARDZO silny zapach kodu.
- Utwórz osobną funkcję, która wyodrębni ostatnią cyfrę.
int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Jest to przydatne, jeśli chcesz użyć tej funkcji w innym miejscu.
- Po prostu nazwij to
c
tak, jak pierwotnie. Ta nazwa zmiennej nie zawiera żadnych użytecznych informacji, ale z drugiej strony nie wprowadza w błąd.
Ale szczerze mówiąc, w tym momencie powinieneś przejść do ważniejszej pracy. :)
n = n * (-1)
jest niedorzecznym sposobem pisanian = -n
; Tylko naukowiec by o tym pomyślał. Nie mówiąc już o dodawaniu niepotrzebnych nawiasów.