Fast Reverse Square Root (även snabb InvSqrt() eller 0x5F3759DF med den "magiska" konstanten som används , i decimal 1 597 463 007) är en snabb approximativ algoritm för att beräkna den inversa kvadratroten för positiva 32-bitars flyttal . Algoritmen använder heltalsoperationer "subtrahera" och " bitskift ", samt bråktal "subtrahera" och "multiplicera" - utan långsamma operationer "divide" och "kvadratrot". Trots att den är "hackig" på bitnivå är approximationen monoton och kontinuerlig: nära argument ger nära resultat. Noggrannhet (mindre än 0,2 % ner och aldrig upp) [1] [2] räcker inte för riktiga numeriska beräkningar, men det är tillräckligt för tredimensionell grafik .
Algoritmen tar ett 32-bitars flyttal ( enkel precision i IEEE 754 -format ) som indata och utför följande operationer på det:
Ursprunglig algoritm:
float Q_rsqrt ( flytnummer ) _ { lång i ; flyta x2 , y ; const float threehalvs = 1,5F ; x2 = antal * 0,5F ; y = nummer ; i = * ( lång * ) & y ; // ond floating point bit level hacking i = 0x5f3759df - ( i >> 1 ); // vad fan? y = * ( flytande * ) & i ; y = y * ( trehalvor - ( x2 * y * y ) ); // Första iterationen // y = y * (trehalvor - (x2 * y * y)); // 2:a iterationen, denna kan tas bort returnera y ; }Korrigera enligt standarderna för modern C-implementering, med hänsyn till möjliga optimeringar och plattformsoberoende :
float Q_rsqrt ( flytnummer ) _ { const float x2 = antal * 0,5F ; const float threehalvs = 1,5F ; union { flyta f ; uint32_t i ; } konv = { nummer }; // medlem 'f' satt till värdet på 'nummer'. konv . i = 0x5f3759df - ( konv . i >> 1 ); konv . f *= trehalvor - x2 * omv . f * konv . f ; returnera konv . f ; }Implementeringen från Quake III: Arena [3] anser att längden är lika med , och använder pekare för konvertering (optimeringen "om , ingen har ändrats" kan fungera felaktigt; på GCC, vid kompilering för att "släppa", är en varning upprörd). Och det innehåller också ett obscent ord - John Carmack , som lade ut spelet i det offentliga området, förstod inte vad som gjordes där. floatlongfloatlong
I C++20 kan du använda den nya funktionen . bit_cast
#inkludera <bit> #inkludera <gränser> #include <cstdint> constexpr float Q_rsqrt ( flytnummer ) noexcept _ { static_assert ( std :: numeric_limits < float >:: is_iec559 ); // Kontrollera om målmaskinen är kompatibel float const y = std :: bit_cast < float > ( 0x5f3759df - ( std :: bit_cast < std :: uint32_t > ( nummer ) >> 1 )); return y * ( 1,5f - ( nummer * 0,5f * y * y )); }Algoritmen utvecklades troligen på Silicon Graphics på 1990-talet, och en implementering dök upp 1999 i källkoden till dataspelet Quake III Arena , men metoden dök inte upp på offentliga forum som Usenet förrän 2002-2003. Algoritmen genererar någorlunda exakta resultat genom att använda den unika första approximationen av Newtons metod . På den tiden var den största fördelen med algoritmen avvisandet av dyra flyttalsberäkningar till förmån för heltalsoperationer. Omvända kvadratrötter används för att beräkna infallsvinklar och reflektion för belysning och skuggning i datorgrafik.
Algoritmen tillskrevs ursprungligen John Carmack , men han föreslog att Michael Abrash , en grafikspecialist, eller Terje Mathisen, en assemblerspråkspecialist, tog den till id Software . Studien av problemet visade att koden hade djupare rötter i både hårdvaru- och mjukvaruområdena för datorgrafik. Korrigeringar och ändringar har gjorts av både Silicon Graphics och 3dfx Interactive , med den tidigaste kända versionen skriven av Gary Tarolli för SGI Indigo . Kanske uppfanns algoritmen av Greg Walsh och Clive Moler , Garys kollegor på Ardent Computer [5] .
Jim Blinn, en 3D-grafikspecialist, har föreslagit en liknande tabellformad invers kvadratrotmetod [6] som räknar upp till 4 siffror (0,01%) och hittades när spelet Interstate '76 (1997) plockades isär [7] .
Med lanseringen av 3DNow! AMD - processorer introducerade PFRSQRT [8] assemblerinstruktionen för snabb ungefärlig beräkning av den inversa kvadratroten. Versionen för dubbel är meningslös - noggrannheten i beräkningarna kommer inte att öka [2] - därför lades den inte till. År 2000 lades RSQRTSS-funktionen [9] till SSE2 , vilket är mer exakt än denna algoritm (0,04 % mot 0,2 %).
Bitrepresentationen av ett 4-byte bråktal i IEEE 754 -format ser ut så här:
Tecken | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Ordning | Mantissa | |||||||||||||||||||||||||||||||
0 | 0 | ett | ett | ett | ett | ett | 0 | 0 | 0 | ett | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
31 | 24 | 23 | 16 | femton | åtta | 7 | 0 |
Vi behandlar bara positiva tal (teckenbiten är noll), inte denormaliserade , inte ∞ och inte NaN . Sådana nummer skrivs i standardform som 1,mmmm 2 ·2 e . Delen 1,mmmm kallas mantissan , e är ordningen . Huvudenheten lagras inte ( implicit enhet ), så låt oss kalla värdet 0,mmmm den explicita delen av mantissan. Dessutom har maskinbråktal en förskjuten ordning : 2 0 skrivs som 011.1111.1 2 .
På positiva tal är "bråkdelen ↔ heltal" -bijektionen (betecknad nedan som ) kontinuerlig som en bitvis linjär funktion och monoton . Av detta kan vi omedelbart konstatera att den snabba inversa roten, som en kombination av kontinuerliga funktioner, är kontinuerlig. Och dess första del - skift-subtraktion - är också monoton och bitvis linjär. Bijektionen är komplicerad, men nästan "gratis": beroende på processorarkitekturen och anropskonventioner behöver du antingen inte göra någonting eller flytta numret från bråkregistret till heltalsregistret.
Till exempel är den binära representationen av det hexadecimala heltal 0x5F3759DF 0|101.1111.0|011.0111.0101.1001.1101.1111 2 (punkter är nibble-gränser, vertikala linjer är datorbråkgränser). Ordningen på 101 1111 0 2 är 190 10 , efter subtrahering av offset 127 10 får vi exponenten 63 10 . Den explicita delen av mantissan 01 101 110 101 100 111 011 111 2 efter tillägg av den implicita ledande enheten blir 1,011 011 101 011 001 110 111 11 2 = 1,432 4... 3 . Med hänsyn till den verkliga precisionen för datorbråk 0x5F3759DF ↔ 1,4324301 10 2 63 .
Vi betecknar den explicita delen av mantissan av numret , - oförskjuten ordning, - bitlängd av mantissan, - förskjutning av ordningen. Antalet , skrivet i ett linjärt-logaritmiskt bitrutnät av datorbråk, kan [10] [3] approximeras av ett logaritmiskt rutnät som , där är en parameter som används för att justera approximationens noggrannhet. Denna parameter sträcker sig från 0 (exakt vid och ) till 0,086 (exakt vid en punkt, )
Med hjälp av denna approximation kan heltalsrepresentationen av ett tal approximeras som
Följaktligen, .
Låt oss göra samma sak [3] för (respektive ) och erhålla
Den magiska konstanten , med hänsyn till gränserna , kommer i bråkaritmetik att ha en opartisk ordning och mantissa ), och i binär notation - 0|101.1111.0|01 1 ... (1 är en implicit enhet; 0,5 kom från ordningen ; en liten enhet motsvarar intervallet [1,375; 1,5) och är därför mycket troligt, men inte garanterat av våra uppskattningar.)
Det är möjligt att beräkna vad den första bitvis linjära approximationen [11] är lika med (i källan, inte själva mantissan, utan dess explicita del används ):
På större eller mindre ändras resultatet proportionellt: när det fyrdubblas halveras resultatet exakt.
Newtons metod ger [11] , , och . Funktionen är avtagande och konvex nedåt, på sådana funktioner närmar sig Newtons metod det sanna värdet från vänster - därför underskattar algoritmen alltid svaret.
Det är inte känt var konstanten 0x5F3759DF ↔ [a] 1.4324301 2 63 kom ifrån . Med brute force upptäckte Chris Lomont och Matthew Robertson [1] [2] att den bästa konstanten [b] i termer av marginellt relativ fel för float är 0x5F375A86 ↔ 1,4324500 2 63 , för dubbelt är det 0x5FE6EB50C7B537A9. Sant, för dubbel är algoritmen meningslös (ger ingen vinst i noggrannhet jämfört med float) [2] . Lomont-konstanten erhölls också analytiskt ( c = 1,432450084790142642179 ) [b] , men beräkningarna är ganska komplicerade [11] [2] .
Efter ett steg i Newtons metod är resultatet ganska exakt ( +0 % -0,18 % ) [1] [2] , vilket är mer än lämpligt för datorgrafikändamål ( 1 ⁄ 256 ≈ 0,39 % ). Ett sådant fel bevaras över hela området av normaliserade bråktal. Två steg ger en noggrannhet på 5 siffror [1] , efter fyra steg uppnås dubbelfelet . Om så önskas kan du balansera om felet genom att multiplicera koefficienterna 1,5 och 0,5 med 1,0009 så att metoden ger symmetriskt ±0,09% - det är vad de gjorde i spelet Interstate '76 och Blinn-metoden, som också itererar Newtons metod [7 ] .
Newtons metod garanterar inte monotonitet, men datoruppräkning visar att monotonitet existerar.
Källkod (C++) #include <iostream> union FloatInt { flyta somFlöta ; int32_t asInt ; }; int floatToInt ( float x ) { FloatInt r ; r . asFloat = x ; returnera r . asInt ; } float intToFloat ( int x ) { FloatInt r ; r . asInt = x ; returnera r . asFloat ; } float Q_rsqrt ( flytnummer ) _ { lång i ; flyta x2 , y ; const float threehalvs = 1,5F ; x2 = antal * 0,5F ; y = nummer ; i = * ( lång * ) & y ; // evil flyttal bitnivå hacking i = 0x5f3759df - ( i >> 1 ); // vad fan? y = * ( flytande * ) & i ; // jag vet inte vad fan! y = y * ( trehalvor - ( x2 * y * y ) ); // Första iterationen returnera y ; } int main () { int iStart = floatToInt ( 1.0 ); int iEnd = floatToInt ( 4.0 ); std :: cout << "Number to go: " << iEnd - iStart << std :: endl ; int nProblem = 0 ; float oldResult = std :: numeric_limits < float >:: infinity (); for ( int i = iStart ; i <= iEnd ; ++ i ) { float x = intToFloat ( i ); float resultat = Q_rsqrt ( x ); if ( resultat > gammalt Resultat ) { std :: cout << "Hittade ett problem på " << x << std :: endl ; ++ nProblem ; } } std :: cout << "Totala problem: " << nProblem << std :: endl ; returnera 0 ; }Det finns liknande algoritmer för andra potenser, såsom kvadrat eller kubrot [3] .
"Direkt" överlagring av belysning på en tredimensionell modell , till och med en högpoly-modell, även med hänsyn till Lamberts lag och andra formler för reflektion och spridning, kommer omedelbart att ge ett polygonalt utseende - betraktaren kommer att se skillnaden i belysning längs med polyederns kanter [12] . Ibland är detta nödvändigt - om föremålet är riktigt kantigt. Och för kurvlinjära föremål gör de detta: i ett tredimensionellt program indikerar de om en skarp kant eller en slät kant [12] . Beroende på detta, även när du exporterar modellen till spelet, beräknar trianglarnas hörn normalen för enhetslängd till den krökta ytan. Under animering och rotation omvandlar spelet dessa normaler tillsammans med resten av 3D-data; vid applicering av belysning interpolerar den över hela triangeln och normaliserar (tar den till en enhetslängd).
För att normalisera en vektor måste vi dividera alla tre dess komponenter med längden. Eller, bättre, multiplicera dem med den reciproka av längden: . Miljontals av dessa rötter måste beräknas per sekund. Innan dedikerad hårdvara för transformation och ljusbehandling skapades kunde datormjukvaran vara långsam. I synnerhet i början av 1990-talet, när koden utvecklades, släpade de flesta flyttalsberäkningar efter heltalsoperationer i prestanda.
Quake III Arena använder en snabb invers kvadratrotsalgoritm för att påskynda grafikbearbetningen av processorn, men algoritmen har sedan dess implementerats i vissa specialiserade hårdvaruvertexshaders med hjälp av dedikerade programmerbara arrayer (FPGA).
Även på 2010-talsdatorer, beroende på belastningen av den fraktionerade samprocessorn , kan hastigheten vara tre till fyra gånger högre än att använda standardfunktioner [11] .