Sunteți pe pagina 1din 9

 

Operații cu numere mari 


scris de Alexandru Lungu 
 

Discuție preliminară 
1. Operațiile  cu  numere  mari  sunt  necesare  în probleme unde întâlnim numere ce depășesc 
limita  tipurilor  de  date  oferite  de  limbajul  de  programare.  În  cazul  C++,  tipul  de  date  cu 
limita cea mai mare este “​unsigned long long​”, ce poate reține numere pana la 2​64​. 
2. Pentru  a  reuși  să  reținem  în  memorie  numere  >  2​64​,  vom  folosi o altă structură, anume un 
vector,  unde  în  fiecare  celulă  vom  reține  o  cifră  a  numărului  în  cauză.  În  continuare  vom 
vorbi  despre  cum  să  formăm  acest  vector  și  despre  operațiile  ce  trebuie  implementate 
pentru a simula adunarea, înmulțirea etc. 

Citirea și scrierea numerelor mari 


Reprezentare în memorie 
Înainte  de  toate,  este  important  să înțelegem cum să reprezentăm în memorie acest vector. Dacă 
vom întâlni numere cu x cifre, atunci vom declara un vector de lungime minim x+1, în care: 

1. la poziția 0 se va reține lungimea numărului (adică x) 


2. la poziția i se va reține a i-a cifră a numărului ​de la dreapta la stânga 

Sunt  necesare  aceste  2  reguli  de  formatare  a  numărului  în  memorie  pentru  a  ne  fi  mai  ușor  să 
implementăm operații mai eficiente pe aceste numere. 

Citirea  
În  momentul  citirii  vom  realiza  formatarea  precizată  mai 
sus.  După  ce  vom  citi  numărul  de  cifre,  vom  completa 
vectorul  A  de  la  sfârșit  spre  început,  iar  pe  poziția  0  vom 
reține numărul de cifre din A. 

Scrierea 
Este asemănătoare cu citirea (se va efectua tot în mod descrescător). 


 

Inițializarea și atribuirea 
Inițializarea  unui  număr  mare A se face prin declararea unui vector așa cum este menționat și mai 
sus.  Important  este  să  setăm  A{0]  =  0,  pentru  a  sugera  faptul  că  în  acest  număr  nu  avem  nici  o 
cifră. (​atenție​: dacă dorim sa avem cifra 0 memorată, atunci A[0]=1, A[1]=0) 

Atribuirea  se  va  face  diferit,  în  funcție  de  valoarea  atribuită.  Vom  vedea  și  în  partea  de  operații 
aritmetice că vom avea în mare parte câte două tipuri de înmulțiri, împărțiri etc.  

Valoarea atribuită poate fi: 

1. Un  scalar,  adică  un  număr  ce  poate  fi 


reprezentat  într-un  tip de variabilă standard. Caz 
în  care  se  iterează  cifrele  scalarului  de  la 
dreapta ​și se introduc progresiv în A. 
2. Un  alt  număr  mare  reprezentat  sub  formă  de 
vector.  Caz  în  care  se  copie  celulă  cu  celulă 
vectorul atribuit. 

Compararea a două numere mari 


Toate operațiile vor fi intuitive, deoarece vor fi implementate cu algoritmii folosiți și la matematică. 

În  cazul  comparării,  este  important  să  vedem  că 


lungimea  numerelor  contează.  Mereu,  numărul  ce 
are mai multe cifre va fi mai mare ca și celălalt. 

În  cazul  în  care  sunt  egale  lungimile,  atunci  vom  fi 
nevoiți  să  parcurgem  ambele  numere  mari. 
Parcurgerea  va  fi  de  la  ​dreapta  la  stânga  și 
simultană​,  adică  verificăm  numerele  cifră  cu  cifră 
pâna când găsim o cifră distinctă. 

1. Dacă  găsim  o  cifra  distinctă  pe  poziții 


identice,  atunci  cifra  mai  mică  se  află  în 
numărul  mai  mic, iar cifra mai mare se află în 
numărul mai mare. 
2. Altfel, numerele sunt egale. 


 

Adunarea a două numere mari 


Cum  am  precizat  și  la  partea  de  atribuire,  operațiile 
pot  fi  implementate  diferit,  depinzând  de  operandul 
din  dreapta.  Adunarea,  de  exemplu, se poate face cu 
un  scalar  sau  cu  un  alt  vector.  În cazul adunării cu un 
scalar,  de  regulă,  se  va  crea  un  nou  număr  mare  la 
care  se  va  atribui  acest  scalar,  iar  apoi  se  va  apela 
funcția de adunare a doua numere mari. 

Metoda  de  adunare  este  un  algoritm  informatic  ce 


ilustrează  un  fundament  matematic.  Se  vor  aduna 
cifră  cu  cifră  cele  doua  numere,  de  la  ​stânga  la 
dreapta​,  iar  în  caz  de  rezultatul,  după  o  iterație,  va 
avea  doua  cifre,  atunci  vom  lăsa  doar  cifra  unităților,  iar  pe  cea  a  zecilor  o  vom  stoca  separat 
(într-un  “transport”).  Transportul  va  fi  adăugat  și  el  la  următoarea  iterație  în  suma  cifrelor. 
Algoritmul  alăturat  va  formula  răspunsul  în  C,  însă  există  și  variațiuni  în  care răspunsul să fie în A 
ori B. (​sumaXL - pbinfo​) 

Scăderea a două numere mari 


Algoritmul  pentru  scădere  este  destul  de 
asemănător  cu  cel  de  la  adunare,  însă  de  data 
aceasta,  transportul  reprezintă  defapt 
“împrumutul”.  Ca  și  la  matematica,  împrumutul  se 
va  realiza  doar  atunci  când  vom  avea  de  scăzut  o 
cifra mai mare dintr-o cifra mai mică.  

Deoarece  reprezentarea  aceasta  propusă  pentru 


numerele  mari  nu  acoperă  și  mulțimea  numerelor 
negative,  algoritmul  va  funcționa  doar  atunci  când 
primul  număr  va  fi  mai  mare  ca  cel  de  al  doilea. 
Dacă  totuși  se vrea diferența în modul, se va apela 
funcția  de  comparare  pe  urma  căreia  se  decide 
metoda  prin  care se apelează scăderea numerelor 
mari.  


 

Înmulțirea și împărțirea numerelor mari prin puteri ale lui 10 


Aceste  doua  funcții  sunt  utile  atât  timp  cât  întâlnim  probleme  cu prelucrarea cifrelor. Mai mult de 
atât  sunt  cazuri  particulare  ale  înmulțirii  și  împărțirii  printr-un  scalar, ce sunt ușor de implementat. 
Acest  fapt  se  datorează  existenței  funcției  memmove(destinație,  sursă,  dimensiune),  ce  mută  un 
block de o dimensiune dată de la sursă la destinație. 

1. În  cazul  înmulțirii  cu  o  10​x​,  trebuie  să  mutăm  cifrele  cu  x  poziții  la  dreapta.  Vom  folosi 
memove  pentru  a  muta  cifrele  și  memset  pentru  a  umple  spațiul  gol  cu  0.  La numărul de 
cifre trebuie sa adăugăm x. 
2. În  cazul  împărțirii  prin  10​x​,  trebuie  să  mutăm  cifrele  cu  x  poziții  începând  de  la  x+1  la 
stânga.  Vom  folosi  de  asemenea  memove.  Nu  mai  este  necesar  memset,  deoarece  este 
suficient sa schimbăm A[0] - numărul de cifre. 

Înmulțirea numerelor mari 


Înmulțirea cu un scalar 
Înmulțirea  cu  un  scalar  se bazează foarte mult 
pe  următoarea  observație.  Fie  abc  numărul 
mare:  abc  =  a*100  +  b*10 c, iar abc*x = a*x*100 
+ b*x*10 + c*x 

Deci fiecare cifra din număr va fi înmulțită cu x, 
evident  existând  și  un  transport  ce  ​nu  ​are 
doar  o  cifră.  Așadar  la  final  trebuie  adăugate 
la  capăt  ​cifrele  transportului.  ​Important! 
Transportul va fi mereu mai mic ca x. (​suma34​) 


 

Înmulțirea cu un alt număr mare 


Deși  un  pic  mai  greu  de  înțeles,  dar  la  fel  de 
asemănătoare  cu  metoda  matematica, 
înmulțirea  a  două  numere  mari  este  o  operație 
în  O(N*M)  spre  deosebire  de  cele  prezentate 
până  acum.  De  asemenea,  foarte  important  de 
reținut  este  că  rezultatul  înmulțirii  are  N+M-1 
cifre,  unde  N  -  numărul  de  cifre  al  primului 
număr,  iar  M  -  numărul  de  cifre  al  celui  de  al 
doilea număr. 

Algoritmul are 4 părți: 

1. Golirea  vectorului  rezultat,  anume  C. 


Pentru  acest  pas  se  poate  folosi  și 
funcția memset.  
2. Înmulțirea  propriu-zisă,  unde  produsul 
cifrelor  de  pe  pozițiile  i  și j va fi adăugat 
la poziția i+j-1. 
3. Fiecare  cifră  din  C  nu  va  fi  o  cifră,  ci  cel  mai  probabil  un  număr  de  minim  2  cifre.  Așadar 
trebuie  să  implementăm  ideea  transportului  ca  să  propagam  cifrele  suplimentare  mai 
departe. 
4. Transportul  de  la  sfârșit  va  avea  cu  siguranță  cel  mult  o  cifra.  Așadar,  în  cazul  în  care 
transportul este existent, îl vom adăuga ca și cifră a lui C. 

De  ce  funcționează  abordarea pasului 2? Dacă avem o cifră în A, anume a, atunci ea va apărea în 


scrierea  zecimală  a  lui  A  ca  și  a*10​i​,  unde  i  este  poziția  la  care  se  află  a.  Alegând  o  cifră  în  B, 
anume  b,  atunci  a*b  va  contribui  la  cifra  de  la  poziția  10​i​*10​j​,  unde  j  este  poziția  la  care  se află b, 
așadar  la  poziția  i+j  (adunând  la  C  a*b*10​i+j​).  Motivul  pentru  care  în  implementare  apare  C[i+j-1], 
este pentru că cifrele sunt indexate de la 1: C[i-1+j-1+1] = C[i+j-1]. 

În  sfârșit,  motivul  pentru  care  la  pasul  4  vom  avea  un  rest  de  o  singură  cifră  de  datorează 
următoarelor  inegalități:  10​N-1  <=  A  <  10​N​,  10​M-1  <=  B  <  10​M​,  deci  10​N+M-2 <= A*B < 10​N+M​. C va avea ori 
N+M-1  cifre  (cazul  în  care  nu  avem  transport  la  final).  Dacă  vom  avea  un  transport,  atunci  acesta 
va  avea  o  singură  cifră,  pentru  ca  C  să  aibă  N+M  cifre.  Dacă  transportul  va  avea  mai  mult  de  o 
cifră,  atunci  C  ar  avea  mai  mult  de  N+M cifre, ce contrazice inegalitatea de mai sus. ​Atenție! ​Este 
vorba  doar  de  transportul  de  la  final (după execuția forului), transporturile din interiorul numărului 
pot avea mai mult de o cifra. (​putereN​, ​SumaGauss1​) 


 

Împărțirea numerelor mari 


Împărțirea la un scalar 
Operația  de  împărțire  printr-un  scalar  se  bazează  ca  oricare  altă  operație  pe  fundamentul 
matematic.  Așadar  algoritmul  poate  fi  ușor  observat  odată  ce  se  simulează  pe  un  exemplu 
practic. 

Se  “coboară”  cifrele  numărului  mare  pe  rând  începând  cu  ​cifra cea mai semnificativă la sfârșitul 
restului  actual  și  se  împarte  prin  scalar.  Câtul  ce  va  fi  format dintr-o cifră se va adăuga la sfârșitul 
numărului,  iar  restul  se  va  păstra  pentru  a  continua  procedeul.  Restul  de  la  sfârșitul  algoritmului 
va reprezenta evident, restul împărțirii. 

De  asemenea,  dacă  se  dorește  doar  restul  împărțirii  numărului  mare  la  scalar,  atunci  se  poate 
renunța  la  liniile  de  cod  legate  de  cât.  Este  necesară  doar  calcularea  restului,  dar  nu  și stocarea 
unui  cât.  Restul  va  fi  mereu  mai  mic  ca  și x, deci nu trebuie sa ne facem probleme legate de tipul 
de date alocat lui r. (​numere20​) 

Împărțirea la un număr mare 


Poate  una  din  cele  mai  complicate  operații,  împărțirea  a  două  numere  mari  implică,  spre 
deosebire  de  împărțirea  printr-un  scalar,  de  un  împărțitor  mare,  dar  și  un  ​rest  mare.  ​La  fel  ca 
împărțirea a două numere mari și împărțirea este o operație in O(N*M), însă dimensiunea câtului și 
al restului este mai mică sau egală ca N, respectiv mai mică ca M. 


 

Algoritmul este asemănător cu cel al împărțirii 
printr-un  scalar,  însă  în  acest  caz  suntem 
obligați  să  găsim  alternative  pentru operațiile 
de înmulțire cu 10 și împărțire.  

1. Din fericire, avem un algoritm scurt de 
înmulțire cu 10 explicat mai sus. 
2. Împărțirea  o  vom  simula  prin  scăderi. 
În  cel  mai  rău  caz  vom  executa  9 
scăderi,  deoarece  câtul  acestei 
înmulțiri este o cifră. 

În  rest,  ideea  este  la  fel  ca  cea  de  mai  sus 
(coborârea  unei  cifre  și  împărțirea  prin  B 
pentru a obține restul local). 

Alte operații sau implementări 


Există mai multe operații ce pot fi implementate cu ajutorul celora prezentate mai sus (exemplu 
fiind împărțirea a două numere mari unde folosim mai multe funcții implementate anterior). 

1. Funcții trigonometrice - acestea pot fi implementate prin observațiile următoare:  


a. unele funcții trigonometrice sunt periodice (sin, cos), așa că putem afla restul 
numărului prin împărțirea la 360 (grade) / 2pi (radiani), apoi conversia într-un tip 
standard pentru a aplica funcțiile din cmath. 
b. Unele funcții trigonometrice pot fi scrise în funcție de altele (tg sau ctg) 
2. Radical - radicalul poate fi implementat folosind căutarea binară. Prin această abordare 
putem implementa chiar radicale de orice ordin. Având la dispoziție împărțirea cu un 
scalar putem afla mijlocul în căutarea binară, iar cu înmulțirea și compararea pe numere 
mari putem verifica răspunsul. 
3. Scăderea - poate fi implementată folosind căutare binară, folosind adunarea pe numere 
mari și compararea numerelor mari. Deși mai ineficient ca metoda prezentată mai sus, 
reprezintă o abordare demnă de luat în considerare. 
4. Împărțirea - poate fi implementată folosind căutare binară, folosind înmulțirea pe numere 
mari și compararea numerelor mari. 

Atenție! ​Pentru ultimele doua operații menționate, avem nevoie de împărțirea cu un scalar, 
pentru a determina mijlocul în căutarea binară. 


 

Exerciții și documentație suplimentară 


Documentație 
1. Pbinfo 
2. Infoarena 
3. https://sites.google.com/site/eildegez/home/x/numere-mari-operatii-cu-siruri-de-caractere 
4. http://ler.is.edu.ro/~cex_is/Informatica/2014/teme/nrmari.pdf 
5. https://codeforces.com/blog/entry/61343 

Exerciții de antrenament 
Exercițiile  sunt  preluate  parțial  din  documentația  suplimentară.  Posibil  să existe și rezolvări ce nu 
implică  numere  mari.  De  asemenea,  există  unele  exerciții  cu  grad  de  dificultate  mai  ridicat. 
Recomandarea mea este să se rezolve problemele ce apar pe parcursul lecției, iar apoi cele de la 
temă.  Sunt  necesare  pentru  exersarea  implementărilor  ce  includ  numere  mari.  După  care  să  se 
insiste pe exercițiile suplimentare. Există unele probleme din documentație ce nu sunt incluse aici 

1. Campion.edu.ro 
a. http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=1570 
b. http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=1106 
c. http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=438 
d. http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=432 
e. http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=1081 
2. Infoarena 
a. https://infoarena.ro/problema/biti2 
b. https://infoarena.ro/problema/patrate2 
c. https://infoarena.ro/problema/aladdin2 
d. https://infoarena.ro/problema/next 
e. https://infoarena.ro/problema/tort 

Temă 
1. Factorial1 
2. Prim023 
3. Stele 

Alexandru Lungu - ​lungualex00@gmail.com  Facultatea de Informatica, UAIC, Iasi 


 

Update 2019 
Probleme noi pentru antrenament  
1. {{2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - -}}​ ​

[Dispoziții de lucru - infogim 2019] 


Problemele de antrenament sunt ascunse până la sfârșitul concursului. Vor fi alocate 2 ore pentru 
rezolvarea a 6 probleme. Concursul va începe pe 26/10/2019, ora 09:30 EEST. 
https://vjudge.net/contest/337746 

S-ar putea să vă placă și