Dupla precisão no formato de ponto flutuante

Origem: Wikipédia, a enciclopédia livre.

Formato de ponto flutuante de precisão dupla (às vezes chamado de FP64 ou float64) é um formato de número de ponto flutuante, geralmente ocupando 64 bits na memória do computador; ele representa uma ampla faixa dinâmica de valores numéricos usando um ponto de base.

A precisão dupla pode ser escolhida quando o intervalo ou a precisão da precisão simples for insuficiente.

No padrão IEEE 754-2008, o formato base 2 de 64 bits é oficialmente referido como binary64; foi chamado de duplo em IEEE 754-1985. A IEEE 754 especifica formatos adicionais de ponto flutuante, incluindo 32 bits de base 2 precisão única e, mais recentemente, representações de base 10 (ponto flutuante decimal).

Uma das primeiras linguagens de programação a fornecer tipos de dados de ponto flutuante foi Fortran.[carece de fontes?] Antes da ampla adoção do IEEE 754-1985, a representação e as propriedades dos tipos de dados de ponto flutuante dependiam do fabricante do computador e do modelo do computador, e das decisões tomadas pelos implementadores da linguagem de programação. Por exemplo, o tipo de dados de precisão dupla do GW-BASIC era o formato de ponto flutuante MBF de 64 bits.

Formato de ponto flutuante binário de precisão dupla IEEE 754: binary64[editar | editar código-fonte]

O ponto flutuante binário de precisão dupla é um formato comumente usado em PCs, devido ao seu alcance mais amplo em relação ao ponto flutuante de precisão simples, apesar de seu desempenho e custo de largura de banda. É comumente conhecido simplesmente como duplo. O padrão IEEE 754 especifica um binary64 como tendo:

O bit de sinal determina o sinal do número (inclusive quando esse número é zero, que é assinado).

O campo expoente é um número inteiro sem sinal de 11 bits de 0 a 2047, em forma tendenciosa: um valor de expoente de 1023 representa o valor real zero. Os expoentes variam de −1022 a +1023 porque os expoentes de −1023 (todos 0) e +1024 (todos 1) são reservados para números especiais.

A precisão do significando de 53 bits fornece precisão de 15 a 17 dígitos decimais significativos (2−53 ≈ 1.11 × 10−16). Se uma sequência decimal com no máximo 15 dígitos significativos for convertida para o formato de precisão dupla IEEE 754, fornecendo um número normal, e depois convertida novamente para uma sequência decimal com o mesmo número de dígitos, o resultado final deverá corresponder à sequência original. Se um número de precisão dupla IEEE 754 for convertido em uma string decimal com pelo menos 17 dígitos significativos e depois convertido novamente para representação de precisão dupla, o resultado final deverá corresponder ao número original.[1]

O formato é escrito com o significando tendo um bit inteiro implícito de valor 1 (exceto para dados especiais, veja a codificação do expoente abaixo). Com os 52 bits do significando da fração (F) aparecendo no formato da memória, a precisão total é, portanto, de 53 bits (aproximadamente 16 dígitos decimais, 53 log10(2) ≈ 15,955). Os bits são dispostos da seguinte forma:

O valor real assumido por um determinado dado de precisão dupla de 64 bits com um determinado expoente tendencioso e uma fração de 52 bits é

ou

Entre 252=4.503.599.627.370.496 e 253=9.007.199.254.740.992 os números representáveis são exatamente os inteiros. Para o próximo intervalo, de 253 a 254, tudo é multiplicado por 2, então os números representáveis são os pares, etc. 251 a 252, o espaçamento é 0,5, etc.

O espaçamento como fração dos números no intervalo de 2n a 2n+1 é 2n' '−52. O erro máximo de arredondamento relativo ao arredondar um número para o número representável mais próximo (o Épsilon de máquina) é, portanto, 2−53.

A largura de 11 bits do expoente permite a representação de números entre 10−308 e 10308, com precisão total de 15 a 17 dígitos decimais. Ao comprometer a precisão, a representação subnormal permite valores ainda menores, até cerca de 5×10−324.

Codificação de expoente[editar | editar código-fonte]

O expoente binário de ponto flutuante de precisão dupla é codificado usando uma representação binário de deslocamento, com o deslocamento de zero sendo 1023; também conhecido como polarização de expoente no padrão IEEE 754. Exemplos de tais representações seriam:

e =000000000012=00116=1: (menor expoente para números normais)
e =011111111112=3ff16=1023: (deslocamento zero)
e =100000001012=40516=1029:
e =111111111102=7fe16=2046: (maior expoente)

Os expoentes 00016 e 7ff16 têm um significado especial:

onde F é a parte fracionária do significativo. Todos os padrões de bits são codificações válidas.

Exceto pelas exceções acima, todo o número de precisão dupla é descrito por:

No caso de números subnormais (e = 0) o número de precisão dupla é descrito por:

Endiannes[editar | editar código-fonte]

Embora muitos processadores usem armazenamento little-endian para todos os tipos de dados (inteiro, ponto flutuante), há diversas arquiteturas de hardware onde ponto flutuante números são representados na forma big-endian, enquanto os números inteiros são representados na forma little-endian.[2] Existem processadores ARM que possuem representação de ponto flutuante mista-endian para números de precisão dupla: cada uma das duas palavras de 32 bits é armazenada como little-endian, mas a palavra mais significativa é armazenada primeiro. VAX o ponto flutuante armazena palavras little-endian de 16 bits na ordem big-endian. Como existem muitos formatos de ponto flutuante sem representação padrão de rede para eles, o padrão XDR usa o big-endian IEEE 754 como sua representação. Portanto, pode parecer estranho que o padrão de ponto flutuante IEEE 754 difundido não especifique endianness.[3] Teoricamente, isso significa que mesmo os dados de ponto flutuante padrão IEEE escritos por uma máquina podem não ser legíveis por outra. No entanto, em computadores padrão modernos (ou seja, implementando IEEE 754), pode-se assumir com segurança que o endianness é o mesmo para números de ponto flutuante e para números inteiros, tornando a conversão simples, independentemente do tipo de dados. Entretanto, pequenos sistemas embarcados usando formatos especiais de ponto flutuante podem ser outro assunto.

Exemplos de precisão dupla[editar | editar código-fonte]

0 01111111111 00000000000000000000000000000000000000000000000000002 ≙ 3FF0 0000 0000 000016 ≙ +20 × 1 = 1
0 01111111111 00000000000000000000000000000000000000000000000000012 ≙ 3FF0 0000 0000 000116 ≙ +20 × (1 + 2−52) ≈ 1.0000000000000002, the smallest number > 1
0 01111111111 00000000000000000000000000000000000000000000000000102 ≙ 3FF0 0000 0000 000216 ≙ +20 × (1 + 2−51) ≈ 1.0000000000000004
0 10000000000 00000000000000000000000000000000000000000000000000002 ≙ 4000 0000 0000 000016 ≙ +21 × 1 = 2
1 10000000000 00000000000000000000000000000000000000000000000000002 ≙ C000 0000 0000 000016 ≙ −21 × 1 = −2
0 10000000000 10000000000000000000000000000000000000000000000000002 ≙ 4008 0000 0000 000016 ≙ +21 × 1.12 = 112 = 3
0 10000000001 00000000000000000000000000000000000000000000000000002 ≙ 4010 0000 0000 000016 ≙ +22 × 1 = 1002 = 4
0 10000000001 01000000000000000000000000000000000000000000000000002 ≙ 4014 0000 0000 000016 ≙ +22 × 1.012 = 1012 = 5
0 10000000001 10000000000000000000000000000000000000000000000000002 ≙ 4018 0000 0000 000016 ≙ +22 × 1.12 = 1102 = 6
0 10000000011 01110000000000000000000000000000000000000000000000002 ≙ 4037 0000 0000 000016 ≙ +24 × 1.01112 = 101112 = 23
0 01111111000 10000000000000000000000000000000000000000000000000002 ≙ 3F88 0000 0000 000016 ≙ +2−7 × 1.12 = 0.000000112 = 0.01171875 (3/256)
0 00000000000 00000000000000000000000000000000000000000000000000012 ≙ 0000 0000 0000 000116 ≙ +2−1022 × 2−52 = 2−1074 ≈ 4.9406564584124654 × 10−324 (mín. subnormal positivo duplo)
0 00000000000 11111111111111111111111111111111111111111111111111112 ≙ 000F FFFF FFFF FFFF16 ≙ +2−1022 × (1 − 2−52) ≈ 2.2250738585072009 × 10−308 (Máx. duplo subnormal)
0 00000000001 00000000000000000000000000000000000000000000000000002 ≙ 0010 0000 0000 000016 ≙ +2−1022 × 1 ≈ 2.2250738585072014 × 10−308 (Min. normal positivo duplo)
0 11111111110 11111111111111111111111111111111111111111111111111112 ≙ 7FEF FFFF FFFF FFFF16 ≙ +21023 × (1 + (1 − 2−52)) ≈ 1.7976931348623157 × 10308 (Máx. duplo)
0 00000000000 00000000000000000000000000000000000000000000000000002 ≙ 0000 0000 0000 000016 ≙ +0
1 00000000000 00000000000000000000000000000000000000000000000000002 ≙ 8000 0000 0000 000016 ≙ −0
0 11111111111 00000000000000000000000000000000000000000000000000002 ≙ 7FF0 0000 0000 000016 ≙ +∞ (infinito positivo)
1 11111111111 00000000000000000000000000000000000000000000000000002 ≙ FFF0 0000 0000 000016 ≙ −∞ (infinito negativo)
0 11111111111 00000000000000000000000000000000000000000000000000012 ≙ 7FF0 0000 0000 000116 ≙ NaN (sNaN na maioria dos processadores, como x86 e ARM)
0 11111111111 10000000000000000000000000000000000000000000000000012 ≙ 7FF8 0000 0000 000116 ≙ NaN (qNaN na maioria dos processadores, como x86 e ARM)
0 11111111111 11111111111111111111111111111111111111111111111111112 ≙ 7FFF FFFF FFFF FFFF16 ≙ NaN (uma codificação alternativa de NaN)
0 01111111101 01010101010101010101010101010101010101010101010101012 = 3FD5 5555 5555 555516 ≙ +2−2 × (1 + 2−2 + 2−4 + ... + 2−52) ≈ 1/3
0 10000000000 10010010000111111011010101000100010000101101000110002 = 4009 21FB 5444 2D1816 ≈ pi

Codificações de qNaN e sNaN não são completamente especificadas em IEEE 754 e dependem do processador. A maioria dos processadores, como a família x86 e a família ARM, use o bit mais significativo do campo significando para indicar um NaN silencioso; isso é o recomendado pela IEEE 754. Os processadores PA-RISC usam o bit para indicar uma sinalização NaN.

Por padrão, 1/3 arredonda para baixo, em vez de up como precisão simples, devido ao número ímpar de bits no significando.

Em mais detalhes:

Dada a representação hexadecimal 3FD5 5555 5555 555516,
  Sinal = 0
  Expoente = 3FD16 = 1021
  Exponent Bias = 1023 (valor constante; veja acima)
  Fração = 5 5555 5555 555516
  Valor = 2(Expoente − Exponent Bias) × 1.Fração – Observe que a fração não deve ser convertida para decimal aqui
        = 2−2 × (15 5555 5555 555516 × 2−52)
        = 2−54 × 15 5555 5555 555516
        = 0.333333333333333314829616256247390992939472198486328125
        ≈ 1/3

Velocidade de execução com aritmética de dupla precisão[editar | editar código-fonte]

Usar variáveis ​​de ponto flutuante de precisão dupla geralmente é mais lento do que trabalhar com suas contrapartes de precisão simples. Uma área da computação onde esse problema é específico é o código paralelo executado em GPUs. Por exemplo, ao usar a plataforma CUDA da NVIDIA, cálculos com precisão dupla podem levar, dependendo do hardware, de 2 a 32 vezes mais tempo para serem concluídos em comparação com aqueles feitos usando precisão simples.[4]

Além disso, muitas funções matemáticas (por exemplo, sin, cos, atan2, log, exp e sqrt) precisam de mais cálculos para fornecer resultados precisos de precisão dupla e, portanto, são mais lentas.

Limitações de precisão em valores inteiros[editar | editar código-fonte]

  • Inteiros de −253 a 253 (−9.007.199.254.740.992 a 9.007.199.254.740.992) podem ser representados com exatidão.
  • Inteiros entre 253 e 254 = 18.014.398.509.481.984 arredondar para um múltiplo de 2 (número par).
  • Inteiros entre 254 e 255 = 36.028.797.018.963.968 arredondar para um múltiplo de 4.
  • Inteiros entre 2n e 2n+1 arredondar para um múltiplo de 2n−52.

Implementações[editar | editar código-fonte]

Os duplos são implementados em muitas linguagens de programação de diferentes maneiras, como a seguir. Em processadores apenas com precisão dinâmica, como x86 sem SSE2 (ou quando SSE2 não é usado, para fins de compatibilidade) e com precisão estendida usada por padrão, o software pode ter dificuldades para cumprir alguns requisitos.

C e C++[editar | editar código-fonte]

C e C++ oferecem uma ampla variedade de tipos aritméticos. A precisão dupla não é exigida pelos padrões (exceto pelo anexo F opcional de C99, que cobre a aritmética IEEE 754), mas na maioria dos sistemas, o tipo double corresponde à precisão dupla. No entanto, em x86 de 32 bits com precisão estendida por padrão, alguns compiladores podem não estar em conformidade com o padrão C ou a aritmética pode sofrer de double rounding.[5]

Fortran[editar | editar código-fonte]

Fortran fornece vários tipos inteiros e reais, e o tipo de 64 bits real64, acessível através do módulo intrínseco do Fortran iso_fortran_env, corresponde à precisão dupla.

Common Lisp[editar | editar código-fonte]

Common Lisp fornece os tipos SHORT-FLOAT, SINGLE-FLOAT, DOUBLE-FLOAT e LONG-FLOAT. A maioria das implementações fornece SINGLE-FLOATs e DOUBLE-FLOATs com os outros tipos de sinônimos apropriados. Common Lisp fornece exceções para capturar underflows e overflows de ponto flutuante, e a exceção inexata de ponto flutuante, conforme IEEE 754. Nenhum infinito e NaNs são descritos no padrão ANSI, no entanto, várias implementações os fornecem como extensões.

Java[editar | editar código-fonte]

Em Java antes da versão 1.2, toda implementação precisava ser compatível com IEEE 754. A versão 1.2 permitiu que implementações trouxessem precisão extra em cálculos intermediários para plataformas como x87. Assim, um modificador strictfp foi introduzido para impor cálculos rigorosos do IEEE 754. O ponto flutuante estrito foi restaurado no Java 17.[6]

JavaScript[editar | editar código-fonte]

Conforme especificado pelo padrão ECMAScript, toda aritmética em JavaScript deve ser feito usando aritmética de ponto flutuante de precisão dupla.[7]

JSON[editar | editar código-fonte]

O formato de codificação de dados JSON suporta valores numéricos, e a gramática à qual as expressões numéricas devem estar em conformidade não tem limites na precisão ou intervalo dos números assim codificados. No entanto, a RFC 8259 informa que, como os números binário64 do IEEE 754 são amplamente implementados, uma boa interoperabilidade pode ser alcançada por implementações que processam JSON se não esperarem mais precisão ou alcance do que o binary64 oferece.[8]

Referências

  1. William Kahan (1 de outubro de 1997). «Lecture Notes on the Status of IEEE Standard 754 for Binary Floating-Point Arithmetic» (PDF). Arquivado do original (PDF) em 8 de fevereiro de 2012 
  2. Savard, John J. G. (2018) [2005], «Floating-Point Formats», quadibloc, consultado em 16 de dezembro de 2023, cópia arquivada em 3 de julho de 2018 
  3. «pack – convert a list into a binary representation» 
  4. «Nvidia's New Titan V Pushes 110 Teraflops From A Single Chip». Tom's Hardware (em inglês). 8 de dezembro de 2017. Consultado em 16 de dezembro de 2023 
  5. «Bug 323 – optimized code gives strange floating point results». gcc.gnu.org. Consultado em 16 de dezembro de 2023. Cópia arquivada em 30 de abril de 2018 
  6. Darcy, Joseph D. «JEP 306: Restore Always-Strict Floating-Point Semantics». Consultado em 16 de dezembro de 2023 
  7. ECMA-262 ECMAScript Language Specification (PDF) 5th ed. [S.l.]: Ecma International. p. 29, §8.5 The Number Type. Cópia arquivada (PDF) em 13 de março de 2012 
  8. «The JavaScript Object Notation (JSON) Data Interchange Format». Internet Engineering Task Force. Dezembro de 2017. Consultado em 16 de dezembro de 2023 

Ligações externas[editar | editar código-fonte]