Prosty program - bład w działaniu


(Kamil I 2006) #1

Znalazłem na necie taki algorytm:

Jak wiemy, PESEL to numer identyfikacyjny każdego z nas, który widnieje w dowodzie osobistym. Większość z nas wie o tym, co z niego można wyciągnąć. Wiemy na pewno (większość z nas), że pierwsze cyfry to data urodzenia właściciela numeru identyfikacyjnego (PESEL), która wygląda tak, że pierwsze dwie cyfry to rok, dwie następne to miesiąc, zaś dwie kolejne to dzień. Np. 771001 będzie znaczyło, że osoba urodziła się 1977 roku, 1 października.

Teraz przejdźmy do czegoś ciekawszego. Z numeru identyfikacyjnego możemy dowiedzieć się o płci osoby, jaką reprezentuje PESEL. Wystarczy spojrzeć na przedostatnią cyfrę, jeśli jest parzysta - kobieta; nieparzysta - mężczyzna.

I tak dla przykładu zbadajmy taki PESEL: 49040501580. Przedostatnia cyfra (parzysta) oznacza, że jest to kobieta urodzona 5 kwietnia, 1949 roku (pięćdziesiątka na karku). I to by mogło być na tyle, ale jak sprawdzić czy w ogóle dany PESEL jest oryginalny? Wystarczy znać pewien algorytm, który poprzez odpowiednie kalkulowanie cyfr numeru identyfikacyjnego musi odpowiadać cyfrze kontrolnej, jaką jest ostatni znak PESELA (na przykładzie to 0). Dokładnie polega to na tym, że każdą cyfrę numeru identyfikacyjnego od pierwszej do przedostatniej (ostatnia to wspomniana suma kontrolna) musi być przemnożona przez odpowiednią cyfrę, która wchodzi w skład odpowiedniej tablicy dziesięciocyfrowej, której cyfry nazywamy wagi. Po pomnożeniu następuje sumowanie tych cyfr i wynik należy podzielić modulo przez 10, z kolei rezultat trzeba zróżnicować z 10 a wynik musi zgadzać się z ostatnią cyfrą.


Przykład:


Naszym numerem identyfikacyjnym niech będzie już wspomniany ciąg: 49040501580.

Wspomniane wagi to cyfry reprezentujące tablicę, które wyglądają tak: 1, 3, 7, 9, 1, 3, 7, 9, 1, 3


1. Teraz każdą cyfrę PESELA (prócz ostatniej) mnożymy zgodnie z cyfrą wagi:


PESEL: 4 9 0 4 0 5 0 1 5 8 0 <== suma kontrolna, którą na tym etapie nie bierzemy pod uwagę;

WAGI: 1 3 7 9 1 3 7 9 1 3

---------------------------------------

WYNIK: 4x1=4 ;9x3=27 ;0x7=0 ;4x9=36 ;0x1=0 ;5x3=15 ;0x7=0 ;1x9=9 ;5x1=5 ;8x3=24


2. Teraz wyniki sumujemy: 4 + 27 + 0 + 36 + 0 + 15 + 0 + 9 + 5 + 24 = 120;


3. Następnie dzielimy module przez 10: 120 mod 10 = 0


4. 10 odejmujemy od wyniku: 10 - 0 = 0;


5. Wynik porównujemy z sumą kontrolną: 0 = 0.

Dawno nic nie pisałem, a wydało mi się ze potrafię coś takiego już napisać, więc zabrałem sie do pracy. Zacząłem od chyba najtrudniejszego, bo od sprawdzania czy podany PESEL jest prawdziwy. Oto kod programu którego zdążyłem dotąd napisać:

#include 

using namespace std;


int * Mnozenie (int *TabPesel, int *TabWaga, int *TabMnozenie);

int Suma (int * TabMnozenie);


const int IloscWaga = 10; //Ilosc liczb wagi

const int IloscLiczb = 11; //Ilosc Liczb w PEŚLE

// ***************************************************************

int main()

{

	cout << "Podaj Twoj PESEL. ";

	cout << "Po kazdej z liczb wcisnij ENTER! " << endl;


	int Pesel[IloscLiczb]; //Tablica na liczby z pesla


	for (int i = 0; i < IloscLiczb; i++)

	{

		cin >> Pesel[i];

	}


	int Waga[IloscWaga]= { 1, 3, 7, 9, 1, 3, 7, 9, 1, 3 };


	int WynikMnozenia[IloscWaga];


	int *WskDoMnozenia = Mnozenie(Pesel, Waga, WynikMnozenia);


	cout << Suma(WskDoMnozenia); //Po uruchomieniu nie zostaje wypisana suma :(



	return 0;

}

// *****************************************************************

int * Mnozenie (int *TabPesel, int *TabWaga, int *TabMnozenie)

{

	// mnozenie elemetow TabPesel * TabWaga

	// zwraca jako rezultat tablice wynikuw

	// mnozeniakolejnych elemetow tablic

	// np. pierwszy elemet tablicy TabPesel * pierwszy elemet TabWaga 

	// zostanie zapisany w pierwszym elemecie tablicy WynikMnozenia


	for (int i = 0; i < IloscWaga; TabPesel++, TabWaga++, TabMnozenie++)

	{

		TabMnozenie[i] = ( (TabWaga[i]) * (TabPesel[i]) );

	}


	return TabMnozenie;

}

// ******************************************************************

int Suma (int *TabMnozenie)

{

	static int suma = 0;

	for (int i = 0; i < IloscWaga; i++)

	{	

		suma = ( suma + ( TabMnozenie[i] ) );

	}


	return suma;

}[/code]

Niestety program po uruchomieniu nie wypisuje sumy na ekranie. Proszę tych, którzy znają C++, żeby naprowadzili mnie na błąd (błędy), które popełniłem. Z góry WIELKIE DZIĘKI :>

ps. Mój kompilator to Visual C++ 6.0


(Ryan) #2

Algorytm jest opisany z błędami, ale mniejsza o to.

Twój program ma kilka błędów i wykorzystuje kilka suboptymalnych rozwiązań. Jeśli coś nie jest jasne albo chcesz więcej info, pisz. Kolejno:

  • czytanie z cin do inta przy pomocy strumieni (>>) jest niebezpieczne i nie powinno być stosowane;

  • jeśli z klawiatury wpisujesz 123 i czytasz z cin do inta, to nie otrzymasz 1 tylko 123;

  • jeśli z klawiatury wpisujesz 49040501580 cin otrzymuje wartość, która przepełnia inta (wartość w tym wypadku będzie ujemna);

Twój kod "wybucha" przy wczytywaniu wartości i nigdy nie dochodzi do obliczeń.

Moja rada: jesli musisz używać tych nieszczęsnych strumieni, używaj metody get() z odpowiednim buforem. Interesuje Ciebie 11-znakowy PESEL, więc stwórz 13-znakowy bufor (miejsce na \0 i miejsce na znak do weryfikacji długości) i wczytaj do niego ciąg znaków, po czym operuj na ciągu znaków a nie na intach. Po pierwsze każdy int to 4 bajty (przeważnie) a każdy znak to jeden bajt (czasem dwa). Oszczędzisz trochę pamięci. Na tym poziomie nie jest Ci to szczególnie potrzebne, ale dobre nawyki powinieneś wyrabiać sobie wcześnie. Przykładowy fragment kodu:

char Pesel[IloscLiczb + 2] = { 0 };

   cin.get(Pesel, IloscLiczb + 2);

   cout << Pesel

       << endl

       << "Dlugosc: "

       << static_cast(strlen(Pesel))

       << (static_cast(strlen(Pesel)) == IloscLiczb ? " (poprawna)" : " (bledna)")

       << endl;

Chcesz sprawdzić poprawność PESELa, ale nie sprawdziłeś długości wprowadzonego ciągu ani czy są to faktycznie liczby. Taki kod nie ma prawa poprawnie działać. Powyższy robi kilka rzeczy.

  • tworzy bufor znaków dłuższy niż oczekiwany ciąg;

  • czyta dokładnie tyle znaków ile może pomieścić bufor;

  • sprawdza, czy wprowadzony ciąg był nie dłuższy i nie krótszy niż oczekiwany;

Do sprawdzenia pozostało: czy są to cyfry i czy wartość jest poprawna (czy wielomian ma oczekiwany wynik).

Kilka dodatkowych uwag:

  • skoro tylko weryfikujesz, powinieneś weryfikować w locie (mnożyć wartość cyfry z PESELa przez kolejny współczynnik);

  • skoro kolejne liczby różnią się o 2, to zamiast tworzyć tablicę, możesz zacząć od jedynki i kolejny mnożnik opisać jako i = (i+2) % 10;

  • skoro podajesz do Mnozenie TabMnozenie, po co zwracasz ją z funkcji? przecież nie zaalokowałeś nowej pamięci a caller wie, gdzie są dane (podał Ci wskaźnik);

  • skoro wielomian obliczany jest w Mnozenie, czemu wagi zdefiniowałeś w main? złe miejsce, więcej przekazywania, itp.;

  • po co składować wyniki mnożenia? wielomian powinien być liczony w locie, interesuje Cię tylko wynik;

  • po co rozdrabniać algorytm na Mnozenie i Suma? to zła architektura aplikacji, tak się nie powinno pisać kodu;

  • suma zdefiniowana jako static? jesteś w stanie wyjaśnić dlaczego jest to zmienna statyczna? zmienne statyczne to krótka droga do ciężkich do wyłapania błędów. zmienne statyczne nie są thread safe i przy bardziej złożonych aplikacjach powodują bóle głowy.

Tyle na dziś.


(rozwalkompa) #3

Według mnie 10 - 0 = 10, ale może to jakaś wyższa matematyka...

Myślę, że po tak szczegółowym opisie algorytmu umiałbym coś takiego napisać, ale w Pascal'u :slight_smile: {nie znam innego języka}


(Ryan) #4

Na to, ze napisano "dziesięć odejmujemy od wyniku" a odjęto dziesięć od wyniku już nie zwróciłeś uwagi? :wink: Jak mówię, opis jest niedoskonały, ale można się na upartego połapać. :slight_smile:


(rozwalkompa) #5

masło maślane

PS. Prywatnie nic do Ciebie nie mam i nawet bardzo mi się podoba, że możesz w tylu problemach pomóc :smiley:


(Ryan) #6

Bah! Miało być "a odjęto wynik od dziesięciu" (zero było wynikiem). Pośpiech...

W każdym razie w algorytmie brakuje takiego drobiazgu jak to, że istotny jest nie wynik odejmowania, a wynik odejmowania modulo 10 (czyli: (10 - wynik) % 10 ).


(rozwalkompa) #7

Rozumiem, każdy się myli, jak się śpieszy.

Napisałem to w Pascal'u. To będzie jakoś tak:

VAR

W:ARRAY[1..10] OF byte;

P:ARRAY[1..11] OF char;

a,i:byte;

plec,dzien,miesiac,rok:string;

suma,suma2:longint;


FUNCTION prawda:boolean;

BEGIN

suma2:=0;

FOR i:=1 TO 10 DO

 BEGIN

 suma:=(ord(P[i])-48)*W[i];

 suma2:=suma2+suma;

 END;

suma:=suma2 mod 10;

IF suma=0 THEN suma:=10;

IF 10 - suma = (ord(P[11])-48) THEN prawda:=true

ELSE prawda:=false;

END;


BEGIN

a:=1;

FOR i:=1 TO 10 DO

 BEGIN

 W[i]:=a;

 a:=a+2;

 IF a=5 THEN a:=a+2;

 IF a>9 THEN a:=1;

 END;

writeln('Wpisz swój PESEL, a na końcu wciśnij [ENTER]');

FOR i:=1 TO 10 DO read(P[i]);

readln(P[11]);

writeln;

IF prawda THEN

BEGIN

IF (ord(P[10]) mod 2) = 0 THEN plec:='kobietą, urodzoną'

ELSE plec:='mężczyzną, urodzonym';

dzien:=P[5]+P[6];

IF P[4]='1' THEN

 IF P[3]='0' THEN miesiac:='stycznia'

 ELSE miesiac:='listopada';

IF P[4]='2' THEN

 IF P[3]='0' THEN miesiac:='lutego'

 ELSE miesiac:='grudnia';

IF P[4]='3' THEN miesiac:='marca';

IF P[4]='4' THEN miesiac:='kwietnia';

IF P[4]='5' THEN miesiac:='maja';

IF P[4]='6' THEN miesiac:='czerwca';

IF P[4]='7' THEN miesiac:='lipca';

IF P[4]='8' THEN miesiac:='sierpnia';

IF P[4]='9' THEN miesiac:='września';

IF P[4]='0' THEN miesiac:='października';

rok:=P[1]+P[2];

writeln('Zatem jesteś ',plec,' ',dzien,' ',miesiac,' 19',rok);

END

ELSE writeln('Podany PESEL jest niepoprawny');

readln;

END.

Jeszcze trzeba by dodać warunek, żeby nie pokazywało np. 05 kwietnia 1949 (ten z opisu na górze strony) Na moim PESELu działa, sprawdźcie na swoim :smiley: EDIT: koleżance wyszło, że jest facetem, więc znalazłem błąd:

IF (ord(P[10]) div 2) = 0 THEN plec:='kobitą, urodzoną'

ELSE plec:='mężczyzną, urodzonym';

ten na górze jest poprawny