BILL CHECKER: UN VÉRIFICATEUR DE VALIDITÉ DE BILLETS EUROPÉENS

cs_Lucky92 Messages postés 180 Date d'inscription mercredi 22 décembre 2004 Statut Membre Dernière intervention 16 août 2012 - 14 avril 2011 à 00:01
LeFauve42 Messages postés 239 Date d'inscription vendredi 20 octobre 2006 Statut Membre Dernière intervention 20 avril 2009 - 20 avril 2011 à 12:20
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.

https://codes-sources.commentcamarche.net/source/53057-bill-checker-un-verificateur-de-validite-de-billets-europeens

LeFauve42 Messages postés 239 Date d'inscription vendredi 20 octobre 2006 Statut Membre Dernière intervention 20 avril 2009
20 avril 2011 à 12:20
> Grosse erreur à ne jamais faire dans un programme:
> {
> char code[12] = "";
> scanf("%s", &code);
> //...
> }

Accessoirement, code etant un tableau (et donc un pointeur) il faudrait ecrire :
scanf("%s", code);
Ou a la rigueur :
scanf("%s", &(code[0]));

Eric
verdy_p Messages postés 202 Date d'inscription vendredi 27 janvier 2006 Statut Membre Dernière intervention 29 janvier 2019
18 avril 2011 à 14:47
oups! mettre "valide = isValid(numero)" entre parenthèses avant le "?"
verdy_p Messages postés 202 Date d'inscription vendredi 27 janvier 2006 Statut Membre Dernière intervention 29 janvier 2019
18 avril 2011 à 14:45
@CptPingu: la fonction atol() ne peut pas marcher pour convertir un si grand nombre (elle retourne un "long" qui sur un système 32-bits est identique à un "int" limité à 32-bits signé).

Ma version corrigée est plus proche de ta première version, mais n'utilise aucun tampon interne supplémentaire, ni les couteuses et dangereuses sprintf(), atol():

#include <stdio.h>

int isValid(const char* code)
{
if (strlen(code) != 12)
return 0; // mauvaise longueur !
int somme = code[0] - 'A' + 2; // rang de la lettre, +1 pour le test du modulo 9 final égal à 8.
//note : dans un modulo 9, il est inutile de décomposer unités et dizaines de la somme ci-dessus.
if (somme < 2 || somme > 27)
return 0; // ce n'est pas une lettre de 'A' à 'Z'
// traiter les 11 chiffres après la lettre
for (int i = 11 ; i > 0; i--) {
const int chiffre = code[i] - 0;
if (chiffre < 0 || chiffre > 9)
return 0; // ce n'est pas un chiffre de '0' à '9'
somme += chiffre;
}
return somme % 9 == 0; // et non 8, car on a déjà ajouté 1 ci-dessus à la somme.
}

int main(int argc, char** argv)
{
char tampon[14]; // + 1 pour un caractère excédentaire, et +1 pour le caractère nul de fin de chaîne
char* numero;
int valide;

printf("Numéro de série du billet : ");
if (argc > 1)
printf("%s\n", numero = argv[1]); // prendre le numéro depuis la ligne de commande
else // permettre de lire jusqu'à un caractère de trop en plus (afin de tester les numéros trop longs)
scanf("%13s", numero = tampon); // tampon doit donc avoir au moins 14 positions (y compris le nul final).

printf("Ce numéro %s\n",
valide = isValid(numero) ? "semble correct." : "est invalide !");

// Permet de tester aussi le statut en sortie du programme (IF ERRORLEVEL 1 ...)
return !valide;
// Convention la plus courante (DOS/Windows/Unix/Mac...) :
// statut = 0 si résultat OK, 1 si résultat faux (>1 ou <0 si exception ou abandon prématuré)
// Note: sous VMS, la convention de statut est inversée :
// statut = 1 si résultat OK, 0 si résultat faux (>1 ou <0 si exception ou abandon prématuré)
}
verdy_p Messages postés 202 Date d'inscription vendredi 27 janvier 2006 Statut Membre Dernière intervention 29 janvier 2019
18 avril 2011 à 14:02
Pour les lettres valides (premier caractère du code billet), il semble qu'actuellement on n'ait que l'une des 12 lettres suivantes: LMNPRSTUVXYZ (donc moins que les 17 pays membres : à vérifier si depuis on a des séries imprimées à Chypre, Malte, Estonie, Slovaquie, Slovénie).

Note: Même si le Vatican, Saint-Marin et Monaco ont été autorisé officiellement à utiliser l'euro et même à émettre des pièces en euros, ils n'émettent pas de billets car ils ne sont pas membres.
Le Kosovo utilise encore officiellement l'euro uniquement dans le cadre provisoire des accords de Dayton (traité de paix international dans lequel l'Union européenne est garante), mais le Kosovo n'est pas encore reconnu comme un pays et ne peut pas être membre. Les euros servent uniquement pour l'administration du territoire par l'ONU puis maintenant par un représentant de l'Union européenne, mais est encore reconnu comme partie de la Serbie. Le Monténégro a choisi unilatéralement d'utiliser l'euro, de même qu'Andorre : ils ne peuvent par émettre de billets (d'ailleurs ni l'un ni l'autre n'a encore de banque centrale nationale, uniquement un établissement national titulaire des garanties d'emprunts d'Etat dans n'importe quelle autre devise inernationale négociable sur les marchés financiers).

Andorre toutefois a été autorisé à émettre des pièces commémoratives libellés en euros, mais sans valeur marchande (des pièces commémoratives en euros, avec un design distinct, sont également autorisées dans les membres, y compris la France).

----

La Suisse n'a pas d'euros mais une association privée de banques émet des "euros WIR" sur les marchés financiers et transactions commerciales de gré à gré (sans pièces ni billets), et sans garantie du gouvernement suisse. Ce sont essentiellement des lignes de compte que peuvent détenir et s'échanger en Suisse entreprises et particuliers pour faciliter les échanges de liquidité sans avoir besoin de recourir au change avec le franc suisse.

(un peu aussi comme les "eurodollars" en Europe, dans certaines banques privées qui autorisent la tenue de compte dans cette devise, sans avoir au préalable à signer une déclaration fiscale aux Etats-Unis, comme on le fait quand on dispose d'un compte titre sur des valeurs cotées en dollars sur les marchés américains: NYSE, DASDAQ, et certaines valeurs EuroNext).

Cependant la liquidité de ces devises "bis" (dont la valeur est presque alignée sur celle de la devise première, avec un léger décalage dans le temps pouvant prendre plusieurs jours) n'est pas assurée sur tous les marchés internationaux (donc le négoce peut prendre plus de temps en cas de change, et nécessite toutefois que quelqu'un dispose de la devise originale et l'accepte en échange de la devise "bis").

----

Le franc CFA, le franc Pacifique, l'escudo du Cap-Vert sont émis avec une valeur fixe par rapport à la valeur équivalente en l'euro du franc français ou de l'escudo portugais, mais selon un accord spécial ayant permis de conserver les accords monétaires pendant l'adoption de l'euro. Mais leur cours peut être révisé sur décision des pays engagés dans ces accords monétaires. Pour le franc Pacifique, seule la France peut fixer la parité avec l'euro. Pour le franc CFA, cela se fait dans deux organisations internationales différentes ayant chacune leur banque centrale (il y a deux devises) : la France en est membre et apporte des garantie financières mais elles sont limitées ce qui a déjà conduit plusieurs fois à des révisions de leur parité par rapport au franc français et maintenant l'euro. La BCE n'apporte aucune garantie à ces devises où la France et le Portugal sont engagés.

Pour le florin des Antilles en fait sa valeur est appuyée sur un panier de devises comprenant le dollar américain (et selon des décisions d'ajustement de ce panier par les Etats antillais des Pays-Bas et l'Etat métropolitain), et pas seulement l'euro (de fait il n'y a pas de parité fixe avec l'euro).

Il en serait de même si le Royaume-Uni rejoignait l'euro pour sa livre sterling (GBP), et sa parité par rapport à la livre de Gibraltar, la livre de Jersey, la livre de Guernesey, et la livre des Falklands, avec leur propre billets et leur propre institut d'émission monétaire, même si la livre sterling y est aussi couramment échangée (à parité sous réserve des contrôles de change locaux) ; certains autres territoires britanniques utilisent soit le dollar américain, soit le dollar Caraïbes (dont la valeur est actuellement à valeur fixe par rapport au dollar américain). Ces territoires d'outre-mer britanniques (ainsi que les 3 possessions de la Couronne, en Manche et en mer d'Irlande) qui utilisent d'autres devises que la livre sterling ne sont toutefois pas dans l'union européenne, ni même formellement dans le Royaume-Uni qui assure leur défense et sécurité en accord avec les parlements locaux (pas même non plus l'île de Man qui pourtant utilise la livre sterling car cette île n'a pas d'institut monétaire local).

Les seuls territoires d'outre-mer britanniques qui utilisent la livre sterling et non une devise locale sont:
- ceux des bases de souveraineté à Chypre (il est question que ces territoires passent à l'euro pour des raisons pratiques avec la population locale, qui compte de nombreux personnels ayant la citoyenneté d'une des deux républiques ; comme c'est aussi déjà le cas pour la zone tampon gérée par l'ONU et qui joint ces deux bases, l'ensemble séparant les deux républiques qui formellement font partie toutes deux de l'Union européenne, y compris en terme de citoyenneté : à Chypre du Nord, l'euro est de plus en plus utilisé à la place de la nouvelle livre turque, ces deux devises étant utilisées de facto).
- la base militaire américaine de Diego Garcia, qui fait formellement partie du Territoire britannique de l'océan Indien (mais a été concédé aux Etats-Unis qui peuvent l'administrer contre un bail de location, mais après que dans les années 1960 dans les années 1960 la population locale y ait été évacuée de force, celle-ci poursuit aujourd'hui le gouvernement britannique devant des cours européennes) : dans cette île, qui ne fait pas non plus formellement partie du Royaume-Uni, c'est le dollar américain qui est utilisé (de même que la base américaine de Guantanamo à Cuba).

Les îles Åland sont une dépendance de la Finlande en mer Baltique mais à proximité immédiate de la Suède : on y parle le suédois et non le finnois et on n'y utilise pas l'euro comme le reste de la Finlande, mais encore la couronne suédoise (qui aurait déjà dû rejoindre l'euro mais la Suède s'est arrangée pour bloquer le processus automatique d'entrée par des dispositions techniques afin de repousser l'échéance). Ces îles finlandaises sont bien dans l'union européenne (de même que la Finlande et la Suède) même si elles un statut d'autonomie spécial au sein de la Finlande. En pratique toutefois, l'euro y circule facilement (d'autant que la couronne suédoise a stabilisé son cours par rapport à l'euro). Il n'y a pas de devise spéciale donc pas d'émissions spécifiques de pièces et billets en euros.
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
18 avril 2011 à 10:27
@verdy_p: Merci pour ces précissions intéressantes. (PS: cryptage => chiffrement)
verdy_p Messages postés 202 Date d'inscription vendredi 27 janvier 2006 Statut Membre Dernière intervention 29 janvier 2019
18 avril 2011 à 05:15
Grosse erreur à ne jamais faire dans un programme:
{
char code[12] = "";
scanf("%s", &code);
//...
}
Le "%s" dans scanf ne donne absolument aucune limite à la longueur de chaine qu'il va vouloir stocker dans le tampon pointé ici par code. Ce tampon étant TRES court (12 caractères Y COMPRIS la place pour l'octet nul final), et en plus alloué en pile, si on saisit plus de 11 caractères, scanf() va provoquer un débordement de tampon, et va donc écraser d'autres éléments de la pile (autres variables locales déclarées dans la fonction courante, adresse du cadre de pile de la fonction appelante, adresse d'exécution au retour de la fonction, n'importe quel gestionnaire d'exceptions). Comme la pile est située à un emplacement à peu près à un emplacement fixe en mémoire virtuelle, c'est facile d'écraser la pile pour que l'adresse de retour tombe à un emplacement situé au début de la pile qu'on aura écrasé volontairement en y copiant du code binaire arbitraire.

Au mieux, si on dispose d'un processeur prenant en charge la protection contre l'exécution, la pile sera dans un emplacement non exécutable, ce qui assurera un crash, mais pas forcément la récupération de l'erreur, ni nécessairement un abandon immédiat du programme par les protections installées par le système.

Quand on utilise scanf() depuis une source dont on n'est pas assuré de connaitre le format des données qui y seront lues, ne JAMAIS utiliser "%s" sans indiquer une limite de longueur.

Autre erreur ici : un numéro de billet bien formé contient 12 caractères (une lettre initiale correspondant au pays ayant émis le billet, en fait plutôt l'imprimerie nationale correspondante qui peut travailler parfois pour plusieurs pays, et transformé en un chiffre de contrôle, mais pas n'importe quelle lettre; cette lettre est suivie de 10 chiffres, plus un chiffre final ajouté uniquement comme contrôle contre les erreurs de lecture/saisie) il faut donc réserver au moins 13 caractères pour le stocker dans une chaîne en comptant l'octet nul final. D'autre part ce code oublie de vérifier la longueur de la chaîne effectivement saisie: un test sur strlen() au début fera l'affaire (sinon le code va trop facilement dire OK dans 95% des cas).

Enfin, à quoi sert le tampon intermédiaire dans le code ? On peut tout faire dans une seule boucle et avec une seule variable de totalisation.

Note: comme pour le contrôle des numéros de cartes bancaires, ce code n'indique pas la validité. En fait même les billets contrefaits vérifient cette formule simple. Le vrai contrôle nécessite la liste complète (gardée secrète) des numéros de série, nécessite de savoir la date d'émission. Il y a d'autres codes en plus sur les billets (ne cherchez pas avec vos yeux, ils sont invisibles à l'œil nu). D'autres codes sont codés sous forme de gravures de type code barre ultrafines, certaines lisibles uniquement sous un rayonnement particulier. Il y a aussi des marques et gravures spécifiques à certaines séries, de légers déplacements d'objets selon les séries, des différences chromatiques (révélables uniquement par traitement chimique dont certains ne peuvent être faits que par la banque centrale puisque cela rend le billet impropre à sa remise en circulation puisque ceal a effacé aussi d'autres éléments de vérification mis à la disposition du public). Il y a enfin une piste magnétique contenant une signature numérique longue (les banques centrales nationales peuvent vérifier ces signatures numériques qui utilisent un cryptage de qualité militaire, et des données relatives au cycle de vie et de distribution du billet).

Le numéro de série visible n'est qu'une partie des données (tout de même utile car les banques ne remettent jamais en circulation immédiatement les billets qu'elles collectent, mais les envoie à la banque de France en échange d'autres billets neufs ou reconditionnés, la BdF assurant le contrôle complet en liaison avec les autres BCN de la BCE). On ne peut pas conclure qu'un billet est véritable avec ce seul code, qui ne sert que dans les systèmes de gestion de caisse et de gestion des billets à vérifier que les numéros enregistrés ont été correctement saisis.

Malgré tous ces points de contrôle, il y a toujours de nombreux faux billets qui circulent, tout bonnement car la plupart des gens et commerces ne peuvent les vérifier tous (même avec un bon équipement). Mais là où on trouve le plus de faux euros, c'est justement hors de l'union européenne, car les banques centrales nationales ne font pas ce contrôle (sauf en cas d'enquête via des agences de la BCE dans certains pays), et les banques commerciales remettent en circulation les billets qu'elles collectent sans les contrôler complètement. Il se passe la même chose avec les faux dollars en Europe et en Asie. Il y a plein de faux euros en Afrique.
litdouilletdu85 Messages postés 12 Date d'inscription samedi 17 mai 2008 Statut Membre Dernière intervention 17 février 2013
15 avril 2011 à 18:55
D'accord merci beaucoup !
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
14 avril 2011 à 22:24
(const char* num) => char* identique à char[] dans ce cas là
const veut dire ici: la chaîne ne peut être modifié par la fonction

num[0] - 'A' + 1 => Récupère le premier caractère de la chaîne, et grâce à 'A' - 1, trouve sa position dans l'alphabet
num + 1 => Toute la chaîne à partir du 2ème caractère.
sprintf => Fait un printf dans une chaîne de caractère. Donc "%i%s" veut dire, mettre la position de l'alphabet du caractère, juste à côté de la chaîne à partir de son 2ème caractère.
Ex: "Z123456789" devient: "26123456789" (%i%s => 26"123456789")
atol(buff) => Transforme une chaîne de caractère en entier.
Enfin je retourne si le nombre final modulo 9 est un multiple de 8

(PS: J'ai vu sur internet qu'il fallait que la somme modulo 9 donne 8)

Inélégant:
if((total + 1) % 9)
return 0;
else
return 1;

Mieux:
if((total + 1) % 9)
return 0;
return 1;

Encore mieux:return (total + 1) % 9 0; /* ou 8 :p */
litdouilletdu85 Messages postés 12 Date d'inscription samedi 17 mai 2008 Statut Membre Dernière intervention 17 février 2013
14 avril 2011 à 21:30
Ah mais bien sur !! --' Merci beaucoup de ton aide ! Voilà mon code après simplification :

#include <stdio.h>
#include <stdlib.h>

int TesterValidite(char code[], int tailleCode, int positionLettre)
{
int codeFinal[12] = {0};
int j;
int total = 0;
for(j=0;j!=tailleCode;j++)
codeFinal[j] = code[j] - '0';
for(j=1;j!=tailleCode;j++)
total += codeFinal[j];
total += (positionLettre / 10) + (positionLettre % 10) ;
if((total +1) % 9) return 0;
else return 1;
}

int main()
{
printf("\n\n\n\n\n\n\n\n\n\n\t\t\tCode de validite : ");
char code[12] = "";
scanf("%s", &code);
int positionLettre = code[0] - 'A' +1;
if(TesterValidite(code, 12, positionLettre)) printf("\n\n\n\t\t\t\tValide !\n\n\n");
else printf("\n\n\n\t\t\t\tInvalide !\n\n\n");
return 0;
}
cs_Lucky92 Messages postés 180 Date d'inscription mercredi 22 décembre 2004 Statut Membre Dernière intervention 16 août 2012 2
14 avril 2011 à 21:27
positionLettre / 10 ne vaut pas 2.1 mais 2 ( il s'agit d'une division entière )
positionLettre % 10 vaut 1
litdouilletdu85 Messages postés 12 Date d'inscription samedi 17 mai 2008 Statut Membre Dernière intervention 17 février 2013
14 avril 2011 à 21:17
Oui j'avais compris ce que ça faisait, mais je comprends jusqu'au / 10, après ça se complique ... Si la lettre est U, total += 2.1 c'est ça ? Mais après ? 2.1 / 10 = 0.21, donc là je bloque ... Le reste c'est quoi à la fin ?
cs_Lucky92 Messages postés 180 Date d'inscription mercredi 22 décembre 2004 Statut Membre Dernière intervention 16 août 2012 2
14 avril 2011 à 20:51
l'opérateur % ( opérateur modulo ) donne le reste de la division euclidienne.
le bout de code que j'ai fourni ajoute au total la somme des chiffres de la position de la lettre.
Par exemple, si la lettre est U (21ème lettre de l'alphabet), total = total + 2 + 1 ; ce qui est équivalent à ton code.

@+
litdouilletdu85 Messages postés 12 Date d'inscription samedi 17 mai 2008 Statut Membre Dernière intervention 17 février 2013
14 avril 2011 à 20:07
Merci beaucoup pour vos réponses, mais je n'ai pas compris ce que faisait le modulo :

total += positionLettre / 10 ;
total += positionLettre % 10 ;

Et pour CptPingu, je ne comprends pas l'intéret d'un const char* ... Et aussi, je ne comprends pas pourquoi tu mets un "%i" lors du sprintf(), et encore une dernière chose, que fait le return de la fonction isValid ?

En tout cas merci beaucoup pour votre aide !
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
14 avril 2011 à 10:16
On peut tout de même pas mal simplifier:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int isValid(const char* num)
{
char buff[14] = {0};
if (strlen(num) != 12)
return 0;

sprintf(buff, "%i%s", num[0] - 'A' + 1, num + 1);
return atol(buff) % 9 == 8;
}

int main()
{
printf("\n\n\n\n\n\n\n\n\n\n\t\t\tCode de validité : ");
char code[12] = "";
scanf("%s", code);
switch (isValid(code))
{
case 1:
printf("\n\n\n\t\t\t\tValide !\n\n\n");
break;
case 0:
printf("\n\n\n\t\t\t\tInvalide !\n\n\n");
break;
}
return 0;
}
cs_Lucky92 Messages postés 180 Date d'inscription mercredi 22 décembre 2004 Statut Membre Dernière intervention 16 août 2012 2
14 avril 2011 à 00:57
re,

Tu peux remplacer ce bout de code :

if(positionLettre > 9 && positionLettre < 20) //si la position de la lettre est entre 10 et 20 on effectue les opérations suivantes
{
j = positionLettre % 10; //j sert d'intermédiaire afin d'y stocker le reste de la division (euclidienne)
positionLettre = j + 1; //on change la valeur de la variable (qui n'est plus utile) par le reste de la division + 1 (pour la dizaine)
total += positionLettre; //on ajoute au total des chiffres cette valeur
}
else if(positionLettre > 19) //si la position de la lettre est au dessus de 20 on effectue les opérations suivantes
{
j = positionLettre % 10;
positionLettre = j + 2;
total += positionLettre;
}
else //si la position de la lettre est en dessous de 10 on effectue les opérations suivantes
{
total += positionLettre;
}

par celui-ci :

total += positionLettre / 10 ;
total += positionLettre % 10 ;

@+
cs_Lucky92 Messages postés 180 Date d'inscription mercredi 22 décembre 2004 Statut Membre Dernière intervention 16 août 2012 2
14 avril 2011 à 00:01
Salut,

La fonction PositionLettre est inutile ; la position d'une lettre dans l'alphabet vaut simplement :
lettre - 'A' + 1.

@+
Rejoignez-nous