Varför resulterar heltalsdelning i ett heltal?

Vi lärde oss i introduktionen till programmering att om du delar två heltal får du alltid ett heltal. För att lösa problemet, gör åtminstone ett av dessa heltal till en flottör.

Varför förstår inte kompilatorn att jag vill att resultatet ska vara ett decimaltal?

Kommentarer

  • Du kanske vill att resultatet ska bli ett heltal istället. Hur kan det se skillnaden?
  • Eftersom C ++ -standarden säger det.
  • @soandos: Hur Pascal och Python båda gör det: har en distinkt heldelningsoperator, men standarddelningsoperatören returnerar alltid det matematiskt korrekta resultatet. (Eller – för pedanten – så korrekt som du kan få med tanke på begränsningar av FP-matematik.)
  • Hur skulle kompilatorn veta att resultatet ska vara ett decimaltal? Det finns giltiga skäl att vilja ha ett heltal.
  • @soandos I Python, // är heltalsdelningsoperatören och # är en enradig kommentar. I Pascal är operatören för heldelningsdelningen nyckelordet div. Båda fungerar ganska bra för deras respektive språk ges. C gör förmodligen det värsta möjliga: en operatör som kan göra två helt olika saker baserat på godtyckligt sammanhang.

Svar

Varför förstår inte kompilatorn att jag vill att resultatet ska vara ett decimaltal?

C ++ – kompilatorn följer helt enkelt väldefinierade och deterministiska regler som anges i C ++ – standarden. C ++ – standarden har dessa regler eftersom standardkommittén bestämde sig för att göra det på det sättet.

De kunde ha skrivit standarden för att säga att heltalsmatematik resulterar i flytande siffror, eller gör det bara i en återstående del. Men det lägger till komplexitet: Jag måste antingen veta i förväg vad resultatet är, eller kanske konvertera tillbaka till heltal om det alltid ger en flottör. Kanske vill jag vilja ett heltal .

En av C ++: s kärnfilosofier är ”du betalar inte för det du inte använder . ” Om du faktiskt vill komplexiteten i att blanda heltal och flottörer (och extra CPU-instruktioner och minnesåtkomst innebär detta 1 ), gör sedan typen som du nämnde i din fråga . Annars, håll dig till standard heltalsmatematik.

Slutligen kan blandning av integrerade och flytande variabler resultera i förlust av precision och ibland felaktiga resultat som jag diskuterar nedan. Om du vill ha detta ska du betala för det: annars föreskriver standarden att kompilatorer håller sig till en strikt uppsättning regler för blandning av datatyper. Detta är väldefinierat beteende: som C ++ -utvecklare kan jag leta upp detta i standarden och se hur det fungerar.


Det finns i huvudsak tre sätt att göra vad du försöker göra, var och en med fördelar och nackdelar.

  • Heltalsmatematik: detta resulterar i att trunkerar resultat under delningen som du fick reda på. Om du vill ha decimaldelen måste du behandla den separat genom att dela, få resten och behandla decimaldelen som resten dividerat med delaren. Detta är lite mer komplicerat i en operation och har fler variabler att jonglera.

  • Flytpunktsmatematik: detta ger i allmänhet korrekta (tillräckligt) resultat för små värden, men kan enkelt införa fel med precision och avrundning, särskilt när exponenten ökar. Om du delar ett stort antal med ett litet antal kan du till och med orsaka ett underflöde eller helt enkelt få fel resultat eftersom skalorna på siffrorna inte spelar snyggt med varandra.

  • Gör din egen matte. Det finns klasser där ute som hanterar utökad precision av decimaler och rationella tal . Dessa är vanligtvis långsammare än matematik på inbyggda typer, men är i allmänhet fortfarande ganska snabba och ger godtycklig precision matematik. Avrundning och andra problem är inte automatiska som med IEEE-flottörer, men du får mer kontroll och säkert mer noggrannhet.

Nyckeln här är att välja baserat på problemdomänen . Alla tre metoder för att representera siffror har sina egna fördelar och nackdelar. Använder du en loopräknare? Välj en integrerad typ. Representerar platser i 3D-rymden? Förmodligen en grupp flottör. Vill du spåra pengar? Använd en fast decimaltyp.


1 Mest populära CPU-arkitekturer (t.ex. x86-64 ) kommer att ha separata uppsättningar instruktioner som fungerar på olika registertyper som heltal och flytpunkt, plus extra instruktioner för att konvertera mellan integrerad, flytande punkt och olika representationer av dem (signerad och osignerad, flyt och dubbel). Några av dessa operationer kan också innebära minnesåtkomst: konvertera ett värde och lagra det i minnet (dess variabel). Matematik på CPU-nivå är inte så enkelt som ”heltal in, flyta ut.”Att lägga till två heltal kan vara en mycket enkel operation, eventuellt en enda instruktion, men blandning av datatyper kan öka komplexiteten.

Kommentarer

  • Du säger att C ++ -standarden anger att detta beteende ska vara så. Varför? Skulle ’ inte göra det lättare att säga, ” Uppdelning av heltal som inte är jämnt delbara resulterar i flytningar, alla andra delar är rättvist spel. ”
  • @ moonman239 se mina ändringar.
  • @ moonman239 Inte för kompilatorförfattare. Många vanliga CPU-arkitekturer ger ett heltalresultat när de ombeds göra delning med två heltal. De måste genomföra en kontroll för resultat som inte är heltal och sedan växla till att använda den långsammare flytpunkten Alternativt kunde de ha fallerat i punktdelning och tappat intresset för dem som ville ha snabb matematik, de som ville ha exakt matematik och de som var vana vid C. Ändra nu är inte ’ t ett alternativ eftersom det skulle bryta kompatibiliteten med befintlig kod.
  • Inte för att du skulle förespråka detta som ett alternativ utan att göra den statiska typen av ett uttryck beror på körningsvärdena för de operander som vunnits ’ för att arbeta med C ++ ’ s system för statisk typ.
  • @ moonman239: Att ha en operation som producerar en annan typ beroende på operandernas värden är ren galenskap.

Svar

Detta beror på utvecklingen av hårdvara. Tillbaka i början av datorer hade inte alla maskiner en flytande enhet, hårdvaran kunde helt enkelt inte förstå begreppet flytande nummer. Naturligtvis kan flytande nummer implementeras som en mjukvaruabstraktion, men det har betydande nackdelar. All aritmetik på dessa maskiner måste vara ren heltalaritmetik som standard.

Och fortfarande idag skiljer det en fast skillnad mellan hel- och flytpunktsräkningsenheter inom en CPU. Deras operander lagras i separata registerfiler till att börja med, och en heltalsenhet är kopplad för att ta två heltalargument och producera ett heltalresultat som hamnar i ett heltalregister. Vissa processorer kräver till och med att ett heltal ska lagras i minnet och sedan laddas om till ett flytpunktsregister innan det kan kodas om till ett flytpunktsnummer innan du kan utföra en flytpunktsdelning på det.

Som sådant var beslutet från C-utvecklarna redan i början av språket (C ++ ärvde helt enkelt detta beteende), det enda lämpliga beslutet att ta och förblir värdefullt idag: Om du behöver flytande matematik, du kan använda den. Om du inte behöver det behöver du inte.

Kommentarer

  • Det är tråkigt att de flesta begränsningar som fanns vid skapandet av C ++ – standarden är ganska föråldrad idag! Till exempel: ” du betalar inte för det du inte använder. ” idag är hårdvara för givet och alla användare vill ha är körning!
  • @ mahen23 Inte alla användare tänker så här. Jag arbetar inom ett fält där program körs på tusentals CPU-kärnor parallellt. På detta område är effektivitet pengar, både när det gäller investeringar och i termer av ren energiförbrukning. Ett språk som Java står inte för en chansspöke i det området, medan C ++ gör det.
  • @ mahen23 Nej det är inte ’ t – eller bättre, det bara är om du tittar på nuvarande CPU-arkitekturer för stationära datorer och senare. Det finns fortfarande många inbäddade system som inte ’ t eller endast delvis stöder flytande punktoperationer, och såväl C som C ++ fortsätter att stödja dem för att ge en så effektiv implementering som möjligt med hjälp av monteraren. BTW, ännu högre nivåspråk som Python skiljer mellan heltal och FP-operationer – prova 10 / 3.

Svar

10/2 med heltal ger dig exakt 5 – rätt svar.

Med flytande matematik kan 10/2 kanske ge rätt svara *.

Med andra ord är det omöjligt för flytande punktsiffror att vara ”perfekta” på nuvarande hårdvara – bara heltalsmatematik kan vara korrekt, tyvärr kan det inte göra decimaler men det finns enkelt arbete runt.

Till exempel i stället för 4/3, gör (4 * 1000) / (3 * 1000) == 1333. Rita bara a. i programvara när du visar svaret för din användare (1.333). Detta ger dig ett exakt svar istället för ett som är felaktigt med ett antal decimaler.

Matematiska fel med flytande punkter kan lägga upp för att orsaka betydande fel – allt som är viktigt (som ekonomi) använder heltalsmatematik .

* 10/2-exemplet kommer faktiskt att vara korrekt med flytande matematik, men du kan inte lita på det, många andra siffror ger felaktiga resultat …läs lite för mer information: http://http.cs.berkeley.edu/~wkahan/ieee754status/ieee754.ps Poängen är att du inte kan lita på noggrannhet när flytpunkter är inblandade

Kommentarer

  • IEEE 754-kompatibla flytpunktsimplementeringar ger dig ett exakt resultat för 10 / 2. Faktum är att de ger dig exakta resultat för alla operationer som endast involverar heltal operander som har ett heltal resultat förutsatt att operander och resultat kan representeras exakt, vilka ”tillräckligt små” heltal kan.
  • @ 5gon12eder där ’ s inget behov av att plocka, jag ’ jag försöker bara beskriva ett komplext problem i enkla termer. Hela poängen för att stödja icke-heltal är att ha decimaler som kan göras med heltal genom att helt enkelt multiplicera allt med antalet decimaler du vill ha som jag bestämde mig för.

Svar

Även om det tekniskt inte är helt korrekt, C ++ betraktas fortfarande som ett superset av C, inspirerades av det och använde som sådant några av dess egenskaper, heltal är en av dem.

C var mestadels utformad för att vara effektiv och snabb, och heltal är i allmänhet mycket snabbare än flytpunkter, eftersom heltalstypen är knuten till hårdvara, medan flytpunkter måste beräknas.

När / operand får två heltal, ett på vänster sida och en till höger, kanske det inte ens gör uppdelning alls, resultatet kan beräknas med enkel tillsats och en slinga och frågar hur många gånger passar operanden på höger sida i operanden till vänster.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *