Czy muszę sumować liczby ujemne lub zero podczas sumowania cyfr w kwadracie?


220

Niedawno miałem test w mojej klasie. Jednym z problemów było:

Biorąc pod uwagę liczbę n , napisz funkcję w C / C ++, która zwraca sumę cyfr liczby podniesionej do kwadratu . (Ważne są następujące). Zakres od n jest [- (7 ^ 10), 10 ^ 7]. Przykład: Jeśli n = 123, funkcja powinna zwrócić 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).

Oto funkcja, którą napisałem:

int sum_of_digits_squared(int n) 
{
    int s = 0, c;

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

Spojrzał mi w prawo. Więc teraz test wrócił i okazało się, że nauczyciel nie dał mi wszystkich punktów z powodu, którego nie rozumiem. Według niego, aby moja funkcja była kompletna, powinienem był dodać następujący szczegół:

int sum_of_digits_squared(int n) 
 {
    int s = 0, c;

    if (n == 0) {      //
        return 0;      //
    }                  //
                       // THIS APPARENTLY SHOULD'VE 
    if (n < 0) {       // BEEN IN THE FUNCTION FOR IT
        n = n * (-1);  // TO BE CORRECT
    }                  //

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

Argumentem tego jest to, że liczba n jest w zakresie [- (10 ^ 7), 10 ^ 7], więc może być liczbą ujemną. Ale nie widzę, gdzie zawodzi moja własna wersja funkcji. Jeśli dobrze rozumiem, sens while(n)jest while(n != 0), nie while (n > 0) , więc w mojej wersji funkcji liczba n nie uda się wprowadzić pętlę. To działałoby tak samo.

Następnie wypróbowałem obie wersje funkcji na komputerze w domu i uzyskałem dokładnie takie same odpowiedzi na wszystkie przykłady, które wypróbowałem. Tak więc sum_of_digits_squared(-123)jest równy sum_of_digits_squared(123)(który znowu jest równy 14) (nawet bez szczegółów, które najwyraźniej powinienem był dodać). Rzeczywiście, jeśli spróbuję wydrukować na ekranie cyfry liczby (od najmniejszej do największej wagi), w 123przypadku, gdy dostaję, 3 2 1a w -123przypadku, gdy dostaję -3 -2 -1(co jest w pewnym sensie interesujące). Ale w tym problemie nie miałoby to znaczenia, ponieważ poprawiliśmy cyfry.

Więc kto się myli?

EDYCJA : Mój zły, zapomniałem sprecyzować i nie wiedziałem, że to ważne. Wersja C używana w naszej klasie i testach musi mieć wersję C99 lub nowszą . Tak więc sądzę (czytając komentarze), że moja wersja w jakikolwiek sposób uzyska poprawną odpowiedź.


120
n = n * (-1)jest niedorzecznym sposobem pisania n = -n; Tylko naukowiec by o tym pomyślał. Nie mówiąc już o dodawaniu niepotrzebnych nawiasów.
user207421,

32
Napisz serię testów jednostkowych, aby sprawdzić, czy dana implementacja pasuje do specyfikacji. Jeśli występuje problem (funkcjonalny) z fragmentem kodu, powinno być możliwe napisanie testu, który wykaże niepoprawny wynik przy danym wejściu.
Carl

94
Interesujące jest dla mnie to, że „sumę cyfr kwadratu” można interpretować na trzy (3) zupełnie różne sposoby. (Jeśli liczba wynosi 123, możliwe interpretacje dają 18, 14 i 36.)
Andreas Rejbrand

23
@ilkkachu: „suma cyfr liczby do kwadratu”. Cóż, „liczba podniesiona do kwadratu” wynosi wyraźnie 123 ^ 2 = 15129, więc „suma cyfr liczby podniesionej do kwadratu” to „suma cyfr 15129”, co oczywiście wynosi 1 + 5 + 1 + 2 + 9 = 18
Andreas Rejbrand

15
n = n * (-1)? Łut ??? To, czego szuka twój profes, to: `n = -n '. Język C ma jednoargumentowy operator minus.
Kaz

Odpowiedzi:


245

Podsumowanie dyskusji, która była przesiąknięta komentarzami:

  • Nie ma dobrego powodu, aby testować z wyprzedzeniem n == 0. while(n)Test będzie obsługiwać tę sprawę doskonale.
  • Najprawdopodobniej twój nauczyciel jest przyzwyczajony do wcześniejszych czasów, kiedy wynik %z argumentami ujemnymi był różnie zdefiniowany. Na niektórych starych systemach (w tym zwłaszcza na wczesnym Unixie na PDP-11, gdzie Dennis Ritchie pierwotnie opracował C), wynik zawszea % b był w zakresie , co oznacza, że ​​-123% 10 wynosiło 7. W takim systemie test z góry byłoby konieczne.[0 .. b-1]n < 0

Ale drugi pocisk dotyczy tylko wcześniejszych czasów. W aktualnych wersjach zarówno standardu C, jak i C ++ podział liczb całkowitych jest zdefiniowany tak, aby obciąć w kierunku 0, więc okazuje się, że n % 10gwarantuje się (prawdopodobnie ujemną) ostatnią cyfrę, nnawet gdy njest ujemna.

Więc odpowiedź na pytanie „Co to znaczy while(n)?” brzmi „Dokładnie to samo co while(n != 0) , a odpowiedź na „Czy ten kod będzie działał poprawnie zarówno dla wartości ujemnych, jak i dodatnich n?” to „Tak, w ramach dowolnego nowoczesnego kompilatora zgodnego ze standardami”. Odpowiedź na pytanie „Dlaczego instruktor to zaznaczył?” prawdopodobnie nie są świadomi znaczącej redefinicji języka, która wydarzyła się w C w 1999 r. i C ++ w 2010 r.


39
„Nie ma dobrego powodu, aby wcześniej testować pod kątem n == 0” - technicznie to prawda. Ale biorąc pod uwagę, że mówimy o profesorze w środowisku nauczania, mogą oni docenić jasność zwięzłości znacznie bardziej niż my. Dodanie dodatkowego testu dla n == 0przynajmniej sprawia, że ​​dla każdego czytelnika jest to natychmiast i całkowicie oczywiste, co dzieje się w takim przypadku. Bez niego czytelnik musi upewnić się, że pętla rzeczywiście jest pomijana, a domyślna wartość szwracana jest poprawna.
ilkkachu

22
Również profesor może chcieć wiedzieć, że student jest świadomy i nie pomyślał o tym, dlaczego i jak zachowuje funkcyjnych z wejściem od zera (czyli, że nie zwróci poprawną wartość przez przypadek ). Mogli spotkać uczniów, którzy nie zdają sobie sprawy z tego, co by się wtedy wydarzyło, że pętla może działać zero razy itp. Kiedy masz do czynienia z ustawieniem nauczania, najlepiej wyjaśnić wszelkie założenia i przypadki narożne. ..
ilkkachu

36
@ilkkachu W takim przypadku nauczyciel powinien przekazać zadanie wymagające takiego testu, aby działał poprawnie.
klutt

38
@ilkkachu Cóż, zgadzam się z ogólną sprawą, ponieważ absolutnie doceniam przejrzystość zwięzłości - i prawie cały czas, niekoniecznie tylko w środowisku pedagogicznym. Ale powiedziawszy to, czasami zwięzłość jest klarowna, a jeśli możesz ustawić, aby główny kod obsługiwał zarówno przypadek ogólny, jak i przypadki brzegowe, bez zaśmiecania kodu (i analizy pokrycia) ze specjalnymi przypadkami dla przypadków skrajnych , to piękna rzecz! Myślę, że należy to docenić nawet na poziomie początkującym.
Steve Summit

49
@ilkkachu Przez ten argument z pewnością powinieneś również dodać testy dla n = 1 i tak dalej. Nie ma w tym nic specjalnego n=0. Wprowadzenie niepotrzebnych gałęzi i komplikacji nie sprawia, że ​​kod jest łatwiejszy, utrudnia, ponieważ teraz nie tylko musisz wykazać, że ogólny algorytm jest poprawny, musisz również osobno pomyśleć o wszystkich przypadkach specjalnych.
Voo

107

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 nzero 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/bjest równa wartości bezwzględnej (-a)/bdla 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%bwedł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==1ale(-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_MINORAZ 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 nmoż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 intmoże sprawić, że jego kod będzie bezpieczny, ale intniekoniecznie 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_MINwyżej wspomnianą wersją. Aby tego uniknąć, możesz longzamiast tego napisać int, która jest 32-bitową liczbą całkowitą dla dowolnej architektury. longGwarantowane 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_MINCzęsto występuje-2147483648LONG_MINjest 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 nna 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, longaby 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 digitmoż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ą digitcałkowicie.
  • Zmień znak, digitjeś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 ctak, 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. :)


19
Jeśli dane wejściowe są, INT_MINa architektura używa uzupełnienia do dwóch (co jest bardzo powszechne), kod twojego nauczyciela da niezdefiniowane zachowanie. Auć. To pozostawi ślad. ;-)
Andrew Henle

5
Należy wspomnieć, że oprócz (a/b)*b + a%b ≡ atego kod PO zależy również od tego, że /zaokrągla się do zera, i to (-c)*(-c) ≡ c*c. To mógłby uznać, że dodatkowe kontrole są uzasadnione pomimo standardu gwarantującego wszystkim, dlatego, że jest wystarczająco nieoczywiste. (Oczywiście równie dobrze można argumentować, że powinien istnieć komentarz łączący odpowiednie standardowe sekcje, ale wytyczne dotyczące stylu są różne.)
lewej około

7
@MartinRosenau Mówisz „może”. Czy na pewno tak się dzieje, czy jest to dozwolone przez standard lub coś, czy tylko spekulujesz?
klutt

6
@MartinRosenau: Ok, ale użycie tych przełączników sprawiłoby, że już nie jest C. GCC / clang nie ma żadnych przełączników, które łamią podział liczb całkowitych na żadnym ISA, o którym wiem. Nawet jeśli zignorowanie bitu znakowego może przyspieszyć użycie normalnej odwrotności multiplikatywnej dla stałych dzielników. (Ale wszystkie znane mi ISA, które mają instrukcję podziału sprzętowego, implementują ją w sposób C99, skracając się do zera, więc C %i /operatorzy mogą kompilować się tylko idivna x86, sdivARM lub cokolwiek innego. Mimo to nie ma to związku z szybsze generowanie kodu dla dzielników stałych kompilacyjnych)
Peter Cordes

5
@TonyK AFIK, tak zazwyczaj jest rozwiązany, ale zgodnie ze standardem jest to UB.
klutt

20

Nie podoba mi się ani twoja wersja, ani wersja twojego nauczyciela. Wersja twojego nauczyciela wykonuje dodatkowe testy, które prawidłowo wskazałeś, są niepotrzebne. Operator modów C nie jest właściwym modem matematycznym: liczba ujemna mod 10 da wynik ujemny (właściwy moduł matematyczny jest zawsze nieujemny). Ale skoro i tak to rozwiązujesz, nie ma różnicy.

Nie jest to jednak oczywiste, dlatego dodam do twojego kodu nie kontrole twojego nauczyciela, ale duży komentarz wyjaśniający, dlaczego to działa. Na przykład:

/ * UWAGA: Działa to dla wartości ujemnych, ponieważ moduł zostaje podniesiony do kwadratu * /


9
C %najlepiej jest nazywać resztą , ponieważ jest tak nawet w przypadku typów podpisanych.
Peter Cordes,

15
Kwadrat jest ważny, ale myślę, że to oczywista część. Należy zauważyć, że (np.) -7 % 10 Faktycznie będzie-7 zamiast 3.
Jacob Raihle,

5
„Właściwy moduł matematyczny” nic nie znaczy. Prawidłowy termin to „euclidean modulo” (zwróć uwagę na sufiks!) I tak właśnie %nie jest operator C.
Jan Hudec,

Podoba mi się ta odpowiedź, ponieważ rozwiązuje ona kwestię wielu sposobów interpretacji modulo. Nigdy nie zostawiaj czegoś takiego przypadkowi / interpretacji. To nie jest golf golfowy.
Harper - Przywróć Monikę

1
„właściwy moduł matematyczny jest zawsze nieujemny” - Niezupełnie. Wynik operacji modulo jest klasą równoważności , ale często traktowany jest jako najmniejsza nieujemna liczba należąca do tej klasy.
klutt

10

UWAGA: Kiedy pisałem tę odpowiedź, wyjaśniłeś, że używasz C. Większość mojej odpowiedzi dotyczy C ++. Ponieważ jednak twój tytuł wciąż zawiera C ++, a pytanie wciąż oznaczone jest C ++, postanowiłem odpowiedzieć tak czy inaczej, na wypadek, gdyby nadal było to przydatne dla innych osób, zwłaszcza że większość odpowiedzi, które widziałem do tej pory, są w większości niezadowalające.

We współczesnym C ++ (uwaga: tak naprawdę nie wiem, na czym polega C), twój profesor wydaje się mylić w obu przypadkach.

Pierwsza jest ta część tutaj:

if (n == 0) {
        return 0;
}

W C ++ jest to w zasadzie to samo, co :

if (!n) {
        return 0;
}

Oznacza to, że twoja chwila jest odpowiednikiem czegoś takiego:

while(n != 0) {
    // some implementation
}

Oznacza to, że po prostu wychodzisz z siebie, jeśli kiedy i tak nie wykonasz, naprawdę nie ma powodu, aby umieszczać to, jeśli tutaj, ponieważ to, co robisz po pętli i if jest równoważne. Chociaż powinienem powiedzieć, że z jakiegoś powodu były one inne, musisz to mieć, jeśli.

Tak naprawdę to stwierdzenie if nie jest szczególnie przydatne, chyba że się mylę.

Druga część to owłosienie:

if (n < 0) {
    n = n * (-1);
}  

Istotą problemu jest to, co wyprowadza moduł liczby ujemnej.

We współczesnym C ++ wydaje się to być w większości dobrze zdefiniowane :

Operator binarny / operator daje iloraz, a operator binarny% daje resztę z podziału pierwszego wyrażenia przez drugie. Jeśli drugi argument / lub% wynosi zero, zachowanie jest niezdefiniowane. W przypadku argumentów całkowitych operator / daje iloraz algebraiczny z odrzuconą dowolną częścią ułamkową; jeśli iloraz a / b jest reprezentatywny w typie wyniku, (a / b) * b + a% b jest równe a.

I później:

Jeśli oba operandy są nieujemne, reszta jest nieujemna; jeśli nie, znak reszty jest zdefiniowany w implementacji.

Jak słusznie pokazuje plakat cytowanej odpowiedzi, ważna część tego równania tutaj:

(a / b) * b + a% b

Biorąc przykład swojej sprawy, otrzymasz coś takiego:

-13/ 10 = -1 (integer truncation)
-1 * 10 = -10
-13 - (-10) = -13 + 10 = -3 

Jedynym haczykiem jest ostatnia linia:

Jeśli oba operandy są nieujemne, reszta jest nieujemna; jeśli nie, znak reszty jest zdefiniowany w implementacji.

Oznacza to, że w takim przypadku tylko znak wydaje się być zdefiniowany w implementacji. Nie powinno to stanowić problemu w twoim przypadku, ponieważ i tak kwadrujesz tę wartość.

To powiedziawszy, pamiętaj, że niekoniecznie dotyczy to wcześniejszych wersji C ++ lub C99. Jeśli tego właśnie używa twój profesor, może to być powód.


EDYCJA: Nie, mylę się. Wydaje się, że dotyczy to również C99 lub nowszej wersji :

C99 wymaga, aby gdy a / b jest reprezentowalne:

(a / b) * b + a% b będzie równe a

I inne miejsce :

Gdy liczby całkowite są podzielone, a podział jest niedokładny, jeśli oba operandy są dodatnie, wynikiem operatora / jest największa liczba całkowita mniejsza niż iloraz algebraiczny, a wynik operatora% jest dodatni. Jeśli któryś z argumentów jest ujemny, to czy wynik operatora / jest największą liczbą całkowitą mniejszą niż iloraz algebraiczny, czy też najmniejszą liczbą całkowitą większą niż iloraz algebraiczny, określa się implementację, podobnie jak znak wyniku operatora%. Jeżeli iloraz a / b jest reprezentowalny, wyrażenie (a / b) * b + a% b będzie równe a.

Czy ANSI C lub ISO C określa, co powinno wynosić -5% 10?

Więc tak. Nawet w C99 nie wydaje się, aby miało to na ciebie wpływ. Równanie jest takie samo.


1
Części, które zacytowałeś, nie obsługują tej odpowiedzi. „znak reszty jest zdefiniowany jako implementacja” nie oznacza, że (-1)%10może produkować -1lub 1; oznacza to, że może produkować -1lub 9, w tym drugim przypadku (-1)/10, produkuje, -1a kod OP nigdy się nie kończy.
stewbasic

Czy możesz wskazać na to źródło? Bardzo trudno mi uwierzyć (-1) / 10 to -1. Powinno to wynosić 0. Ponadto (-1)% 10 = 9 wydaje się naruszać równanie rządzące.
Chipster

1
@Chipster, zacznij od (a/b)*b + a%b == a, a następnie pozwól a=-1; b=10, dając (-1/10)*10 + (-1)%10 == -1. Teraz, jeśli -1/10rzeczywiście zostanie zaokrąglony w dół (w kierunku -inf), to mamy (-1/10)*10 == -10, i musisz mieć, (-1)%10 == 9aby pierwsze równanie się zgadzało . Podobnie jak w przypadku innych odpowiedzi , nie działa to w ramach obecnych standardów, ale tak to działało. Tak naprawdę nie chodzi o znak reszty jako takiej, ale o to, jak zaokrągla się podział i jaka musi być reszta, aby spełnić równanie.
ilkkachu

1
@Chipster Źródłem są cytowane fragmenty. Należy pamiętać, że (-1)*10+9=-1więc wybór (-1)/10=-1i (-1)%10=9nie narusza równania rządzącego. Z drugiej strony wybór (-1)%10=1nie może spełnić równania rządzącego, bez względu na (-1)/10to, jak zostanie wybrany; nie ma qtakiej liczby całkowitej , że q*10+1=-1.
stewbasic

8

Jak zauważyli inni, specjalne traktowanie dla n == 0 to nonsens, ponieważ dla każdego poważnego programisty C oczywiste jest, że „while (n)” wykonuje swoje zadanie.

Zachowanie dla n <0 nie jest tak oczywiste, dlatego wolałbym zobaczyć te 2 linie kodu:

if (n < 0) 
    n = -n;

lub przynajmniej komentarz:

// don't worry, works for n < 0 as well

Szczerze mówiąc, od kiedy zacząłeś rozważać, że n może być ujemne? Pisząc kod lub czytając uwagi nauczyciela?


1
Niezależnie od tego, czy N jest ujemne, N do kwadratu będzie dodatnie. Po co więc usuwać znak? -3 * -3 = 9; 3 * 3 = 9. Czy matematyka zmieniła się w ciągu 30 lat, odkąd się tego nauczyłem?
Merovex,

2
@CB Szczerze mówiąc, nawet nie zauważyłem, że n może być ujemne podczas pisania testu, ale kiedy wrócił, miałem wrażenie, że pętla while nie zostanie pominięta, nawet jeśli liczba jest ujemna. Zrobiłem kilka testów na moim komputerze i potwierdziło to mój sceptycyzm. Potem opublikowałem to pytanie. Więc nie, nie myślałem tak głęboko podczas pisania kodu.
user010517720,

5

To przypomina mi zadanie, które mi się nie udało

W latach 90. Wykładowca opowiadał o pętlach i, krótko mówiąc, naszym zadaniem było napisanie funkcji, która zwróciłaby liczbę cyfr dla dowolnej liczby całkowitej> 0.

Na przykład liczba cyfr 321będzie wynosić 3.

Chociaż zadanie mówiło po prostu o napisaniu funkcji zwracającej liczbę cyfr, oczekiwano, że użyjemy pętli dzielącej przez 10, dopóki ... nie dostaniesz jej, jak opisano w wykładzie .

Ale używanie pętli nie zostało wyraźnie stwierdzone, więc ja: took the log, stripped away the decimals, added 1i następnie został zniesławiony przed całą klasą.

Chodzi o to, że celem tego zadania było sprawdzenie naszego zrozumienia tego, czego nauczyliśmy się podczas wykładów . Z wykładu, który otrzymałem, dowiedziałem się, że nauczyciel komputerowy był trochę palantem (ale może palantem z planem?)


W twojej sytuacji:

napisz funkcję w C / C ++, która zwraca sumę cyfr liczby do kwadratu

Zdecydowanie podałbym dwie odpowiedzi:

  • prawidłowa odpowiedź (najpierw kwadrat) i
  • niepoprawna odpowiedź zgodna z przykładem, żeby go uszczęśliwić ;-)

5
A także trzeci kwadrat sumujący cyfry?
kriss

@kriss - tak, nie jestem taki mądry :-(
SlowLearner

1
Miałem też udział w zbyt niejasnych zadaniach w czasie studiów. Nauczyciel chciał trochę biblioteki manipulacji, ale był zaskoczony moim kodem i powiedział, że to nie działa. Musiałem mu powiedzieć, że nigdy nie zdefiniował endianizmu w swoim zadaniu i wybrał niższy bit jako bit 0, podczas gdy ja dokonałem innego wyboru. Jedyną irytującą rzeczą jest to, że powinien był dowiedzieć się, gdzie była różnica, bez mojej wiedzy.
kriss

1

Zasadniczo w zadaniach nie wszystkie znaki są podawane tylko dlatego, że kod działa. Otrzymujesz również oceny za uczynienie rozwiązania łatwym do odczytania, wydajnym i eleganckim. Te rzeczy nie zawsze się wykluczają.

Jednym z tych, których nie potrafię streścić, jest „używanie znaczących nazw zmiennych” .

W twoim przykładzie nie ma to większego znaczenia, ale jeśli pracujesz nad projektem z milionem linii czytelności kodu, staje się bardzo ważne.

Inną rzeczą, którą zwykle widzę w kodzie C, są ludzie próbujący wyglądać sprytnie. Zamiast używać while (n! = 0) pokażę wszystkim, jak sprytny jestem, pisząc while (n), ponieważ oznacza to to samo. Cóż, robi to w kompilatorze, który masz, ale jak sugerujesz, starsza wersja nauczyciela nie zaimplementowała go w ten sam sposób.

Typowym przykładem jest odwoływanie się do indeksu w tablicy przy jednoczesnym zwiększaniu go; Liczby [i ++] = iPrime;

Teraz następny programista, który pracuje nad kodem, musi wiedzieć, czy jestem zwiększany przed lub po zadaniu, aby ktoś mógł się popisać.

Megabajt miejsca na dysku jest tańszy niż rolka papieru toaletowego, idź dla zachowania przejrzystości, zamiast próbować zaoszczędzić miejsce, inni programiści będą szczęśliwsi.


2
Programowałem w C tylko kilka razy i wiem, że to ++iprzyrosty przed oceną, a i++potem przyrosty. while(n)jest także wspólną funkcją językową. W oparciu o taką logikę widziałem wiele takich kodów if (foo == TRUE). Zgadzam się jednak na nazwy zmiennych.
alan ocallaghan

3
Ogólnie rzecz biorąc, nie jest to zła rada, ale unikanie podstawowych funkcji językowych (które i tak nieuchronnie się spotka) w celu programowania obronnego jest nadmierne. Krótki, czytelny kod i tak jest często bardziej czytelny. Nie mówimy tu o zwariowanym perlu lub bashu one-linerach, tylko o podstawowych funkcjach językowych.
alan ocallaghan

1
Nie jestem pewien, po co podano opinie negatywne dotyczące tej odpowiedzi. Dla mnie wszystko, co zostało tu powiedziane, jest prawdziwe i ważne oraz dobrą radą dla każdego programisty. Zwłaszcza część z inteligentnym programowaniem, chociaż while(n)nie jest to najgorszy przykład („lubię” tym if(strcmp(one, two))bardziej)
Kai Huppmann

1
Poza tym naprawdę nie powinieneś pozwalać nikomu, kto nie zna różnicy między kodem C i++i ++imodyfikować go, który powinien być używany w produkcji.
klutt

2
@PaulMcCarthy Obaj jesteśmy pod kątem czytelności kodu. Ale nie zgadzamy się co do tego. Ponadto nie jest obiektywny. To, co jest łatwe do odczytania dla jednej osoby, może być trudne dla innej. Ma na to duży wpływ osobowość i wiedza z przeszłości. Chodzi mi przede wszystkim o to, że nie uzyskuje się maksymalnej czytelności przez ślepe przestrzeganie niektórych zasad.
klutt

0

Nie kłóciłbym się o to, czy oryginalna czy współczesna definicja „%” jest lepsza, ale każdy, kto napisze dwie instrukcje return w tak krótkiej funkcji, nie powinien uczyć programowania C. Dodatkowy zwrot jest instrukcją goto i nie używamy goto w C. Ponadto kod bez kontroli zerowej miałby ten sam wynik, dodatkowy zwrot utrudniał czytanie.


4
„Dodatkowy zwrot jest wyrażeniem goto i nie używamy goto w C.” - to połączenie bardzo szerokiego uogólnienia i bardzo dalekiego zasięgu.
SS Anne,

1
„My” zdecydowanie używamy goto w C. Nie ma w tym nic złego.
klutt

1
Ponadto, nie ma nic złego w funkcji takiej jakint findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
klutt

1
@PeterKrassoi Nie jestem zwolennikiem trudnego do odczytania kodu, ale widziałem mnóstwo przykładów, w których kod wygląda jak kompletny bałagan, aby uniknąć prostego goto lub dodatkowej instrukcji return. Oto kod z odpowiednim użyciem goto. Wzywam cię do usunięcia instrukcji goto, jednocześnie ułatwiając czytanie i zarządzanie kodem: pastebin.com/aNaGb66Q
klutt

1
@PeterKrassoi Pokaż również swoją wersję: pastebin.com/qBmR78KA
klutt

0

Opis problemu jest mylący, ale przykład liczbowy wyjaśnia znaczenie sumy cyfr liczby podniesionej do kwadratu . Oto ulepszona wersja:

Napisz funkcję we wspólnym podzbiorze C i C ++, która przyjmuje liczbę całkowitą nz zakresu [-10 7 , 10 7 ] i zwraca sumę kwadratów cyfr jej reprezentacji w bazie 10. Przykład: jeśli njest 123, twoja funkcja powinien zwrócić 14(1 2 + 2 2 + 3 2 = 14).

Funkcja, którą napisałeś jest w porządku, z wyjątkiem 2 szczegółów:

  • Argument powinien mieć typ longuwzględniający wszystkie wartości w określonym zakresie, ponieważ longnorma C gwarantuje, że typ ma co najmniej 31 bitów wartości, a zatem zakres wystarczający do przedstawienia wszystkich wartości w [-10 7 , 10 7 ] . (Należy zauważyć, że typ intjest wystarczający dla typu zwracanego, którego maksymalna wartość wynosi568 .)
  • Zachowanie %argumentów ujemnych nie jest intuicyjne, a jego specyfikacja różniła się między wersją standardową C99 i poprzednimi wersjami. Powinieneś udokumentować, dlaczego twoje podejście jest ważne nawet w przypadku negatywnych danych wejściowych.

Oto zmodyfikowana wersja:

int sum_of_digits_squared(long n) {
    int s = 0;

    while (n != 0) {
        /* Since integer division is defined to truncate toward 0 in C99 and C++98 and later,
           the remainder of this division is positive for positive `n`
           and negative for negative `n`, and its absolute value is the last digit
           of the representation of `n` in base 10.
           Squaring this value yields the expected result for both positive and negative `c`.
           dividing `n` by 10 effectively drops the last digit in both cases.
           The loop will not be entered for `n == 0`, producing the correct result `s = 0`.
         */
        int c = n % 10;
        s += c * c;
        n /= 10;
    }
    return s;
}

Odpowiedź nauczyciela ma wiele wad:

  • rodzaj int może mieć niewystarczający zakres wartości.
  • nie ma potrzeby szczególnego przypadku wartości 0 .
  • negowanie wartości ujemnych jest niepotrzebne i może mieć niezdefiniowane zachowanie n = INT_MIN.

Biorąc pod uwagę dodatkowe ograniczenia w opisie problemu (C99 i zakres wartości dla n), tylko pierwsza wada jest problemem. Dodatkowy kod nadal daje poprawne odpowiedzi.

W tym teście powinieneś uzyskać dobrą ocenę, ale w teście pisemnym wymagane jest wyjaśnienie, aby wykazać, że rozumiesz problemy jako negatywne n, w przeciwnym razie nauczyciel może założyć, że nieświadomy i po prostu miałeś szczęście. Na egzaminie ustnym miałbyś pytanie, a twoja odpowiedź by go przybiła.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.