Hallo liebes Forum,
ich habe mal eine Frage bezüglich meiner Check Routine für den Euronoten
Code. Ich werde das Gefühl nicht los, dass das noch einfacher geht. Ich
finde es persönlich lang, wobei ich das schon seperat in eine eigene
Datei mit Header ausgelagert habe.
Kurz um euch die Berechnungsroutine darzustellen:
Banknoten Code einlesen CheckCode("CODE"):
- Prüfe ob es exakt 12 Zeichen sind
- Prüfe ob erstes Zeichen ein Buchstabe ist
- Prüfe ob zweites Zeichen ein Buchstabe ist
- Ist Nur Zeichen 1 ein Buchstabe -> Rufe CheckCodeOldEuro() auf
- Sind Zeichen 1 und Zeichen 2 Buchstaben -> Rufe CheckCodeNewEuro() auf
- Sind es nicht 12 Zeichen abbruch
Funktion CheckCodeOldEuro()
- Prüfe ob nachfolgende Zeichen Zahlen sind
- Wenn Ja, dann bilde dort schon mal die Quersumme
- Wenn Nein, Abbruch
- Wandle Buchstabe in Zahl um (A=1, B=2, C=3 etc...)
- Ist die umgewandlete Zahl höher als 9, also 10 oder mehr
- Wenn ja Splitte die Zahlen auf und addiere auf zur Quersumme
- Beispiel: Ist die Zahl 11, dann mache Quersumme += 1 + 1
- Ansonsten einfach auf Quersumme aufrechnen
- Dann Berechne den Divisionsrest -> Quersumme % 9
- Berechne Differenz -> Differenz = 8 - Divisionsrest
- Übrprüfe Letzte Zahl im Kode, ob die übereinstimmt mit Differenz
- Wenn Ja, dann ist die Note Echt
- Wenn Nein, handelt es sich um eine Fälschung
Funktion CheckCodeNewEuro()
- Prüfe ob nachfolgende Zeichen Zahlen sind
- Wenn Ja, dann bilde dort schon mal die Quersumme
- Wenn Nein, Abbruch
- Wandle Buchstaben in Zahlen um (A=1, B=2, C=3 etc...)
- Sind die umgewandelten Zahlen höher als 9, also 10 oder mehr
- Wenn ja Splitte die Zahlen auf und addiere auf zur Quersumme
- Beispiel: Ist die Zahl 11, dann mache Quersumme += 1 + 1
- Ansonsten einfach auf Quersumme aufrechnen
- Dann Berechne den Divisionsrest -> Quersumme % 9
- Berechne Differenz -> Differenz = 7 - Divisionsrest
- Übrprüfe Letzte Zahl im Kode, ob die übereinstimmt mit Differenz
- Wenn Ja, dann ist die Note Echt
- Wenn Nein, handelt es sich um eine Fälschung
Ich hoffe ihr habt das soweit verstanden. NewEuro sind z.B. die neuen 5
und 10 Euro Scheine, da gibt es immer 2 Buchstaben, der erste gibt die
Druckerei an, der zweite eine Interne Bezeichnung vom Drucker(?). Bei
den alten Euro Scheinen ist das der Ländercode.
Die Letzte Zahl ist immer die Prüfziffer, die darf man nicht zur
Quersumme aufaddieren.
Ich hab beide Routinen mit meinem Geld getestet, das funktioniert
soweit, also ich hab keine Fälschungen xD
Eventuell gibts ja noch eine einfache Möglichkeit. Ich hoffe ihr könnt
den Code relativ gut lesen^^
Lieben Gruß
Dein Algorithmus, um einen Buchstaben in einen numerischen Wert
umzuwandeln ist ziemlich umständlich.
Statt
1
first[0]=toupper(string[0]);
2
first[1]=0;
3
4
f_position=strcspn(lookup,first);
genügt hier ein
1
f_position=toupper(string[0])-'@';
... sofern man sich auf den ASCII-Code und die Reihenfolge der Zeichen
darin verlässt. '@' ist das Zeichen, das unmittelbar vor 'A' kommt.
Mit isalpha kann man überprüfen, ob ein Zeichen ein Buchstabe ist;
Vergleiche mit der Konstanten 27 sind daher überflüssig.
Rufus Τ. F. schrieb:> ... sofern man sich auf den ASCII-Code und die Reihenfolge der Zeichen> darin verlässt. '@' ist das Zeichen, das unmittelbar vor 'A' kommt.
Dann könnte man auch 'A' - 1 schreiben und könnte sich den Kommentar
sparen, dass '@' das Zeichen genau vor dem 'A' ist ;-)
Ein paar Anmerkungen und Verbesserungsvorschläge:
Da höchstens ein Element der Struktur Error_t wahr werden kann, nimmt
man stattdessen besser ein Enum, das neben den einzelnen Fehlerfällen
auch einen Wert (üblicherweise den ersten) für "ok" enthält.
Statt den Fehlercode in einer globalen Variable zu speichern, würde ich
ihn als Funktionswert anstelle des bool zurückliefern.
Da die Funktionen CheckCodeOldEuro und CheckCodeNewEuro nur von
CheckCode aufgerufen werden und wegen der vielen globalen Variable
sinnvollerweise auch nur von dort aufgerufen werden sollten, würde ich
ihre Deklaration aus CheckCode.h streichen und sie stattdessen in
CheckCode.c als static deklarieren. Damit sind diese Funktionen für
andere C-Module verborgen, so dass sie auch nicht aus Versehen
aufgerufen werden können.
Warum kopierst du in CheckCode das Argument str in die neue, dynamisch
erzeugte Variable string? Reiche doch einfach str als Argument an die
beiden Funktionen CheckCodeOldEuro und CheckCodeNewEuro weiter. Da str
nirgends überschrieben wird, ist das überhaupt kein Problem und eine
weitere globale Variable (string) kann wegfallen, ebenso die Funktion
CheckFree.
Wo wird CheckFree überhaupt aufgerufen? Von extern? Das wäre erst recht
ein Grund, auf die dynamisch erzeugte Variable zu verzichten.
Kann es sein, dass du den dynamischen Speicher für mehrere Aufrufe von
CheckCode nutzen möchtest? Wenn ja, gibt es ein Problem, wenn CheckCode
zuerst mit einem kurzen und danach mit einem langen String aufgerufen
wird, da in diesem Fall der Speicherplatz für den zweiten String nicht
ausreicht.
Die Berechnung der Prüfsumme ist viel einfacher, als du es in deinem
Text beschrieben hast:
Anstatt bei der Summenbildung die letzte Ziffer auszusparen, kannst du
die Summe auch gleich über alle Ziffern bilden und ihren Neunerrest dann
mit einem konstanten Wert vergleichen. Dieser konstante Wert ist sowohl
für die alten als auch die neuen Scheine 0, wenn man für die Buchstaben
ihren ASCII-Code (als Großbuchstaben, also 'A'->65, 'B'->66 usw.) und
für die Ziffern ihren numerischen Wert (also '0'->0, '1'->1 usw.)
summiert.
Für zweistellige Summanden (bei den ASCII-Codes) musst nicht gesondert
die Quersumme bilden, denn diese Quersumme ist nichts anderes als der
Neunerrest des Summanden, und es genügt, diesen ganz am Schluss von der
Gesamtsumme zu berechnen.
Im folgenden Code sind die obigen Bemerkungen umgesetzt:
Dabei werden keine globalen Variablen verwendet, und die alten und die
neuen Scheine werden in ein und derselben Routine behandelt.
Da ich gerade keinen neuen Schein zum Testen da habe, hoffe ich, dass
ich trotzdem keinen Mist gebaut habe ;-)
Edit:
Die Prüfung der letzten Ziffer für die neuen Scheine sieht bei dir so
aus:
1
diff=7-divrest;
2
3
if(diff!=string[11]-'0')
Das funktioniert nicht, wenn divrest = 8 und damit diff = -1 wird.
Müller schrieb:> - Wenn Ja, dann ist die Note Echt> - Wenn Nein, handelt es sich um eine Fälschung
Ich hoffe der Banknotenfälscher denkt auch dran eine ungültige Nummer
auf den Schein zu drucken.
Yalu danke für die ausführliche Hilfe. Ich habe auf der Suche nach einer
Möhlichkeit diesen Code zu prüfen, erstmal nichts gefunden per Google,
bin dann irgendwann mal auf einer Seite gelandet, wo das quasi so
beschrieben wurde. Dass es dann doch so einfach geht, ist natürlich
positiv, danke dir,
Ich werd das morgen mal testen.
Lieben Gruß
@NeinDanke
Wär ich nicht darauf gekommen, Mensch bist wohl ein Hecht wa? Cicalone
Yalus Lösung sieht sauber und bis auf einen Punkt korrekt aus.
Das Problem ist dieses Fragment:
1
>charc=*str++;
2
>if(n<2&&isupper(c))
Die ctype-Funktionen brauchen als Argument einen unsigned char oder
EOF, normalerweise also einen Wert zwischen -1 und 255. Ein "char c"
kann hier zu merkwürdigen Fehlern (inkl. Programmabstürzen!) führen.
Desweiteren werden, je nach locale, mehr als die 26 Zeichen A-Z als
Großbuchstaben akzeptiert (z.B. ÄÖÜ).
Aus diesen Gründen benutze ich die ctype-Funktionen kaum noch - ich
schreib dann lieber "if (n < 2 && c >= 'A' && c <= 'Z')". Das hat diese
Probleme nicht und EBCDIC ist eh tot ;-)
Mal wieder so eine nette Sache mit dem char...
asdfasd schrieb:> Die ctype-Funktionen brauchen als Argument einen unsigned char oder> EOF, normalerweise also einen Wert zwischen -1 und 255.
... das steht so in der Linux man page.
Der ISO Standard sagt: "a character representable as an unsigned char
or equal to the value of the macro EOF". Ein signed char ist doch wohl
representable als unsigned char, oder?
Ich gucke auch häufig auf http://www.cplusplus.com. Die sagen:
"Character to be checked, casted to an int, or EOF." Und haben auch ein
Beispiel:
> Müller schrieb:>> Ich hoffe der Banknotenfälscher denkt auch dran eine ungültige Nummer> auf den Schein zu drucken.
Genau aus diesem Grund habe ich das mit dem 13-€-Schein geschrieben...
:-)
asdfasd schrieb:> Die ctype-Funktionen brauchen als Argument einen unsigned char oder> EOF, normalerweise also einen Wert zwischen -1 und 255. Ein "char c"> kann hier zu merkwürdigen Fehlern (inkl. Programmabstürzen!) führen.
Stimmt, das habe ich nicht bedacht.
Die Programmabstürze bzw. Assertion-Abbrüche in einigen Implementationen
lassen sich dadurch vermeiden, dass man das Argument der is*-Funktionen
nach (unsigned char) castet.
Ich verstehe aber auch nicht ganz, warum für die is*-Funktionen EOF als
Argument zugelassen ist. Normalerweise prüft man doch die Bytes eines
Datenstroms auf EOF ab, bevor sie weiterverarbeitet werden. Dann
könnte das Argument einfach char sein, und der hässliche Cast wäre nicht
nötig.
> Desweiteren werden, je nach locale, mehr als die 26 Zeichen A-Z als> Großbuchstaben akzeptiert (z.B. ÄÖÜ).
Ja, korrekterweise sollte man deswegen in der Funktion CheckCode das
Locale temporär auf "C" setzen.
Hier ist eine Version der Funktion, die diese Dinge berücksichgtigt:
> Aus diesen Gründen benutze ich die ctype-Funktionen kaum noch
Ich auch nicht, deswegen bin ich auch über das Problem gestolpert :)
> ich schreib dann lieber "if (n < 2 && c >= 'A' && c <= 'Z')". Das hat> diese Probleme nicht und EBCDIC ist eh tot ;-)
Oh, sag so etwas nicht, da sind nämlich die Herren von IBM ganz anderer
Meinung ;-)
Alles in allem gesehen ist die Methode des TE mit dem Lookup-String
vielleicht doch nicht die schlechteste. Immerhin könnte man damit mit
einer kleinen Modifikation auch leicht abprüfen, ob der entsprechende
Buchstabencode für das Land bzw. die Druckerei überhaupt vergeben ist.
Hallo zusammen,
wenigstens ist nicht alles schlecht, was ich da wieder fabriziert habe.
eine Überprüfung des Länder Codes hatte ich auch noch geplant. Ich meine
irgendwo gelesen zu haben, dass einige Buchstaben sogar eine Fälschung
angeben.
Zu den Druckerei verwendeten Buchstaben habe ich jedoch auf Anhieb keine
Liste gefunden, ich werde aber mal genauer suchen.
Ich könnte ja bei der Eingabe, wie Yalu beschrieben hat, explizit auf
EOF prüfen, und dann könnte man ja auch auf den Cast verzichten.
Ich werde mich mal nachher dransetzen. Vielen Dank
Lieben Gruß
Hallo zusammen,
ich habe jetzt nochmal etwas geschrieben. Dein Beispiel habe ich
größtenteils übernommen. Länderkennung und Druckerei Kennung Überprüfung
ist da auch drinne, und sollte funktionieren.
Jedoch musste ich die Druckereikennungen auf Heap laden, eine normale
Iniatilisation auf den Stack mittels const char **kennung = {"..",
".."...
hat aus mir nicht näher bekannten Gründen nicht geklappt.
Ich konnte zwar das so hinschreiben ohne Fehlermeldung, kompiliert hat
er das auch. Dann wollte ich testen, ob der mir alle 24 ausgibt per
printf.
Er hat dann zwar die ersten 3 oder 4 ausgegeben, jedoch kam es direkt
danach zu einem segfault.
Weiss der Geier warum das so war.
Könnt ja mal drüber schauen, bestimmt kann man das noch extrem
vereinfachen.
Lieben Gruß
> Ich verstehe aber auch nicht ganz, warum für die is*-Funktionen EOF als> Argument zugelassen ist. Normalerweise prüft man doch die Bytes eines> Datenstroms auf EOF ab, bevor sie weiterverarbeitet werden.
Bequemlichkeit und Geschwindigkeit (als diese Funktionen entstanden hat
man noch Taktzyklen gezählt). Man konnte so seine Abfragen nach
Häufigkeit sortieren und EOF kam dann (da es "in" jeder Datei ja nur
einmal vorkommt) ganz nach hinten. Außerdem war "Text" nur 7 bit so
dass das signed/unsigned char Problem auf keinem Radar war (das
ctype-array war entspr auch nur 129 Byte groß).
> korrekterweise sollte man deswegen in der Funktion CheckCode das> Locale temporär auf "C" setzen.
Keine so gute Idee. Zum einen ist setlocale ein Schwergewicht (lädt
Dateien von Platte) und zum anderen, und viel wichtiger, setlocale ist
global und ändert das Locale aller Threads des Prozesses.
Der ANSI-C locale Kram ist ziemlicher Bockmist. Das war ein
Schnellschuss um mit möglichst wenig Aufwand (eine Zeile in main -
setlocale(LC_ALL, "");) Programme zu lokalisieren. Funktioniert leider
nur bei trivialen Programmen.
Dummerweise ist die API so beschissen, dass es nur ein entweder-oder
gibt, locales oder keine. Locale-unabhängiger Kode hat nun das Problem,
dass er locale-abhängige Funktionen vermeiden muss - problematisch
insbesondere bei implementationsempfindlichen Sachen wie strtod.
Bei mir gehen deshalb immer die Alarmglocken an, wenn ich in einem
Programm ein setlocale sehe - zu viele Programmierer sind sich der
Konsequenzen nicht bewusst.
Zurück zum Checker: warum willst du unbedingt die ctype-Funktionen
verwenden? Der Algorithmus funktioniert so eh nur mit ASCII und da sind
isupper und isdigit trivial[1].
> > EBCDIC ist eh tot ;-)>> Oh, sag so etwas nicht, da sind nämlich die Herren von IBM ganz> anderer Meinung ;-)
Für das Encoding sollen sie ewig bluten. EBCDIC-Support wird bei mir
absichtlich ignoriert - sollen sich die damit rumschlagen, die es
verbrochen haben ;-)
> [...] leicht abprüfen, ob der entsprechende Buchstabencode für das> Land bzw. die Druckerei überhaupt vergeben ist
Dann musst du aber auch noch ein Datum übergeben und eine Glaskugel für
die Zukunft einbauen ;-) Kommerzielle Softwarehäuser machen sowas um
Wartungsverträge zu rechtfertigen. Für sich selbst sollte man so einen
Schmu nicht machen. Diese Routine soll die Checksum und
(notwendigerweise) den lexikalischen Aufbau überprüfen[2]. Semantische
Checks (Länder-, Druckereikodes, Nummernbereiche, etc) gehören woanders
hin. "Do one thing and do it right" ;-)
[1] Die triviale Implementation ist heutzutage wohl sogar schneller.
[2] Btw, ein Test fehlt nocht: letzte Ziffer != '0'.
Müller schrieb:
> Länderkennung und Druckerei Kennung Überprüfung ist da auch drinne
Halte ich nicht viel von, s.o.
> Jedoch musste ich die Druckereikennungen auf Heap laden,
Wenn schon, dann doch ordentlich. Mal dir mal auf, wie deine Daten
jetzt im Speicher liegen und was du wirklich brauchst. Auch scheint es
mir etwas unnötig, die Daten bei jedem Check neu anzulegen.
> eine normale Iniatilisation auf den Stack [...] hat aus mir nicht> näher bekannten Gründen nicht geklappt.
Auf den Stack packt man so etwas eh nicht. Und wenn etwas nicht klappt
(obwohl man weiß, dass es gehen sollte), tut man gut daran,
herauszufinden, wo der Fehler ist. Dadurch lernt man am meisten ;-)
(Tipp: bei der Definition/Initialisierung brauchst du keine Sternchen.)
> explizit auf EOF prüfen
EOF ist ein Rückgabewert von getchar & co. In Strings wirst du das
nicht finden. Ein EOF-Test befreit dich aber eh nicht davon, die
Argumente von isigit & co nach unsigned char casten zu müssen (signed
char hat Werte von -128 bis 127, gültige Werte für isdigit & co sind -1
bis 255).
asdfasd schrieb:>> korrekterweise sollte man deswegen in der Funktion CheckCode das>> Locale temporär auf "C" setzen.>> Keine so gute Idee. Zum einen ist setlocale ein Schwergewicht (lädt> Dateien von Platte)
Kann man nicht davon ausgehen, dass wenigstens das C-Locale bereits
geladen ist? Das muss ja vor dem ersten setlocale-Aufruf auch schon
verfügbar gewesen sein.
Alternativ gäbe es ja noch die POSIX-Funktionen is*_l, denen man das
gewünschte Locale (das man beim Programmstart einmal anlegt) als
Argument übergibt. Leider sind diese lokalisierten ctype-Funktionen
(noch) nicht in den C-Statndard eingeflossen.
> und zum anderen, und viel wichtiger, setlocale ist global und ändert> das Locale aller Threads des Prozesses.
Ja, das ist natürlich blöd. In C++ ist diese ganze Lokalisierei deutlich
besser gelöst. Dort muss man das globale Locale praktisch nie ändern.
Müller schrieb:> Jedoch musste ich die Druckereikennungen auf Heap laden, eine normale> Iniatilisation auf den Stack mittels const char **kennung = {"..",> ".."...> hat aus mir nicht näher bekannten Gründen nicht geklappt.
Mit ={...} kannst du ein Array oder eine Struktur intialisieren, nicht
aber einen Zeiger. Anders als bei String-Literalen wird hier nicht
automatisch Speicher reserviert. Folgendes geht aber:
Die Variable kennungen ist also ein Array, desswen Elemente wiederum
Arrays mit jeweils 3 Chars sind.
Möglich wäre auch ein Array von Zeigern, die auf die einzelnen Strings
zeigen:
1
staticconstchar*kennungen[]={"EA","EB",...};
Hier wird aber zusätzlich Speicherplatz für die Zeiger benötigt, und der
Zugriff auf die Strings erfordert zusätzlich die Dereferenzierung dieser
Zeiger, so dass im vorliegenden Fall die erste Variante effizienter ist.
Ansonsten sollte aber auch deine Methode mit den mallocs funktionieren.
Du solltest aber in der Deklaration von kennungen das const weglassen,
da du ja in k_init Daten hineinkopierst. Der Compiler meckert das an,
wenn du die Warnungen aktivierst.
asdfasd schrieb:> EOF ist ein Rückgabewert von getchar & co. In Strings wirst du das> nicht finden. Ein EOF-Test befreit dich aber eh nicht davon, die> Argumente von isigit & co nach unsigned char casten zu müssen (signed> char hat Werte von -128 bis 127, gültige Werte für isdigit & co sind -1> bis 255).
Der User soll später die Möglichkeit haben, den Code einzugeben in die
Console. Der kann ja auf die Idee kommen und Ctrl+d eingeben.
Trotzdem hast du nicht unrecht, damit dass jedesmal neu Speicher geladen
wird. Mit Yalus Tipps werde ich das nochmal überarbeiten.
Isdigit verwende ich nicht. Mein Ziel ist es noch, komplett auf die
Ctype Funktionen zu verzichten
@Yalu
Danke für die Hilfe mit der Initialisierung, werde das entsprechend
ändern. Dann kann ich die init und free Sachen wieder rausschmeißen,
super. Macht den Code ja wieder übersichtlicher.
Schönen Abend noch!
So kurzes Update, habe jetzt beschriebene Aktualisierung vorgenommen.
Ich weiß die Ländererkennung / Druckerei Erkennung mag für manche nicht
so doll sein, aber die bleibt da ! ;)
Anregung sind weiterhin erwünscht.
Schönen Abend noch.