Förstå bitvis operatörer

Bitvis operatörer är de konstiga ser operatörer som kan se svårt att förstå ... men inte mer! Den här enkla att följa artikeln hjälper dig att förstå vad de är och hur man använder dem, med några praktiska exempel för att visa dig när och varför du skulle behöva dem.


Introduktion

Bitvis operatörer är operatörer (precis som +, *, &&, etc.) som fungerar på ints och uints på binär nivå. Detta betyder att de ser direkt på binära siffror eller bitar av ett heltal. Det här låter läskigt, men i verkligheten är bitvis operatörer ganska lätta att använda och också ganska användbara!

Det är dock viktigt att du har en förståelse för binära tal och hexadecimala tal. Om du inte gör det, kolla in den här artikeln - det kommer verkligen hjälpa dig! Nedan är en liten applikation som låter dig prova de olika bitvisa operatörerna.

Oroa dig inte om du inte förstår vad som händer än, det kommer snart att vara klart ...


Erkännande av bitvisa operatörer

Låt oss ta en titt på de bitvisa operatörerna som AS3 levererar. Många andra språk är ganska lika (till exempel JavaScript och Java har praktiskt taget samma operatörer):

  • & (bitvis AND)
  • | (bitvis OR)
  • ~ (bitvis NOT)
  • ^ (bitvis XOR)
  • << (bitwise left shift)
  • >> (bitvis höger skift)
  • >>> (bitvis unsigned right shift)
  • & = (bitvis och uppgift)
  • | = (bitvis ELLER uppgift)
  • ^ = (bitvis XOR-uppgift)
  • <<= (bitwise left shift and assignment)
  • >> = (bitvis rätt skift och uppgift)
  • >>> = (bitvis osignerad höger skift och uppgift)

Det finns ett par saker du bör ta ifrån: För det första ser vissa bitvisa operatörer ut som operatörer du har använt tidigare (& vs &&, | vs. ||). Detta beror på att de är något liknande.

För det andra kommer de flesta bitvisa operatörerna med a sammansatt uppgiftsformulär av sig själva. Detta är detsamma som hur du kan använda + och + =, - och - =, etc.


Operatören

Upp först: Bitwise AND-operatören, &. En snabb heads-up men: normalt, ints och uints ta upp 4 byte eller 32 bitar utrymme. Detta betyder varje int eller uint lagras som 32 binära siffror. För denna handledning skänker vi ibland det ints och uints ta bara upp 1 byte och har bara 8 binära siffror.

Operatören jämför varje binär siffra med två heltal och returnerar ett nytt heltal, med en 1 var båda siffrorna hade en 1 och en 0 någon annanstans. Ett diagram är värt tusen ord, så här är en för att rensa upp saker. Det representerar att göra 37 & 23, vilket är lika med 5.

Lägg märke till hur varje binär siffra på 37 och 23 jämförs, och resultatet har en 1 var både 37 och 23 hade en 1 och resultatet har en 0 annars.

Ett vanligt sätt att tänka på binära siffror är som Sann eller falsk. Det vill säga 1 motsvarar Sann och O motsvarar falsk. Detta gör & operatören mer meningsfull.

När vi jämför två booleaner, gör vi normalt boolean1 && boolean2. Det uttrycket är bara sant om båda boolean1 och boolean2 är sanna. På samma sätt, heltal1 och heltal2 motsvarar, eftersom & operatören endast matar ut en 1 när båda binära siffrorna i våra två heltal är 1.

Här är ett bord som representerar den ideen:

En snygg liten användning av & operatören är att kontrollera om ett tal är jämnt eller udda. För heltal kan vi helt enkelt kolla högerbiten (även kallad minsta signifikanta bit) för att bestämma om heltalet är udda eller jämnt. Detta beror på att när den konverteras till bas 10 representerar den högra biten 20 eller 1. När den högsta punkten är 1 vet vi att vårt nummer är udda eftersom vi lägger till 1 till en massa krafter på två som alltid kommer att vara jämn. När den högsta punkten är 0 vet vi att vårt antal kommer att vara jämnt, eftersom det helt enkelt består av att lägga upp en massa jämna siffror.

Här är ett exempel:

 var randInt: int = int (Math.random () * 1000); om (randInt & 1) spår ("udda nummer");  annat spår ("jämnt nummer"); 

På min dator var denna metod ungefär 66% snabbare än användningen randInt% 2 att kontrollera om jämn och udda tal. Det är ganska en prestationsökning!


The | Operatör

Upp nästa är bitwise OR-operatören, |. Som du kanske har gissat, är | operatören är till || operatör som & operatör är till && operatören. The | operatören jämför varje binär siffra över två heltal och ger tillbaka en 1 om antingen av dem är 1. Återigen liknar detta || operatör med booleans.

Låt oss ta en titt på samma exempel som tidigare, förutom att nu använda | operatör istället för & operatören. Vi gör nu 37 | 23 vilket motsvarar 55:


Flaggor: En Användning av & och | operatörer

Vi kan dra nytta av & and | operatörer att tillåta oss att skicka flera alternativ till en funktion i en enda int.

Låt oss ta en titt på en eventuell situation. Vi bygger en popup-fönsterklass. I botten av det kan vi ha en Ja, Nej, Okej eller Avbryt-knapp eller någon kombination av dem - hur ska vi göra det här? Här är det svåra sättet:

 public class PopupWindow utökar Sprite // Variabler, Constructor, etc ... public static void showPopup (yesButton: Boolean, noButton: Boolean, okayButton: Boolean, cancelButton: Boolean) if (yesButton) // lägg till JA knappen om ) // lägg till ingen knapp // och så vidare för resten av knapparna

Är det här hemskt? Nej. Men det är dåligt, om du är en programmerare, att behöva leta upp ordningsföljden varje gång du ringer upp funktionen. Det är också irriterande, till exempel om du bara vill visa knappen Avbryt måste du ställa in alla andra Booleans till falsk.

Låt oss använda vad vi lärde oss om & och | för att göra en bättre lösning:

 public class PopupWindow utökar Sprite public static const YES: int = 1; offentlig statisk konst nr: int = 2; statisk statisk konst OK: int = 4; statisk statisk konst CANCEL: int = 8; public static void showPopup (knappar: int) if (knappar och JA) // lägg till JA-knappen om (knappar och NEJ) // lägg till NO-knappen

Hur skulle en programmerare ringa funktionen så att Ja-knappen, Nej-knappen och Avbryt-knappen visas? Så här:

 PopupWindow.show (PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL);

Vad pågår? Det är viktigt att notera att våra konstanter i det andra exemplet är alla befogenheter av två. Så, om vi tittar på deras binära former, märker vi att de alla har en siffra lika med 1 och resten är lika med 0. Faktum är att de alla har en annan siffra som är lika med 1. Det betyder att oavsett hur vi kombinerar dem med |, kommer varje kombination ge oss ett unikt nummer. Titta på det på ett annat sätt, resultatet av vår | uttalandet kommer att vara ett binärt nummer med en 1 varhelst våra alternativ hade en 1.

För vårt nuvarande exempel har vi PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL vilket motsvarar 1 | 2 | 8 som omskrivits i binär är 00000001 | 00000010 | 00001000 vilket ger oss ett resultat av 00001011.

Nu, i vår visa popup() funktion använder vi & för att kontrollera vilka alternativ som skickades in. Till exempel när vi kontrollerar knappar & YES, alla bitar i JA är lika med 0 utom den högra högsta. Så, vi kontrollerar i huvudsak om den högsta delen i knapparna är en 1 eller inte. Om det är, knappar & YES kommer inte att vara lika med 0 och allt i if-steget kommer att utföras. Omvänt, om den högsta delen i knapparna är 0, knappar & YES kommer att motsvara 0, och if-steget kommer inte att utföras.


Operatören

Bitwise NOT-operatören är något annorlunda än de två vi har tittat på hittills. I stället för att ta ett heltal på varje sida av det tar det ett heltal först efter det. Det här är precis som! operatör, och inte överraskande, det gör en liknande sak. Faktum är precis som! vänder en boolesk från Sann till falsk eller vice versa, återställer operatören varje binär siffra i ett heltal: från 0 till 1 och 1 till 0:

Ett snabbt exempel. Säg att vi har heltalet 37 eller 00100101. ~ 37 är då 11011010. Vad är basens 10 värde för detta? Väl…


Två komplement, uint mot. int, och mer!

Nu börjar roligt! Vi ska titta närmare på binära nummer på en dator. Låt oss börja med uint. Som tidigare nämnts, a uint är typiskt 4 byte eller 32 bitar lång, vilket betyder att den har 32 binära siffror. Det här är lätt att förstå: för att få basen 10-värdet konverterar vi numret till bas 10 regelbundet. Vi får alltid ett positivt nummer.

Men hur är det med int? Det använder också 32 bitar, men hur lagras det negativa tal? Om du gissade att den första siffran används för att lagra skylten, är du på rätt väg. Låt oss ta en titt på två komplement system för lagring av binära nummer. Medan vi inte kommer in i alla detaljer här används ett två komplementssystem, eftersom det gör binär aritmetik lätt.

För att hitta de två komplementen av ett binärt tal vänder vi helt enkelt alla bitar (dvs gör vad operatören gör) och lägg till en till resultatet. Låt oss prova detta en gång:

Vi definierar då vårt resultat som värdet -37. Varför gör denna komplicerade process och inte bara vända den allra första biten och kalla det -37?

Tja, låt oss ta ett enkelt uttryck 37 + -37. Vi vet alla att detta borde vara 0, och när vi lägger till 37 till dess två komplement, det är vad vi får:

Observera att eftersom våra heltal bara innehåller åtta binära siffror faller 1 i vårt resultat och vi hamnar med 0, som vi borde.

För att återskapa, för att hitta det negativa av ett tal, tar vi helt enkelt dess två komplement. Vi kan göra detta genom att invertera alla bitar och lägga till en.

Vill du prova detta själv? Lägg till spåra (~ 37 + 1); till en AS3-fil, kompilera och kör sedan den. Du kommer att se -37 skrivs ut, som det borde vara.

Det finns också en liten genväg för att göra det för hand: Börja från höger, arbeta till vänster tills du når en 1. Vänd alla bitarna till vänster om den här första 1.

När vi tittar på ett signerat binärt tal (med andra ord en som kan vara negativ, en int inte en uint) kan vi titta på den vänstra siffran för att se om det är negativt eller positivt. Om det är en 0, är ​​numret positivt och vi kan konvertera till bas 10 helt enkelt genom att beräkna dess bas 10-värde. Om den vänstra delen är en 1, är siffran negativ, så vi tar de två komplementen till numret för att få det positiva värdet och sedan helt enkelt lägga till ett negativt tecken.

Om vi ​​till exempel har 11110010 vet vi att det är ett negativt tal. Vi kan hitta det två komplementet genom att vända alla siffrorna till vänster om höger 1, vilket ger oss 00001110. Detta motsvarar 13, så vi vet att 11110010 är lika med -13.


Operatören

Vi är tillbaka till bitvisa operatörer, och uppåt är den bitvisa XOR-operatören. Det finns ingen motsvarande boolean operatör till den här.

Operatören ^ liknar & och | operatörer genom att det tar en int eller uint på båda sidor. När det beräknas det resulterande numret jämförs det igen de binära siffrorna i dessa nummer. Om den ena eller den andra är en 1, kommer den att sätta in en 1 in i resultatet, annars kommer det att infoga en 0. Det är här namnet XOR, eller "exklusivt eller" kommer från.

Låt oss ta en titt på vårt vanliga exempel:

Operatören har användningsområden - det är särskilt bra att byta binära siffror - men vi kommer inte att täcka några praktiska tillämpningar i den här artikeln.


De << Operator

Vi är nu på bithift-operatörerna, specifikt den bitvisa vänsterväxeloperatören här.

Dessa fungerar lite annorlunda än tidigare. Istället för att jämföra två heltal som &, |, och ^ ändrade dessa operatörer ett heltal. På vänster sida av operatören är heltalet som förskjuts, och till höger är hur mycket som ska bytas ut. Så, till exempel, 37 << 3 is shifting the number 37 to the left by 3 places. Of course, we're working with the binary representation of 37.

Låt oss ta en titt på detta exempel (kom ihåg, vi ska bara låtsas att heltal bara har 8 bitar istället för 32). Här har vi nummer 37 som sitter på sitt fina block av minne 8 bitars breda.

Okej, låt oss glida alla siffror över till vänster med 3, som 37 << 3 skulle göra:

Men nu har vi ett litet problem - vad gör vi med de 3 öppna bitarna av minne där vi flyttade siffrorna från?

Självklart! Några tomma fläckar ersätts bara med 0s. Vi hamnar med 00101000. Och det är allt som finns till vänster bithift. Tänk på att Flash alltid tycker att resultatet av en vänster bithift är en int, inte en uint. Så om du behöver en uint av någon anledning måste du kasta den till en uint så här: uint (37 << 3). Denna gjutning ändrar inte faktiskt någon av binärinformationen, bara hur Flash tolkar det (hela tv-komplementet).

En intressant egenskap hos vänster bithift är att det är detsamma som att multiplicera ett tal med två till shiftAmount-th-effekten. Så, 37 << 3 == 37 * Math.pow(2,3) == 37 * 8. Om du kan använda vänsterskiftet istället för Math.pow, du ser en enorm prestationsökning.

Du kanske har märkt att binärnumret vi slutade inte var lika med 37 * 8. Det här är bara från vår användning av endast 8 bitars minne för heltal; om du försöker det i ActionScript får du rätt resultat. Eller försök med demo högst upp på sidan!


>> Operatören

Nu när vi förstår den vänstra bitskiftet kommer nästa, den rätta bitskiftet, att bli lätt. Allt glider till höger om det belopp vi anger. Den enda mindre skillnaden är vad de tomma bitarna fylls med.

Om vi ​​börjar med ett negativt tal (ett binärt tal där den vänstra delen är en 1) fylls alla tomma mellanslag med en 1. Om vi ​​börjar med ett positivt tal (där den vänstra delen eller mest signifikanta bit, är 0), då fylls alla tomma utrymmen med en 0. Återigen går allt tillbaka till två komplement.

Även om detta låter komplicerat, behåller det i princip bara tecknet på numret vi börjar med. Så -8 >> 2 == -2 medan 8 >> 2 == 2. Jag skulle rekommendera att prova dem på papper själv.

Eftersom >> är motsatsen till <<, it's not surprising that shifting a number to the right is the same as dividing it by 2 to the power of shiftAmount. You may have noticed this from the example above. Again, if you can use this to avoid calling Math.pow, du får en betydande prestationsökning.


Operatören >>>

Vår sista bitvisa operatör är den bitvisa, unsigned right shiften. Detta liknar det vanliga bitvisa högerskiftet, förutom att alla tomma bitar till vänster är fyllda med 0s. Detta innebär att resultatet av denna operatör alltid är ett positivt heltal och det behandlar alltid heltalet som förskjuts som ett osignerat heltal. Vi kommer inte att springa igenom ett exempel på detta i det här avsnittet, men vi kommer se en ny användning för det inom kort.


Använda bitvis operatörer att arbeta med färger

En av de mest praktiska användningarna av bitvisa operatörer i ActionScript 3 arbetar med färger, som lagras typiskt som uints.

Standardformatet för färger är att skriva dem i hexadecimalt: 0xAARRGGBB - varje bokstav representerar en hexadecimal siffra. Här representerar de två första hexadecimala siffrorna, som motsvarar de första åtta binära siffrorna, vår alfa eller genomskinlighet. De följande åtta bitarna representerar mängden rött i vår färg (så ett heltal från 0 till 255), nästa åtta mängden grön och den sista åtta representerar mängden blå i vår färg.

Utan bitwise operatörer är det extremt svårt att arbeta med färger i detta format - men med dem är det enkelt!

Utmaning 1: Hitta mängden blå i en färg: Använd & operatören, försök att hitta mängden blå i en godtycklig färg.

 public function findBlueComponent (färg: uint): uint // Din kod här! 

Vi behöver ett sätt att "radera" eller maskera alla andra data i Färg och bara har den blå komponenten kvar. Det här är enkelt, faktiskt! Om vi ​​tar färg & 0x000000FF - eller, skrivet mer enkelt, färg & 0xFF - vi hamnar bara med den blå komponenten.

Som du kan se ovanifrån och du lärde dig i beskrivningen av & operatören, kommer alla binära siffror & 0 alltid att vara lika med 0, medan alla binära siffror & 1 kommer att hålla sitt värde. Så om vi maskerar vår färg med 0xFF som bara har 1 där den blå komponenten i vår färg ligger, hamnar vi bara med den blå komponenten.

Utmaning 2: Hitta mängden rött i en färg: Använd två bitvis operatorer, försök att hitta mängden rött i en godtycklig färg.

 public function findRedComponent (färg: uint): uint // Din kod här! 

Vi har faktiskt två lösningar på detta problem. Man skulle vara returnera (färg & 0xFF0000) >> 16; och den andra skulle vara returnera (färg >> 16) & 0xFF;

Det här ligner mycket utmaning 1, förutom att vi måste byta vårt svar vid någon tidpunkt.

Utmaning 3: Hitta transparens i en färg: Använd bara en bitvis operatör, försök att hitta alfabet av en färg (ett heltal från 0 till 255).

 allmän funktion findAlphaComponent (färg: uint): uint // Din kod här! 

Den här är snabbare. Vi måste vara försiktiga med vilken rätt skiftoperatör vi väljer. Eftersom vi arbetar med de vänstra siffrorna i a uint, Vi vill använda >>> operatören. Så, vårt svar är helt enkelt returfärg >>> 24;.

Slutlig utmaning: Skapa en färg från dess komponenter: Använda << and | operators, take the components of a color and merge them in to one uint.

 public function createColor (a: uint, r: uint, g: uint, b: uint): uint // Din kod här! 

Här måste vi flytta varje komponent till sin rätta position, och sedan slå samman dem. Vi vill att Flash ska behandla det som ett osignerat heltal, så vi kastar det till en uint: returnera uint ((a << 24) | (r << 16) | (g << 8) | b);


Sammansatta operatörer

Du kanske har märkt att jag har försummat att förklara de sammansatta bitvisa operatörerna. Föreställ dig att vi har ett heltal x. Sedan, x = x & 0xFF är det samma som x & = 0xFF, x = x | 256 är det samma som x | = 256, och så vidare för övriga operatörer.


Slutsats

Tack för att du läste den här artikeln! Jag hoppas att du nu förstår bitvis operatörer och kan använda dem i din AS3-kod (eller på många andra språk!). Som alltid, om du har några frågor eller kommentarer, vänligen lämna dem nedan.