Muss ich beim Summieren von quadratischen Ziffern explizit mit negativen Zahlen oder Nullen umgehen?

220

Ich hatte vor kurzem einen Test in meiner Klasse. Eines der Probleme war das folgende:

Schreiben Sie bei gegebener Zahl n eine Funktion in C / C ++, die die Summe der Ziffern der quadrierten Zahl zurückgibt . (Folgendes ist wichtig). Der Bereich von n ist [- (10 ^ 7), 10 ^ 7]. Beispiel: Wenn n = 123 ist, sollte Ihre Funktion 14 zurückgeben (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).

Dies ist die Funktion, die ich geschrieben habe:

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

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

    return s;
}

Sah für mich richtig aus. Nun kam der Test zurück und ich stellte fest, dass der Lehrer mir aus einem Grund, den ich nicht verstehe, nicht alle Punkte gegeben hat. Ihm zufolge hätte ich, damit meine Funktion vollständig ist, das folgende Detail hinzufügen sollen:

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;
}

Das Argument dafür ist, dass die Zahl n im Bereich [- (10 ^ 7), 10 ^ 7] liegt, also eine negative Zahl sein kann. Aber ich sehe nicht, wo meine eigene Version der Funktion fehlschlägt. Wenn ich richtig verstehe, von der Bedeutung while(n)ist while(n != 0), nicht while (n > 0) , so in meiner Version der Funktion die Anzahl n nicht die Schleife eintreten würde scheitern. Es würde genauso funktionieren.

Dann habe ich beide Versionen der Funktion auf meinem Computer zu Hause ausprobiert und für alle Beispiele, die ich ausprobiert habe, genau die gleichen Antworten erhalten. Also, sum_of_digits_squared(-123)ist gleich sum_of_digits_squared(123)(was wiederum gleich ist 14) (auch ohne das Detail, das ich anscheinend hätte hinzufügen sollen). In der Tat, wenn ich versuche, die Ziffern der Zahl (von der geringsten bis zur größten Bedeutung) auf den Bildschirm zu drucken, in dem 123Fall, den ich bekomme 3 2 1und in dem -123Fall, den ich bekomme -3 -2 -1(was eigentlich irgendwie interessant ist). Aber bei diesem Problem wäre es egal, da wir die Ziffern quadrieren.

Also, wer ist falsch?

EDIT : Mein schlechtes, ich habe vergessen anzugeben und wusste nicht, dass es wichtig ist. Die in unserer Klasse und unseren Tests verwendete Version von C muss C99 oder neuer sein . Ich denke also (durch Lesen der Kommentare), dass meine Version in irgendeiner Weise die richtige Antwort bekommen würde.

user010517720
quelle
120
n = n * (-1)ist eine lächerliche Art zu schreiben n = -n; Nur ein Akademiker würde daran denken. Ganz zu schweigen von den redundanten Klammern.
user207421
32
Schreiben Sie eine Reihe von Komponententests, um zu überprüfen, ob eine bestimmte Implementierung der Spezifikation entspricht. Wenn es ein (funktionales) Problem mit einem Code gibt, sollte es möglich sein, einen Test zu schreiben, der das falsche Ergebnis bei einer bestimmten Eingabe demonstriert.
Carl
94
Ich finde es interessant, dass "die Summe der Ziffern der Zahl im Quadrat" auf drei (3) völlig unterschiedliche Arten interpretiert werden kann. (Wenn die Zahl 123 ist, ergeben die möglichen Interpretationen 18, 14 und 36.)
Andreas Rejbrand
23
@ilkkachu: "die Summe der Ziffern der Zahl im Quadrat". Nun, "die Zahl im Quadrat" ist eindeutig 123 ^ 2 = 15129, also ist "die Summe der Ziffern der Zahl im Quadrat" "die Summe der Ziffern von 15129", was offensichtlich 1 + 5 + 1 + 2 + 9 ist = 18.
Andreas Rejbrand
15
n = n * (-1)? Wut ??? Was Ihr Professor sucht, ist Folgendes: `n = -n '. Die C-Sprache hat einen unären Minusoperator.
Kaz

Antworten:

245

Zusammenfassung einer Diskussion, die in den Kommentaren versickert:

  • Es gibt keinen guten Grund, im Voraus zu testen n == 0. Der while(n)Test wird diesen Fall perfekt behandeln.
  • Es ist wahrscheinlich, dass Ihr Lehrer noch an frühere Zeiten gewöhnt ist, als das Ergebnis %mit negativen Operanden anders definiert wurde. Auf einigen älteren Systemen (einschließlich, insbesondere, frühe Unix auf einem PDP-11, wo Dennis Ritchie ursprünglich entwickelt , C), das Ergebnis a % bwar immer im Bereich [0 .. b-1], was bedeutet , dass -123% 10 war 7. Auf einem solchen System den Test im Voraus für n < 0wäre notwendig.

Der zweite Punkt gilt jedoch nur für frühere Zeiten. In den aktuellen Versionen sowohl des C- als auch des C ++ - Standards wird die Ganzzahldivision so definiert, dass sie gegen 0 abschneidet. Es stellt sich also heraus, dass Sie n % 10garantiert die (möglicherweise negative) letzte Ziffer erhalten, nauch wenn sie nnegativ ist.

Also die Antwort auf die Frage "Was bedeutet das while(n)?" ist "Genau das Gleiche wie while(n != 0)" und die Antwort auf "Funktioniert dieser Code sowohl für negative als auch für positive nZwecke ordnungsgemäß ?" ist "Ja, unter jedem modernen, standardkonformen Compiler." Die Antwort auf die Frage "Warum hat der Ausbilder sie dann notiert?" ist wahrscheinlich, dass ihnen keine signifikante Neudefinition der Sprache bekannt ist, die 1999 mit C und 2010 mit C ++ passiert ist.

Steve Summit
quelle
39
"Es gibt keinen guten Grund, im Voraus auf n == 0 zu testen" - technisch gesehen ist das richtig. Angesichts der Tatsache, dass es sich um einen Professor in einem Lehrumfeld handelt, schätzen sie Klarheit über Kürze viel mehr als wir. Das Hinzufügen des zusätzlichen Tests für n == 0mindestens macht es für jeden Leser sofort und völlig offensichtlich, was in diesem Fall passiert. Ohne sie muss sich der Leser davon überzeugen, dass die Schleife tatsächlich übersprungen wird und der Standardwert von sreturn der richtige ist.
Ilkkachu
22
Außerdem möchte der Professor möglicherweise wissen, dass der Student sich bewusst ist und darüber nachgedacht hat, warum und wie sich die Funktion bei einer Eingabe von Null verhält (dh, dass sie nicht versehentlich den richtigen Wert zurückgibt ). Möglicherweise haben sie Schüler getroffen, die nicht wissen, was in diesem Fall passieren würde, dass die Schleife null Mal ausgeführt werden kann usw. Wenn Sie sich mit einer Unterrichtseinstellung befassen, ist es am besten, sich über Annahmen und Eckfälle besonders klar zu sein. ..
ilkkachu
36
@ilkkachu Wenn dies der Fall ist, sollte der Lehrer eine Aufgabe verteilen, für die ein solcher Test erforderlich ist, damit er ordnungsgemäß funktioniert.
Klutt
38
@ilkkachu Nun, ich verstehe Ihren Standpunkt im allgemeinen Fall, weil ich Klarheit über Kürze absolut schätze - und das fast immer, nicht unbedingt nur in einem pädagogischen Umfeld. Trotzdem ist Kürze Klarheit, und wenn Sie dafür sorgen können, dass der Hauptcode sowohl den allgemeinen Fall als auch den Randfall behandelt, ohne den Code (und die Abdeckungsanalyse) mit Sonderfällen für die Randfälle zu überladen , das ist eine schöne Sache! Ich denke, es ist etwas, das man selbst als Anfänger schätzen sollte.
Steve Summit
49
@ilkkachu Mit diesem Argument sollten Sie sicherlich auch Tests für n = 1 usw. hinzufügen. Es gibt nichts Besonderes n=0. Das Einführen unnötiger Verzweigungen und Komplikationen macht den Code nicht einfacher, sondern auch schwieriger, da Sie jetzt nicht nur zeigen müssen, dass der allgemeine Algorithmus korrekt ist, sondern auch alle Sonderfälle separat betrachten müssen.
Voo
107

Ihr Code ist vollkommen in Ordnung

Sie sind absolut richtig und Ihr Lehrer ist falsch. Es gibt absolut keinen Grund, diese zusätzliche Komplexität hinzuzufügen, da sie das Ergebnis überhaupt nicht beeinflusst. Es führt sogar einen Fehler ein. (Siehe unten)

Erstens ist die separate Prüfung, ob nNull ist, offensichtlich völlig unnötig und dies ist sehr einfach zu realisieren. Um ehrlich zu sein, stelle ich die Kompetenz Ihres Lehrers in Frage, wenn er Einwände dagegen hat. Aber ich denke, jeder kann von Zeit zu Zeit einen Hirnfurz haben. Ich denke jedoch, dasswhile(n) dies geändert werden sollte, while(n != 0)da es ein wenig mehr Klarheit ohne auch nur eine zusätzliche Zeile zu kosten. Es ist jedoch eine Kleinigkeit.

Der zweite ist etwas verständlicher, aber er liegt immer noch falsch.

Dies ist der C11-Standard 6.5.5.p6 sagt :

Wenn der Quotient a / b darstellbar ist, ist der Ausdruck (a / b) * b + a% b gleich a; Andernfalls ist das Verhalten von a / b und a% b nicht definiert.

Die Fußnote sagt dies:

Dies wird oft als "Abschneiden gegen Null" bezeichnet.

Abschneiden gegen Null bedeutet, dass der Absolutwert für a/bgleich dem Absolutwert (-a)/bfür alle ista und ist b, was wiederum bedeutet, dass Ihr Code vollkommen in Ordnung ist.

Modulo ist eine einfache Mathematik, kann aber nicht intuitiv sein

Ihr Lehrer hat jedoch den Punkt, dass Sie vorsichtig sein sollten, da die Tatsache, dass Sie das Ergebnis quadrieren, hier tatsächlich entscheidend ist. Das Berechnen a%bgemäß der obigen Definition ist einfach, kann aber Ihrer Intuition widersprechen. Bei Multiplikation und Division ist das Ergebnis positiv, wenn die Operanden das gleiche Vorzeichen haben. Aber wenn es um Modulo geht, hat das Ergebnis das gleiche Vorzeichen wie der erste Operand. Der zweite Operand beeinflusst das Vorzeichen überhaupt nicht. Zum Beispiel 7%3==1aber (-7)%(-3)==(-1).

Hier ist ein Ausschnitt, der dies demonstriert:

$ 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

Ironischerweise hat Ihr Lehrer seinen Standpunkt bewiesen, indem er sich geirrt hat.

Der Code Ihres Lehrers ist fehlerhaft

Ja, das ist es tatsächlich. Wenn die Eingabe INT_MINUND die Architektur das Zweierkomplement ist UND das Bitmuster, bei dem das Vorzeichenbit 1 ist und alle Wertbits 0 sind, KEIN Trap-Wert ist (die Verwendung des Zweierkomplements ohne Trap-Werte ist sehr häufig), führt der Code Ihres Lehrers zu einem undefinierten Verhalten auf der Linie n = n * (-1). Ihr Code ist - wenn auch nur geringfügig - besser als sein. Und wenn man bedenkt, einen kleinen Fehler einzuführen, indem man den Code unnötig komplex macht und einen Wert von absolut Null erreicht, würde ich sagen, dass Ihr Code VIEL besser ist.

Mit anderen Worten, in Kompilierungen mit INT_MIN = -32768 (obwohl die resultierende Funktion keine Eingabe von <-32768 oder> 32767 empfangen kann) verursacht die gültige Eingabe von -32768 undefiniertes Verhalten, da das Ergebnis von - (- 32768i16) kann nicht als 16-Bit-Ganzzahl ausgedrückt werden. (Tatsächlich würde -32768 wahrscheinlich kein falsches Ergebnis verursachen, da - (- 32768i16) normalerweise -32768i16 ergibt und Ihr Programm negative Zahlen korrekt verarbeitet.) (SHRT_MIN kann je nach Compiler -32768 oder -32767 sein.)

Ihr Lehrer hat jedoch ausdrücklich angegeben, dass ndies im Bereich [-10 ^ 7; 10 ^ 7]. Eine 16-Bit-Ganzzahl ist zu klein. Sie müssten [mindestens] eine 32-Bit-Ganzzahl verwenden. Die Verwendung intscheint seinen Code sicher zu machen, außer dass dies intnicht unbedingt eine 32-Bit-Ganzzahl ist. Wenn Sie für eine 16-Bit-Architektur kompilieren, sind beide Codefragmente fehlerhaft. Aber Ihr Code ist immer noch viel besser, weil dieses Szenario den INT_MINoben erwähnten Fehler mit seiner Version wieder einführt . Um dies zu vermeiden, können Sie longstattdessen schreiben int, was in beiden Architekturen eine 32-Bit-Ganzzahl ist. A kann longgarantiert jeden Wert im Bereich [-2147483647; 2147483647]. C11 Standard 5.2.4.2.1 LONG_MIN ist häufig-2147483648aber der maximal zulässige Wert (ja, maximal, es ist eine negative Zahl) fürLONG_MINist 2147483647.

Welche Änderungen würde ich an Ihrem Code vornehmen?

Ihr Code ist in Ordnung, es handelt sich also nicht wirklich um Beschwerden. Es ist eher so, wenn ich wirklich, wirklich etwas über Ihren Code sagen muss, gibt es einige kleine Dinge, die es nur ein kleines bisschen klarer machen könnten.

  • Die Namen der Variablen könnten etwas besser sein, aber es ist eine kurze Funktion, die leicht zu verstehen ist, also keine große Sache.
  • Sie können den Zustand von nbis ändern n!=0. Semantisch ist es 100% äquivalent, aber es macht es ein bisschen klarer.
  • Verschieben Sie die Deklaration von c(in die ich umbenannt habe digit) in die while-Schleife, da sie nur dort verwendet wird.
  • Ändern Sie den Argumenttyp, longum sicherzustellen, dass er den gesamten Eingabesatz verarbeiten kann.
int sum_of_digits_squared(long n) 
{
    long sum = 0;

    while (n != 0) {
        int digit = n % 10;
        sum += (digit * digit);
        n /= 10;
    }

    return sum;
}

Tatsächlich kann dies etwas irreführend sein, da die Variable - wie oben erwähnt - digiteinen negativen Wert erhalten kann, eine Ziffer an sich jedoch niemals positiv oder negativ ist. Es gibt ein paar Möglichkeiten, dies zu umgehen, aber das ist WIRKLICH ein Trottel, und ich würde mich nicht für so kleine Details interessieren. Insbesondere die separate Funktion für die letzte Ziffer geht zu weit. Ironischerweise ist dies eines der Dinge, die der Code Ihres Lehrers tatsächlich löst.

  • Wechseln Sie sum += (digit * digit)zu sum += ((n%10)*(n%10))und überspringen Sie die Variable digitvollständig.
  • Ändern Sie das Vorzeichen, digitwenn negativ. Ich würde jedoch dringend davon abraten, den Code komplexer zu gestalten, nur um einen Variablennamen sinnvoll zu machen. Das ist ein sehr starker Code-Geruch.
  • Erstellen Sie eine separate Funktion, die die letzte Ziffer extrahiert. int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }Dies ist nützlich, wenn Sie diese Funktion an einer anderen Stelle verwenden möchten.
  • Nennen Sie es einfach so, cwie Sie es ursprünglich getan haben. Dieser Variablenname gibt keine nützlichen Informationen, ist aber auch nicht irreführend.

Aber um ehrlich zu sein, sollten Sie an dieser Stelle zu einer wichtigeren Arbeit übergehen. :) :)

klutt
quelle
19
Wenn die Eingabe lautet INT_MINund die Architektur das Zweierkomplement verwendet (was sehr häufig vorkommt), führt der Code Ihres Lehrers zu undefiniertem Verhalten. Autsch. Das wird Spuren hinterlassen. ;-)
Andrew Henle
5
Es sollte erwähnt werden, dass (a/b)*b + a%b ≡ ader Code des OP zusätzlich von der Tatsache abhängt, dass /auf Null gerundet wird, und dass (-c)*(-c) ≡ c*c. Es könnte argumentiert werden, dass die zusätzlichen Kontrollen gerechtfertigt sind, obwohl der Standard all dies garantiert, da dies nicht offensichtlich genug ist. (Natürlich könnte man genauso gut argumentieren, dass es eher einen Kommentar geben sollte, der die relevanten Standardabschnitte verknüpft, aber die Stilrichtlinien variieren.)
Links um den
7
@ MartinRosenau Du sagst "könnte". Sind Sie sicher, dass dies tatsächlich geschieht oder dass es nach dem Standard oder etwas anderem zulässig ist, oder spekulieren Sie nur?
Klutt
6
@ MartinRosenau: Ok, aber mit diesen Schaltern würde es nicht mehr C machen. GCC / Clang haben keine Schalter, die die Ganzzahldivision auf einer mir bekannten ISA aufheben. Auch wenn das Ignorieren des Vorzeichenbits möglicherweise zu einer Beschleunigung führen könnte, wenn die normale multiplikative Inverse für konstante Teiler verwendet wird. (Aber alle ISAs, von denen ich weiß, dass sie von einem Hardware-Divisionsbefehl auf C99-Weise implementiert werden und gegen Null abgeschnitten werden, sodass C %und /Operatoren nur idivauf x86 oder sdivauf ARM oder was auch immer kompiliert werden können schnellere Code-Gen für Teiler mit konstanter Kompilierungszeit)
Peter Cordes
5
@ TonyK AFIK, so wird es normalerweise gelöst, aber nach dem Standard ist es UB.
Klutt
20

Ich mag weder deine Version noch die deines Lehrers ganz. Die Version Ihres Lehrers führt die zusätzlichen Tests durch, auf die Sie richtig hinweisen, und ist nicht erforderlich. Der Mod-Operator von C ist kein richtiger mathematischer Mod: Eine negative Zahl mod 10 erzeugt ein negatives Ergebnis (der richtige mathematische Modul ist immer nicht negativ). Aber da Sie es trotzdem quadrieren, kein Unterschied.

Aber das ist alles andere als offensichtlich, deshalb würde ich Ihrem Code nicht die Überprüfungen Ihres Lehrers hinzufügen, sondern einen großen Kommentar, der erklärt, warum es funktioniert. Z.B:

/ * HINWEIS: Dies funktioniert für negative Werte, da der Modul quadriert wird * /

Lee Daniel Crocker
quelle
9
Cs %wird am besten als Rest bezeichnet , da dies auch für signierte Typen der Fall ist.
Peter Cordes
15
Das Quadrieren ist wichtig, aber ich denke, das ist der offensichtliche Teil. Es sollte darauf hingewiesen werden, dass (zB) -7 % 10 tatsächlich-7 eher als 3 sein wird.
Jacob Raihle
5
"Richtiger mathematischer Modul" bedeutet nichts. Der richtige Begriff ist "Euklidisches Modulo" (beachten Sie das Suffix!) Und das ist in der Tat der %Operator von C.
Jan Hudec
Ich mag diese Antwort, weil sie die Frage nach mehreren Möglichkeiten zur Interpretation von Modulo regelt. Überlassen Sie so etwas niemals dem Zufall / der Interpretation. Dies ist kein Code Golf.
Harper - Stellen Sie Monica
1
"Der richtige mathematische Modul ist immer nicht negativ" - Nicht wirklich. Das Ergebnis einer Modulo-Operation ist eine Äquivalenzklasse , aber es ist üblich, das Ergebnis als die kleinste nicht negative Zahl zu behandeln, die zu dieser Klasse gehört.
Klutt
10

HINWEIS: Während ich diese Antwort schrieb, haben Sie klargestellt, dass Sie C verwenden. Der Großteil meiner Antwort bezieht sich auf C ++. Da Ihr Titel jedoch immer noch C ++ enthält und die Frage immer noch mit C ++ gekennzeichnet ist, habe ich mich trotzdem für eine Antwort entschieden, falls dies für andere Personen noch nützlich ist, zumal die meisten Antworten, die ich bisher gesehen habe, größtenteils unbefriedigend sind.

In modernem C ++ (Hinweis: Ich weiß nicht wirklich, wo C dazu steht) scheint Ihr Professor in beiden Punkten falsch zu liegen.

Erstens ist dieser Teil genau hier:

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

In C ++ ist dies im Grunde dasselbe wie :

if (!n) {
        return 0;
}

Das heißt, Ihre Zeit entspricht so etwas:

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

Das heißt, da Sie nur in Ihrem if beenden, wenn die Weile sowieso nicht ausgeführt wird, gibt es wirklich keinen Grund, dies zu setzen, wenn hier, da das, was Sie nach der Schleife tun, und im if sowieso gleichwertig sind. Obwohl ich sagen sollte, dass dies aus irgendeinem Grund anders war, müssten Sie dies haben, wenn.

Also wirklich, diese if-Anweisung ist nicht besonders nützlich, wenn ich mich nicht irre.

Im zweiten Teil wird es haarig:

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

Das Herzstück des Problems ist, was die Ausgabe des Moduls einer negativen Zahl ausgibt.

In modernem C ++ scheint dies größtenteils gut definiert zu sein :

Der binäre / Operator ergibt den Quotienten, und der binäre% -Operator ergibt den Rest aus der Division des ersten Ausdrucks durch den zweiten. Wenn der zweite Operand von / oder% Null ist, ist das Verhalten undefiniert. Für integrale Operanden liefert der Operator / den algebraischen Quotienten, wobei jeder gebrochene Teil verworfen wird. Wenn der Quotient a / b in der Art des Ergebnisses darstellbar ist, ist (a / b) * b + a% b gleich a.

Und später:

Wenn beide Operanden nicht negativ sind, ist der Rest nicht negativ. Wenn nicht, ist das Vorzeichen des Restes implementierungsdefiniert.

Wie das Poster der zitierten Antwort richtig zeigt, ist der wichtige Teil dieser Gleichung hier:

(a / b) * b + a% b

Wenn Sie ein Beispiel für Ihren Fall nehmen, erhalten Sie ungefähr Folgendes:

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

Der einzige Haken ist die letzte Zeile:

Wenn beide Operanden nicht negativ sind, ist der Rest nicht negativ. Wenn nicht, ist das Vorzeichen des Restes implementierungsdefiniert.

Das bedeutet, dass in einem solchen Fall nur das Vorzeichen implementierungsdefiniert zu sein scheint. Das sollte in Ihrem Fall kein Problem sein, weil Sie diesen Wert sowieso quadrieren.

Beachten Sie jedoch, dass dies nicht unbedingt für frühere Versionen von C ++ oder C99 gilt. Wenn Ihr Professor dies verwendet, könnte dies der Grund sein.


EDIT: Nein, ich liege falsch. Dies scheint auch für C99 oder höher der Fall zu sein :

C99 erfordert Folgendes, wenn a / b darstellbar ist:

(a / b) * b + a% b ist gleich a

Und noch ein Ort :

Wenn ganze Zahlen geteilt werden und die Division ungenau ist, wenn beide Operanden positiv sind, ist das Ergebnis des Operators / die größte ganze Zahl, die kleiner als der algebraische Quotient ist, und das Ergebnis des Operators% ist positiv. Wenn einer der Operanden negativ ist, ist die Implementierung definiert, ob das Ergebnis des Operators / die größte Ganzzahl ist, die kleiner als der algebraische Quotient ist, oder die kleinste Ganzzahl, die größer als der algebraische Quotient ist, ebenso wie das Vorzeichen des Ergebnisses des Operators%. Wenn der Quotient a / b darstellbar ist, ist der Ausdruck (a / b) * b + a% b gleich a.

Gibt entweder ANSI C oder ISO C an, was -5% 10 sein soll?

Also ja. Selbst in C99 scheint dies Sie nicht zu beeinträchtigen. Die Gleichung ist dieselbe.

Chipster
quelle
1
Die von Ihnen zitierten Teile unterstützen diese Antwort nicht. "das Zeichen des Restes ist die definierte Implementierung" bedeutet nicht, (-1)%10dass produziert werden könnte -1oder 1; es bedeutet, dass es produzieren könnte -1oder 9, und im letzteren Fall (-1)/10wird es produzieren -1und der OP-Code wird niemals enden.
Stewbasic
Könnten Sie auf eine Quelle dafür verweisen? Es fällt mir sehr schwer zu glauben, dass (-1) / 10 -1 ist. Das sollte 0 sein. Außerdem scheint (-1)% 10 = 9 die maßgebliche Gleichung zu verletzen.
Chipster
1
@Chipster, beginne mit (a/b)*b + a%b == aund lass dann a=-1; b=10geben (-1/10)*10 + (-1)%10 == -1. Wenn nun -1/10tatsächlich abgerundet wird (in Richtung -inf), dann haben wir (-1/10)*10 == -10, und Sie müssen haben, (-1)%10 == 9damit die erste Gleichung übereinstimmt. Wie in den anderen Antworten heißt es nicht, wie es innerhalb der aktuellen Standards funktioniert, aber so hat es früher funktioniert. Es geht nicht wirklich um das Vorzeichen des Restes als solches, sondern darum, wie die Division rundet und was der Rest dann sein muss , um die Gleichung zu erfüllen.
Ilkkachu
1
@Chipster Die Quelle sind die von Ihnen zitierten Schnipsel. Beachten Sie, dass (-1)*10+9=-1also die Auswahl (-1)/10=-1und (-1)%10=9nicht gegen die maßgebliche Gleichung verstößt. Andererseits kann die Wahl (-1)%10=1die maßgebliche Gleichung nicht erfüllen, egal wie (-1)/10gewählt wird; Es gibt keine ganze Zahl, qso dass q*10+1=-1.
Stewbasic
8

Wie andere betont haben, ist die spezielle Behandlung für n == 0 Unsinn, da für jeden ernsthaften C-Programmierer offensichtlich ist, dass "while (n)" die Arbeit erledigt.

Das Verhalten für n <0 ist nicht so offensichtlich, deshalb würde ich diese 2 Codezeilen lieber sehen:

if (n < 0) 
    n = -n;

oder zumindest einen Kommentar:

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

Ehrlich gesagt, zu welcher Zeit haben Sie angefangen zu überlegen, dass n negativ sein könnte? Beim Schreiben des Codes oder beim Lesen der Bemerkungen Ihres Lehrers?

CB
quelle
1
Unabhängig davon, ob N negativ ist, ist N im Quadrat positiv. Warum also überhaupt das Schild entfernen? -3 * -3 = 9; 3 * 3 = 9. Oder hat sich die Mathematik in den letzten 30 Jahren geändert, seit ich das gelernt habe?
Merovex
2
@CB Um fair zu sein, habe ich nicht einmal bemerkt, dass n negativ sein könnte, während ich den Test schrieb, aber als er zurückkam, hatte ich nur das Gefühl, dass die while-Schleife nicht übersprungen würde, selbst wenn die Zahl negativ ist. Ich habe einige Tests auf meinem Computer durchgeführt und dies hat meine Skepsis bestätigt. Danach habe ich diese Frage gestellt. Also nein, ich habe beim Schreiben des Codes nicht so tief nachgedacht.
user010517720
5

Dies erinnert mich an eine Aufgabe, bei der ich versagt habe

Schon in den 90ern. Der Dozent hatte sich über Schleifen Gedanken gemacht, und kurz gesagt, unsere Aufgabe war es, eine Funktion zu schreiben, die die Anzahl der Ziffern für eine bestimmte ganze Zahl> 0 zurückgibt.

So zum Beispiel der Anzahl der Stellen in 321wäre 3.

Obwohl in der Aufgabe lediglich gesagt wurde, dass eine Funktion geschrieben werden soll, die die Anzahl der Ziffern zurückgibt, wurde erwartet, dass wir eine Schleife verwenden, die durch 10 geteilt wird, bis ... Sie sie erhalten, wie in der Vorlesung beschrieben .

Aber die Verwendung von Schleifen wurde nicht explizit angegeben, so dass ich: took the log, stripped away the decimals, added 1und anschließend vor der ganzen Klasse lambastiert wurde.

Der Zweck der Aufgabe war es, unser Verständnis dessen zu testen, was wir während der Vorlesungen gelernt hatten . Durch die Vorlesung, die ich erhielt, erfuhr ich, dass der Computerlehrer ein bisschen ein Idiot war (aber vielleicht ein Idiot mit einem Plan?)


In Ihrer Situation:

Schreiben Sie eine Funktion in C / C ++, die die Summe der Ziffern der quadrierten Zahl zurückgibt

Ich hätte definitiv zwei Antworten gegeben:

  • die richtige Antwort (Quadrieren der Zahl zuerst) und
  • die falsche Antwort im Einklang mit dem Beispiel, nur um ihn glücklich zu machen ;-)
Langsamer Lerner
quelle
5
Und auch ein dritter, der die Summe der Ziffern quadriert?
Kriss
@kriss - ja, ich bin nicht so schlau :-(
SlowLearner
1
Ich hatte auch meinen Anteil an zu vagen Aufgaben in meiner Studentenzeit. Ein Lehrer wollte ein bisschen Manipulationsbibliothek, aber er war von meinem Code überrascht und sagte, dass es nicht funktioniert. Ich musste ihn darauf hinweisen, dass er Endianness in seiner Aufgabe nie definiert hat und er ein niedrigeres Bit als Bit 0 gewählt hat, während ich die andere Wahl getroffen habe. Der einzige ärgerliche Teil ist, dass er hätte herausfinden können, wo der Unterschied war, ohne dass ich es ihm gesagt hätte.
Kriss
1

Im Allgemeinen werden bei Aufgaben nicht alle Noten vergeben, nur weil der Code funktioniert. Sie erhalten auch Noten dafür, dass eine Lösung einfach zu lesen, effizient und elegant ist. Diese Dinge schließen sich nicht immer gegenseitig aus.

Eine, die ich nicht genug verstehen kann, ist "Verwenden Sie aussagekräftige Variablennamen" .

In Ihrem Beispiel macht es keinen großen Unterschied, aber wenn Sie an einem Projekt mit Millionen Zeilen Code-Lesbarkeit arbeiten, wird dies sehr wichtig.

Eine andere Sache, die ich mit C-Code sehe, sind Leute, die versuchen, klug auszusehen. Anstatt while (n! = 0) zu verwenden, zeige ich allen, wie schlau ich bin, indem ich while (n) schreibe, weil es dasselbe bedeutet. Nun, es funktioniert in dem Compiler, den Sie haben, aber wie Sie vorgeschlagen haben, hat die ältere Version Ihres Lehrers es nicht auf die gleiche Weise implementiert.

Ein häufiges Beispiel ist das Verweisen auf einen Index in einem Array, während dieser gleichzeitig erhöht wird. Zahlen [i ++] = iPrime;

Jetzt muss der nächste Programmierer, der an dem Code arbeitet, wissen, ob ich vor oder nach der Zuweisung inkrementiert werde, damit jemand angeben kann.

Ein Megabyte Speicherplatz ist billiger als eine Rolle Toilettenpapier. Um Klarheit zu schaffen, anstatt Platz zu sparen, sind Ihre Programmierkollegen glücklicher.

Paul McCarthy
quelle
2
Ich habe nur ein paar Mal in C programmiert und weiß, dass die ++iSchritte vor der Auswertung und i++danach inkrementiert werden. while(n)ist auch eine gemeinsame Sprachfunktion. Basierend auf dieser Logik habe ich viel Code gesehen if (foo == TRUE). Ich stimme jedoch den Variablennamen zu.
Alan Ocallaghan
3
Im Allgemeinen ist das kein schlechter Rat, aber das Vermeiden grundlegender Sprachfunktionen (auf die die Leute ohnehin unweigerlich stoßen werden) zum Zweck der defensiven Programmierung ist übertrieben. Kurzer, klarer Code ist ohnehin oft besser lesbar. Wir sprechen hier nicht von verrückten Perl- oder Bash-Einzeilern, sondern nur von wirklich grundlegenden Sprachfunktionen.
Alan Ocallaghan
1
Ich bin mir nicht sicher, wofür die Abstimmungen zu dieser Antwort gegeben wurden. Für mich ist alles, was hier gesagt wird, wahr und wichtig und ein guter Rat für jeden Entwickler. Besonders der Smart-Ass-Programmier-Teil, obwohl er while(n)nicht das schlechteste Beispiel dafür ist (ich "mag" if(strcmp(one, two))umso mehr)
Kai Huppmann
1
Außerdem sollten Sie wirklich niemanden zulassen, der den Unterschied zwischen C-Code, der in der Produktion verwendet werden soll, nicht kennt, i++und ihn ++iändern.
Klutt
2
@PaulMcCarthy Wir sind beide für die Lesbarkeit von Code. Wir sind uns jedoch nicht einig darüber, was das bedeutet. Es ist auch nicht objektiv. Was für eine Person leicht zu lesen ist, kann für eine andere Person schwierig sein. Persönlichkeit und Hintergrundwissen beeinflussen dies stark. Mein Hauptpunkt ist, dass Sie nicht die maximale Lesbarkeit erhalten, wenn Sie einige Regeln blind befolgen.
Klutt
0

Ich würde nicht darüber streiten, ob die ursprüngliche oder die moderne Definition von '%' besser ist, aber jeder, der zwei return-Anweisungen in eine so kurze Funktion schreibt, sollte C-Programmierung überhaupt nicht unterrichten. Zusätzliche Rückgabe ist eine goto-Anweisung, und wir verwenden goto in C nicht. Außerdem hätte der Code ohne die Nullprüfung das gleiche Ergebnis, zusätzliche Rückgabe erschwerte das Lesen.

Peter Krassoi
quelle
4
"Extra return ist eine goto-Anweisung und wir verwenden goto in C nicht." - Das ist eine Kombination aus einer sehr breiten Verallgemeinerung und einer sehr weiten Strecke.
SS Anne
1
"Wir" verwenden auf jeden Fall goto in C. Daran ist nichts auszusetzen.
Klutt
1
Auch an einer Funktion wieint 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 Ich befürworte keinen schwer lesbaren Code, aber ich habe unzählige Beispiele gesehen, bei denen der Code wie ein komplettes Durcheinander aussieht, nur um ein einfaches Goto oder eine zusätzliche Rückgabeanweisung zu vermeiden. Hier ist ein Code mit angemessener Verwendung von goto. Ich fordere Sie die GOTO - Anweisungen zu entfernen , während gleichzeitig der Code leichter zu lesen und mantain: pastebin.com/aNaGb66Q
Klutt
1
@PeterKrassoi Bitte zeigen Sie auch Ihre Version davon: pastebin.com/qBmR78KA
Klutt
0

Die Problemstellung ist verwirrend, aber das numerische Beispiel verdeutlicht die Bedeutung der Summe der Ziffern der quadrierten Zahl . Hier ist eine verbesserte Version:

Schreiben Sie eine Funktion in die gemeinsame Teilmenge von C und C ++, die eine Ganzzahl nim Bereich [-10 7 , 10 7 ] annimmt und die Summe der Quadrate der Ziffern ihrer Darstellung in Basis 10 zurückgibt. Beispiel: Wenn nja 123, Ihre Funktion sollte zurückkehren 14(1 2 + 2 2 + 3 2 = 14).

Die Funktion, die Sie geschrieben haben, ist bis auf 2 Details in Ordnung:

  • Das Argument sollte einen Typ haben long, der für alle Werte im angegebenen Bereich geeignet longist, da der Typ durch den C-Standard garantiert, dass er mindestens 31 Wertbits aufweist, daher ein Bereich, der ausreicht, um alle Werte in [-10 7 , 10 7 ] darzustellen . (Beachten Sie, dass der Typ intfür den Rückgabetyp ausreicht, dessen Maximalwert ist 568.)
  • Das Verhalten %für negative Operanden ist nicht intuitiv und seine Spezifikation variiert zwischen dem C99-Standard und früheren Ausgaben. Sie sollten dokumentieren, warum Ihr Ansatz auch für negative Eingaben gültig ist.

Hier ist eine modifizierte Version:

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;
}

Die Antwort des Lehrers weist mehrere Mängel auf:

  • Typ hat intmöglicherweise einen unzureichenden Wertebereich.
  • Es ist nicht erforderlich, den Wert als Sonderfall festzulegen 0.
  • Das Negieren negativer Werte ist nicht erforderlich und hat möglicherweise ein undefiniertes Verhalten für n = INT_MIN.

Angesichts der zusätzlichen Einschränkungen in der Problemstellung (C99 und Wertebereich für n) ist nur der erste Fehler ein Problem. Der zusätzliche Code liefert immer noch die richtigen Antworten.

Sie sollten in diesem Test eine gute Note bekommen, aber die Erklärung ist in einem schriftlichen Test erforderlich, um Ihr Verständnis der Probleme für das Negative zu zeigen n, andernfalls kann der Lehrer annehmen, dass Sie sich dessen nicht bewusst waren und einfach Glück hatten. In einer mündlichen Prüfung hätten Sie eine Frage bekommen und Ihre Antwort hätte sie getroffen.

chqrlie
quelle