Vraag lang dubbel (GCC-specifiek) en __float128


Ik ben op zoek naar gedetailleerde informatie over long double en __float128 in GCC / x86 (meer uit nieuwsgierigheid dan vanwege een daadwerkelijk probleem).

Weinig mensen zullen deze waarschijnlijk ooit nodig hebben (ik heb net, voor de allereerste keer ooit, werkelijk nodig a double), maar ik denk dat het nog steeds de moeite waard is (en interessant) om te weten wat je in je gereedschapskist hebt en waar het over gaat.

In dat licht, excuseer mijn enigszins open vragen:

  1. Kan iemand de implementatieresten en het beoogde gebruik van deze typen uitleggen, ook in vergelijking met elkaar? Zijn dit bijvoorbeeld 'beschamende implementaties' omdat de standaard het type toestaat en iemand zou kunnen klagen als ze maar net dezelfde precisie hebben als doubleof zijn ze bedoeld als eersteklas types?
  2. Of heeft iemand een goede, bruikbare webreferentie om te delen? Een Google-zoekopdracht op "long double" site:gcc.gnu.org/onlinedocs gaf me niet veel dat echt nuttig is.
  3. Ervan uitgaande dat de gemeenschappelijke mantra "als je gelooft dat je dubbel moet zijn, begrijp je waarschijnlijk geen zwevende punt" is niet van toepassing, d.i. u werkelijk behoefte aan meer precisie dan alleen floaten het maakt niet uit of 8 of 16 bytes geheugen worden verbrand ... is het redelijk om te verwachten dat je net zo goed gewoon naar long double of __float128 in plaats van double zonder een significant effect op de prestaties?
  4. De "extended precision" -functie van Intel CPU's heeft van oudsher voor onaangename verrassingen gezorgd toen waarden werden verplaatst tussen geheugen en registers. Als er daadwerkelijk 96 bits zijn opgeslagen, is de long double type moet dit probleem oplossen. Aan de andere kant, ik begrijp dat het long double type is wederzijds exclusief met -mfpmath=sse, want er bestaat niet zoiets als "uitgebreide precisie" in SSE. __float128, aan de andere kant, zou perfect moeten werken met SSE-wiskunde (hoewel in afwezigheid van quad-precisie-instructies zeker niet op een 1: 1-instructiebasis). Heb ik gelijk in deze aannames?

(3. en 4. kan waarschijnlijk worden vastgesteld met wat werk besteed aan profilering en demontage, maar misschien had iemand anders al eerder dezelfde gedachte en heeft dat werk al gedaan.)

Achtergrond (dit is het TL; DR-gedeelte):
Ik struikelde aanvankelijk long double omdat ik omhoog keek DBL_MAX in <float.h>en incidenteel LDBL_MAX staat op de volgende regel. "Oh kijk, GCC heeft eigenlijk 128 bit doubles, niet dat ik ze nodig heb, maar ... cool" was mijn eerste gedachte. Verrassing, verrassing: sizeof(long double) komt terug 12 ... wacht, bedoel je 16?

De C- en C ++ -standaarden geven niet verrassend een zeer concrete definitie van het type. C99 (6.2.5 10) zegt dat het aantal double zijn een subset van long double terwijl C ++ 03 verklaart (3.9.1 8) dat long double heeft minstens net zoveel precisie als double (dat is hetzelfde, alleen anders geformuleerd). Kort gezegd laten de normen alles over aan de implementatie, op dezelfde manier als met long, int, en short.

Wikipedia zegt dat GCC gebruikt "80-bit extended precision op x86-processors ongeacht de gebruikte fysieke opslag".

In de GCC-documentatie staat, allemaal op dezelfde pagina, dat de grootte van het type 96 bits is vanwege de i386 ABI, maar niet meer dan 80 bits precisie worden ingeschakeld door een optie (hè? Wat?), Ook Pentium en nieuwer processors willen dat ze worden uitgelijnd als 128-bits nummers. Dit is de standaard onder 64 bits en kan handmatig worden ingeschakeld onder 32 bits, wat resulteert in 32 bits nul opvulling.

Tijd om een ​​test uit te voeren:

#include <stdio.h>
#include <cfloat>

int main()
{
#ifdef  USE_FLOAT128
    typedef __float128  long_double_t;
#else
    typedef long double long_double_t;
#endif

long_double_t ld;

int* i = (int*) &ld;
i[0] = i[1] = i[2] = i[3] = 0xdeadbeef;

for(ld = 0.0000000000000001; ld < LDBL_MAX; ld *= 1.0000001)
    printf("%08x-%08x-%08x-%08x\r", i[0], i[1], i[2], i[3]);

return 0;
}

De uitvoer, bij gebruik long double, ziet er ongeveer zo uit, waarbij de gemarkeerde cijfers constant zijn en alle anderen uiteindelijk veranderen naarmate de cijfers groter en groter worden:

5636666b-c03ef3e0-00223fd8-deadbeef
                  ^^       ^^^^^^^^

Dit suggereert dat het is niet een 80-bits nummer. Een 80-bits nummer heeft 18 hexadecimale cijfers. Ik zie 22 hexadecimale cijfers veranderen, die veel meer op een 96-bits getal (24 hexadecimalen) lijken. Het is ook geen 128-bits nummer sindsdien 0xdeadbeef is niet aangeraakt, wat consistent is met sizeof terugkomst 12.

De uitvoer voor __int128 ziet eruit als een 128-bits nummer. Alle bits draaien uiteindelijk.

Compileren met -m128bit-long-double doet niet richten long double tot 128 bits met een 32-bit nulpolstering, zoals aangegeven door de documentatie. Het gebruikt niet __int128 ofwel, maar lijkt inderdaad uit te lijnen met 128 bits, opvullend met de waarde 0x7ffdd000(?!).

Verder, LDBL_MAX, lijkt te werken als +inf voor beide long double en __float128. Een getal toevoegen of aftrekken zoals 1.0E100 of 1.0E2000 naar Van LDBL_MAX resulteert in hetzelfde bitpatroon.
Tot nu toe was het mijn geloof dat de foo_MAX constanten moesten het grootste representeerbare getal bevatten dat dat is niet  +inf (blijkbaar is dat niet het geval?). Ik ben ook niet helemaal zeker hoe een 80-bits nummer zich zou kunnen gedragen +inf voor een waarde van 128 bits ... misschien ben ik aan het eind van de dag gewoon te moe en heb ik iets verkeerd gedaan.


24
2017-11-22 16:07


oorsprong


antwoorden:


Advertentie 1.

Die typen zijn ontworpen om te werken met getallen met een enorm dynamisch bereik. De lange dubbele is geïmplementeerd op een native manier in de x87 FPU. De 128b dubbele I-verdachte zou worden geïmplementeerd in de softwaremodus op moderne x86s, omdat er geen hardware is om de berekeningen in hardware uit te voeren.

Het grappige is dat het heel gewoon is om veel drijvende-kommabewerkingen op een rij te doen en de tussenresultaten worden niet daadwerkelijk opgeslagen in gedeclareerde variabelen, maar eerder opgeslagen in FPU-registers, waarbij ze profiteren van volledige precisie. Dat is waarom vergelijking:

double x = sin(0); if (x == sin(0)) printf("Equal!");

Is niet veilig en kan niet worden gegarandeerd werken (zonder extra schakelaars).

Advertentie. 3.

Er is een impact op de snelheid, afhankelijk van de precisie die u gebruikt. U kunt de precisie van de FPU wijzigen met behulp van:

void 
set_fpu (unsigned int mode)
{
  asm ("fldcw %0" : : "m" (*&mode));
}

Het zal sneller zijn voor kortere variabelen, langer langzamer. 128-bit dubbel zal waarschijnlijk in de software worden gedaan, dus zal het veel langzamer zijn.

Het gaat niet alleen om RAM-geheugen verspild, het gaat over het verspillen van cache. Als je 80 bit dubbel of 64b dubbel gebruikt, verlies je van 33% (32b) tot bijna 50% (64b) van het geheugen (inclusief cache).

Ad 4.

Aan de andere kant begrijp ik dat het lange dubbele type wederzijds is   exclusief met -mfpmath = sse, want er bestaat niet zoiets als "uitgebreid   precisie "in SSE. __float128, aan de andere kant, zou gewoon moeten werken   prima met SSE-wiskunde (hoewel in quad precisie ontbreekt   instructies zeker niet op een 1: 1-instructiebasis). Zit ik daar net onder   deze aannames?

De FPU- en SSE-eenheden zijn volledig gescheiden. U kunt code met behulp van FPU op hetzelfde moment als SSE schrijven. De vraag is wat de compiler zal genereren als u deze beperkt om alleen SSE te gebruiken? Zal het hoe dan ook proberen om FPU te gebruiken? Ik heb wat geprogrammeerd met SSE en GCC genereert alleen één SISD op zichzelf. Je moet het helpen om SIMD-versies te gebruiken. __float128 zal waarschijnlijk op elke machine werken, zelfs de 8-bit AVR uC. Het is toch gewoon een beetje aan het spelen met stukjes.

De 80 bit in hex-weergave is eigenlijk 20 hex-cijfers. Misschien zijn de stukjes die niet worden gebruikt afkomstig van een of andere oude operatie? Op mijn computer heb ik je code gecompileerd en slechts 20 bits lang gewijzigd modus: 66b4e0d2-ec09c1d5-00007ffe-deadbeef

De 128-bits versie heeft alle bits veranderd. Kijken naar de objdump het lijkt erop dat het software-emulatie gebruikte, er zijn bijna geen FPU-instructies.

Verder lijkt LDBL_MAX te werken als + inf voor zowel long double als   __float128. Het toevoegen of aftrekken van een nummer zoals 1.0E100 of 1.0E2000 van / naar LDBL_MAX resulteert in hetzelfde bitpatroon. Tot nu toe was het mijn   het geloof dat de foo_MAX-constanten de grootste zouden moeten bevatten   representeerbaar getal dat niet + inf is (blijkbaar is dat niet het   geval?).

Dit lijkt vreemd te zijn ...

Ik ben er ook niet helemaal zeker van hoe een 80-bits nummer denkbaar zou zijn   fungeren als + inf voor een 128-bits waarde ... misschien ben ik gewoon te moe aan het eind   van de dag en hebben iets verkeerd gedaan.

Het wordt waarschijnlijk verlengd. Het patroon dat wordt herkend als + inf in 80-bits wordt ook vertaald naar + inf in 128-bits float.


19
2018-05-03 14:13



IEEE-754 definieerde 32 en 64 drijvende-kommaweergaven voor het doel van efficiënte gegevensopslag en een 80-bits representatie ten behoeve van efficiënte berekening. De bedoeling was dat gegeven float f1,f2; double d1,d2; een verklaring zoals d1=f1+f2+d2; zou worden uitgevoerd door de argumenten om te zetten in 80-bits drijvende-kommawaarden, ze toe te voegen en het resultaat terug te converteren naar een 64-bits drijvende-kommatype. Dit zou drie voordelen bieden in vergelijking met het rechtstreeks uitvoeren van bewerkingen op andere drijvende-kommatypes:

  1. Hoewel afzonderlijke code of circuits nodig zouden zijn voor conversies van / naar 32-bits typen en 64-bits typen, zou het slechts nodig zijn om slechts één "toevoeg" -implementatie, één "vermenigvuldigen" -implementatie, één "vierkantswortel" -implementatie, enz.

  2. Hoewel in zeldzame gevallen het gebruik van een 80-bits computationeel type resultaten kon opleveren die heel iets minder nauwkeurig waren dan rechtstreeks andere typen gebruiken (worst-case rounding-fout is 513 / 1024ulp in gevallen waarin berekeningen op andere typen een fout van 511 / 1024ulp zouden opleveren ), geketende berekeningen met 80-bits typen zouden vaak nauwkeuriger zijn - soms veel nauwkeuriger - dan berekeningen met andere typen.

  3. Op een systeem zonder een FPU, scheiden van een double in een afzonderlijke exponent en mantisse voor het uitvoeren van berekeningen, het normaliseren van een mantisse en het converteren van een afzonderlijke mantisse en exponent in een double, zijn enigszins tijdrovend. Als het resultaat van de ene berekening wordt gebruikt als invoer voor een andere en wordt weggegooid, kunt u met behulp van een onverpakt 80-bits type deze stappen weglaten.

Om deze benadering voor drijvende-kommawiskunde nuttig te laten zijn, is het echter noodzakelijk dat code tussenliggende resultaten met dezelfde precisie kan opslaan als bij de berekening, zodat temp = d1+d2; d4=temp+d3; zal hetzelfde resultaat opleveren als d4=d1+d2+d3;. Van wat ik kan vertellen, het doel van long double was om worden dat type. Helaas, hoewel K & R C zo heeft ontworpen dat alle drijvende-kommawaarden op dezelfde manier aan variadische methoden werden doorgegeven, brak ANSI C dat. In C zoals oorspronkelijk ontworpen, gezien de code float v1,v2; ... printf("%12.6f", v1+v2);, de printf methode hoeft zich geen zorgen te maken over de vraag of v1+v2 zou opbrengen float of a double, omdat het resultaat ongeacht een bepaald type wordt afgedwongen. Verder, zelfs als het type v1 of v2 veranderd in double, de printf verklaring zou niet moeten veranderen.

ANSI C vereist echter die code die oproept printf moet weten welke argumenten dat zijn double en welke zijn long double; veel code - zo niet een meerderheid - van de gebruikte code long double maar werd geschreven op platforms waar het synoniem voor is double faalt om de juiste formaatspecificaties te gebruiken voor long double waarden. In plaats van het hebben long double een 80-bits type zijn, behalve wanneer dit wordt doorgegeven als een variadisch methodeargument, in welk geval het zou worden afgedwongen tot 64 bits, veel compilers besloten om long double synoniem zijn met double en geen middelen bieden om de resultaten van tussentijdse berekeningen op te slaan. Omdat het gebruik van een uitgebreid precisietype voor berekening alleen goed is als dat type beschikbaar wordt gemaakt voor de programmeur, kwamen veel mensen tot de slotsom dat uitgebreide precisie kwaadaardig was, hoewel het alleen ANSI C's nalaten om verstandig om te gaan met variadische argumenten die het problematisch maakten.

PS - Het beoogde doel van long double zou hebben geprofiteerd als er ook een was geweest long float die werd gedefinieerd als het type waarnaar float argumenten kunnen het meest efficiënt worden bevorderd; op veel machines zonder drijvende-komma-eenheden die waarschijnlijk een 48-bits type zouden zijn, maar de optimale grootte zou overal kunnen variëren van 32 bits (op machines met een FPU die rechtstreeks 32-bits wiskunde uitvoert) tot 80 (op machines die gebruikmaken van het ontwerp voorzien door IEEE-754). Maar nu te laat.


1
2018-06-05 19:23



Het komt neer op het verschil tussen 4.9999999999999999999 en 5.0.

  1. Hoewel het bereik het grootste verschil is, is precisie het belangrijkste.
  2. Dit type gegevens is nodig in grote cirkelberekeningen of coördineert de wiskunde die waarschijnlijk met GPS-systemen wordt gebruikt.
  3. Omdat de precisie veel beter is dan bij normaal dubbel, betekent dit dat u doorgaans 18 significante cijfers kunt vasthouden zonder dat u de nauwkeurigheid in berekeningen verliest.
  4. Uitgebreide precisie Ik geloof dat 80 bits worden gebruikt (meestal in wiskundeprocessors), dus 128 bits zullen veel nauwkeuriger zijn.

-1
2017-11-15 18:24



C99 en C ++ 11 toegevoegde typen float_t en double_t die aliassen zijn voor ingebouwde typen met drijvende komma. Ongeveer, float_t is het type resultaat van het doen van rekenkunde tussen waarden van het type float, en double_t is het type resultaat van het doen van rekenkunde tussen waarden van het type double.


-1
2018-05-31 22:22