De ce rezultă diviziunea întreagă într-un număr întreg?

Am învățat în Introducere la programare că, dacă împărțiți doi numere întregi, veți obține întotdeauna un număr întreg. Pentru a rezolva problema, faceți cel puțin unul dintre aceste numere întregi să fie float.

De ce compilatorul nu înțelege că vreau ca rezultatul să fie un număr zecimal?

Comentarii

  • Poate doriți ca rezultatul să fie un număr întreg. Cum poate face diferența?
  • Deoarece standardul C ++ spune acest lucru.
  • @soandos: felul în care Pascal și Python o fac ambii: au un operator de diviziune întreagă distinct, dar operatorul de diviziune standard returnează întotdeauna rezultatul corect din punct de vedere matematic. (Sau – pentru pedant – atât de corect cât puteți obține dat limitări ale matematicii FP.)
  • Cum ar ști compilatorul că doriți ca rezultatul să fie un număr zecimal? Există motive valabile pentru a dori un număr întreg.
  • @soandos În Python, // este operatorul diviziunii întregi și # este un comentariu cu o singură linie. În Pascal, operatorul diviziunii întregi este cuvântul cheie div. Ambele funcționează destul de bine pentru limba lor respectivă ges. C face probabil cel mai rău lucru posibil: un operator care poate face două lucruri complet diferite pe baza unui context arbitrar.

Răspuns

De ce compilatorul nu înțeleg că vreau ca rezultatul să fie un număr zecimal?

Compilatorul C ++ urmează pur și simplu reguli bine definite și deterministe așa cum sunt stabilite în standardul C ++. Standardul C ++ are aceste reguli deoarece comitetul de standarde a decis să facă acest lucru.

Ei ar fi putut scrie standardul pentru a spune că matematica întreagă are ca rezultat numere în virgulă mobilă sau face acest lucru numai în cazul unui rest. Cu toate acestea, acest lucru adaugă complexitate: fie trebuie să știu din timp ce rezultat este, fie poate converti înapoi în număr întreg dacă dă întotdeauna un float. Poate că vreau un număr întreg .

Una dintre filosofiile de bază ale C ++ este „nu plătești pentru ceea ce nu folosești . ” Dacă de fapt doriți complexitatea amestecării numerelor întregi și flotante (și instrucțiunile suplimentare ale procesorului și accesul la memorie implică 1 ), atunci efectuați tipul exprimat așa cum ați menționat în întrebarea dvs. . În caz contrar, rămâneți la matematica numerelor întregi standard.

În cele din urmă, amestecarea variabilelor cu virgulă integrală și variabilă poate duce la pierderea preciziei și, uneori, la rezultate incorecte, așa cum discut mai jos. Dacă doriți acest lucru, plătiți-l: în caz contrar, standardul impune ca compilatoarele să respecte un set strict de reguli pentru amestecarea tipurilor de date. Acesta este un comportament bine definit: ca dezvoltator C ++, pot căuta acest lucru în standard și pot vedea cum funcționează.


Există în esență trei moduri de a face ceea ce încercați să faceți, fiecare cu beneficii și dezavantaje.

  • Matematică întregi: acest lucru duce la trunchierea rezultatelor în timpul divizării așa cum ați aflat. Dacă doriți porțiunea zecimală, trebuie să o tratați separat împărțind, obținând restul și tratând porțiunea zecimală ca restul împărțit la divizor. Aceasta este o operațiune puțin mai complexă și are mai multe variabile de jonglat.

  • Matematică în virgulă mobilă: aceasta va produce în general rezultate corecte (suficiente) pentru valori mici, dar poate introduceți cu ușurință erori cu precizie și rotunjire, mai ales pe măsură ce exponentul crește. Dacă împărțiți un număr mare la un număr mic, puteți provoca chiar o scurgere sau pur și simplu obțineți un rezultat greșit, deoarece scările numerelor nu se joacă frumos între ele.

  • Fă-ți propriile calcule. Există clase care gestionează precizie extinsă a numerelor zecimale și raționale . Acestea vor fi de obicei mai lente decât matematica pentru tipurile încorporate, dar sunt în general destul de rapide și oferă matematică de precizie arbitrară. Problemele de rotunjire și alte probleme nu sunt automate, așa cum sunt în cazul flotei IEEE, dar veți obține mai mult control și cu siguranță mai multă precizie.

Cheia aici este să alegeți pe baza domeniului problemei . Toate cele trei metode de reprezentare a numerelor au propriile avantaje și dezavantaje. Folosiți un contor de bucle? Alegeți un tip integral. Reprezentați locații în spațiul 3D? Probabil un grup de plutitoare. Doriți să urmăriți banii? Utilizați un tip cu zecimal fix.


1 Cele mai populare arhitecturi CPU (de ex. x86-64 ) va avea seturi separate de instrucțiuni care funcționează pe diferite tipuri de registre, cum ar fi număr întreg și virgulă mobilă, plus instrucțiuni suplimentare pentru a converti între integral, virgulă mobilă și diferite reprezentări ale acestora (semnate și nesemnate, plutitoare și duble). Unele dintre aceste operații pot implica și acces la memorie: convertiți o valoare și stocați-o în memorie (variabila sa). Matematica la nivelul procesorului nu este la fel de simplă ca „integer in, float out.„În timp ce adăugarea a două numere întregi poate fi o operațiune foarte simplă, posibil o singură instrucțiune, amestecarea tipurilor de date poate crește complexitatea.

Comentarii

  • Spuneți că standardul C ++ stipulează că acel comportament ar trebui să fie așa. De ce? ‘ nu ar face lucrurile mai ușor de spus, ” Divizarea numerelor întregi care nu sunt divizibile în mod egal rezultă în flotări, orice altă diviziune este un joc echitabil. ”
  • @ moonman239 vezi modificările mele.
  • @ moonman239 Nu pentru scriitorii de compilatoare. Multe arhitecturi CPU utilizate în mod obișnuit oferă un rezultat întreg atunci când li se cere să facă împărțirea cu două numere întregi. Ar trebui să implementeze o verificare pentru rezultate care nu sunt întregi și apoi să treacă pentru a utiliza punctul mobil mai lent În mod alternativ, ei ar fi putut să nu fie divizați în virgulă mobilă și să fi pierdut interesul celor care doreau matematica rapidă, a celor care doreau matematica exactă și a celor obișnuiți cu C. Schimbarea acum nu este ‘ o opțiune, deoarece aceasta ar rupe compatibilitatea cu codul existent.
  • Nu că ați pleda pentru aceasta ca alternativă, ci pentru a face tipul static al unei expresii depinde de valorile de rulare ale operanzilor câștigat ‘ nu funcționează cu C ++ ‘ sistem de tip static.
  • @ moonman239: A avea o operație care produce un tip diferit în funcție de valorile ale operanzilor este o nebunie pură.

Răspuns

Acest lucru se datorează evoluției hardware-ului. În primele zile ale computerelor, nu toate mașinile aveau o unitate cu virgulă mobilă, hardware-ul pur și simplu nu era capabil să înțeleagă noțiunea unui număr în virgulă mobilă. Desigur, numerele cu virgulă mobilă pot fi implementate ca o abstractizare a software-ului, dar care are dezavantaje semnificative. Toată aritmetica de pe aceste mașini trebuia să fie în mod implicit aritmetică întreagă pură.

Și astăzi, există o distincție fermă între unități aritmetice întregi și virgulă mobilă într-un procesor. Operanții lor sunt stocați în fișiere de registre separate pentru a începe, iar o unitate întreagă este conectată pentru a lua două argumente întregi și pentru a produce un rezultat întreg care se termină într-un registru întreg. Unele procesoare necesită chiar o valoare întreagă pentru a fi stocată în memorie și apoi reîncărcată înapoi într-un registru în virgulă mobilă, înainte de a putea fi recodificată într-un număr în virgulă mobilă, înainte de a putea efectua o divizare în virgulă mobilă pe ea.

a atare, decizia luată de dezvoltatorii C încă de la începutul limbajului (C ++ pur și simplu a moștenit acest comportament), a fost singura decizie adecvată de luat și rămâne de valoare astăzi: Dacă aveți nevoie de matematică în virgulă mobilă, o poate folosi. Dacă nu aveți nevoie de el, nu trebuie.

Comentarii

  • Este trist faptul că majoritatea constrângerilor care existau la crearea standardului C ++ este destul de depășită astăzi! De exemplu: ” nu plătiți pentru ceea ce nu utilizați. ” în zilele noastre, hardware-ul este considerat de la sine înțeles și toți utilizatorii doresc este execuție!
  • @ mahen23 Nu toți utilizatorii gândesc așa. Lucrez într-un domeniu în care programele sunt rulate pe mii de nuclee CPU în paralel. În acest domeniu, eficiența înseamnă bani, atât în ceea ce privește investițiile, cât și în ceea ce privește consumul de energie. Un limbaj precum Java nu suportă fantoma unei șanse în acea zonă, în timp ce C ++ o face.
  • @ mahen23 Nu, nu este ‘ t – sau mai bine, este numai dacă vă uitați la arhitecturile CPU actuale pentru desktop-uri și mai sus. Există încă multe sisteme încorporate care nu ‘ t sau acceptă parțial doar operații în virgulă mobilă, iar C, precum și C ++ continuă să le susțină pentru a oferi cea mai eficientă implementare posibilă folosind asamblor. BTW, limbaje chiar și de nivel superior, cum ar fi Python, fac distincția între operațiile întregi și FP – încercați 10 / 3.

Răspuns

10/2 cu numere întregi vă oferă exact 5 – răspunsul corect.

Cu matematică în virgulă mobilă, 10/2 ar putea să dea corect răspuns *.

Cu alte cuvinte, este imposibil ca numerele în virgulă mobilă să fie „perfecte” pe hardware-ul curent – doar matematica întregi poate fi corectă, din păcate nu poate face zecimale, dar există o muncă ușoară arounds.

De exemplu, în loc de 4/3, faceți (4 * 1000) / (3 * 1000) == 1333. Trebuie doar să desenați un .în software când afișați răspunsul utilizatorului dvs. (1.333). Acest lucru vă oferă un răspuns corect, în loc de unul care este „incorect cu un număr de zecimale.

Erorile matematice în virgulă mobilă se pot adăuga pentru a provoca erori semnificative – orice lucru important (cum ar fi finanțele) va folosi matematica întregi .

* exemplul 10/2 va fi de fapt corect cu matematică în virgulă mobilă, dar nu te poți baza pe asta, multe alte numere dau rezultate incorecte …pentru mai multe detalii, citiți: http://http.cs.berkeley.edu/~wkahan/ieee754status/ieee754.ps Ideea este că nu vă puteți baza pe precizie ori de câte ori sunt puncte flotante implicate

Comentarii

  • Implementările în virgulă mobilă conforme IEEE 754 vă vor oferi un rezultat exact pentru 10 / 2. De fapt, acestea vă vor oferi exact rezultate pentru orice operație care implică doar operanzi întregi care au un rezultat întreg, cu condiția ca operanzi și rezultatul să poată fi reprezentați exact, care pot fi întregi „suficient de mici”.
  • @ 5gon12eder acolo ‘ nu este nevoie să selectez, eu ‘ încerc doar să descriu o problemă complexă în termeni simpli. Întregul punct al susținerii valorilor care nu sunt întregi este să ai zecimale ( care se poate face folosind numere întregi, înmulțind pur și simplu totul cu numărul de zecimale pe care le doriți așa cum am demonstrat).

Răspuns

Deși din punct de vedere tehnic nu este complet corect, C ++ este încă considerat un superset de C, a fost inspirat de acesta și, ca atare, și-a însușit unele dintre proprietățile sale, diviziunea întregi fiind una dintre ele. mult mai rapid decât virgulele mobile, deoarece tipul întreg este legat de hardware, în timp ce virgulele mobile trebuie calculate.

Când operandul / primește două numere întregi, unul din partea stângă și una din dreapta, s-ar putea să nu facă nici măcar diviziune, rezultatul poate fi calculat folosind adăugarea simplă și o buclă, întrebând de câte ori se încadrează operandul din partea dreaptă în operandul din stânga.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *