Já havia vários artigos sobre Habré (
um ,
dois ,
dois e meio ) dedicados ao novo formato de ponto flutuante Posit, cujos autores o apresentam como superior ao flutuador padrão IEEE 754 em todos os aspectos. O novo formato também encontrou críticos (
um ,
dois ) que afirmam que as falhas de Posit superam seus méritos. Mas e se realmente tivermos um novo formato revolucionário, e as críticas forem simplesmente causadas pela inveja e incompetência daqueles que criticam? Bem, a melhor maneira de descobrir é tomar e calcular a si mesmo.
1. Introdução
As vantagens do novo formato são demonstradas por exemplos simples, com adição / multiplicação de números de uma ordem semelhante, que resultam em uma precisão de um ou dois dígitos a mais. No entanto, em cálculos reais, mais / menos um bit do erro de uma única operação não importa, pois, de qualquer maneira, o calculamos com precisão limitada. O
acúmulo de erro como resultado da sequência de operações é importante, quando após algum tempo os erros nos dígitos inferiores começam a levar ao erro nos antigos. É isso que tentaremos experimentar.
Preparação
Para testar, peguei a implementação do Posit com um título pathos
daqui . Para compilá-lo no Visual Studio, tivemos que adicionar a linha #define CLZ (n) __lzcnt (n) no arquivo util.h e substituir 0.f / 0.f no arquivo posit.cpp por std :: numeric_limits <float> :: quiet_NaN () A propósito, implementações para funções matemáticas elementares (bem, exceto para a raiz) também não foram encontradas nesta biblioteca - esse é outro motivo para suspeitar que algo estava errado.
Teste 1. Multiplicação de números complexos
A transformada de Fourier, calculada usando aritmética complexa, é usada sempre que possível. No começo, eu queria testar o Posit na transformação de Fourier; mas como a precisão é bastante dependente da implementação, para o teste correto, você precisará considerar todos os algoritmos básicos, o que consome algum tempo; portanto, você pode começar com uma operação mais simples - multiplicação de números complexos.
Se pegarmos um determinado vetor e girá-lo 360 vezes por 1 °, no final, devemos obter o mesmo vetor original. De fato, o resultado será um pouco diferente devido ao acúmulo de erros - e quanto maior o número de voltas, maior o erro. Então, usando esse código simples
complex<T> rot(cos(a), sin(a)); complex<T> vec(length, 0); for (long i = 0; i < count; i++) { vec *= rot; } cout << "error: " << stdev(vec.real() - length, vec.imag()) << endl;
rotacionamos um vetor com diferentes tipos de dados e consideraremos o erro como o desvio quadrado médio do vetor resultante do original (que também pode ser interpretado como o comprimento do vetor de diferença).
Primeiro, considere o vetor unitário o mais favorável ao Posit:
Ainda não há um líder óbvio aqui - a vantagem é duas vezes maior que uma ou a outra. Aumente o comprimento do vetor girado para 1000:
Os valores de erro são quase iguais. Vá em frente - 1.000.000:
Aqui, o Posit já está confiantemente atrasado, e o duplo erro começa a aparecer no carro alegórico. Vamos demorar ainda mais - 10
10 para apreciar plenamente as vantagens dos formatos de ponto flutuante:
Aqui, a coisa mais interessante no início, em 4 iterações - quando o float dá um erro proporcional ao dobro, e o Posit já tem um resultado completamente incorreto.
Teste 2. Cálculo de um polinômio racional
Como não havia funções matemáticas na biblioteca original, tentaremos fazer algo por conta própria. Muitas funções são pouco aproximadas pela expansão em uma série de Taylor e são mais convenientes para calcular através da aproximação por um polinômio racional. Essa aproximação pode ser obtida de várias maneiras, inclusive puramente analiticamente - através da
aproximação de Padé . Além disso, vamos testá-lo com coeficientes suficientemente grandes para que eles também sejam arredondados antes do cálculo.
Usando Wolfram Mathematica e o comando PadeApproximant [Sin [x], {x, 0, {11, 11}}]
obtemos um polinômio racional para aproximar o seno, o que fornece dupla precisão no intervalo de cerca de -2 a 2:
\ frac {- \ frac {481959816488503 x ^ {11}} {363275871831577908403200} + \ frac {23704595077729 x ^ 9} {42339845201815607040} - \ frac {2933434889971 x ^ 7} {33603051747472704} } {617703157122660} - \ frac {109061004303 x ^ 3} {722459832892} + x} {\ frac {37291724011 x ^ {10}} {11008359752472057830400} + \ frac {3924840709 x ^ 8} {20161831048483622 x ^ 6} {168015258737363520} + \ frac {1679739379 x ^ 4} {13726736824948} + \ frac {34046903537 x ^ 2} {2167379498676} +1}
O esquema de Horner é geralmente usado diretamente nos cálculos para economizar nos cálculos. No nosso caso (usando o HornerForm), parecerá
template< typename T > T padesin(T x) { T xx = x*x; return (x*(T(363275871831577908403200.) + xx*(-T(54839355237791393068800.) + xx*(T(2120649063015013090560.) + xx*(-T(31712777908498486800.) + xx*(T(203385425766914820.) - T(481959816488503.) * xx)))))) / (T(363275871831577908403200.) + xx*(T(5706623400804924998400.) + xx*(T(44454031219351353600.) + xx* (T(219578286347980560.) + xx*(T(707177798947620.) + T(1230626892363.) * xx))))); }
Vamos ver:
Como você pode ver, a situação com o Posit aqui parece deplorável - apenas dois números significativos são discados.
Conclusão
Infelizmente, um milagre não aconteceu e a revolução foi cancelada. A vantagem do Posit demonstrada em cálculos únicos não passa de um truque, cujo preço é uma diminuição catastrófica da precisão em cálculos reais "pesados". A única razão pela qual faz sentido usar o Posit em vez do ponto flutuante ou ponto fixo IEEE 754 é religiosa. O uso do formato mágico, cuja precisão é garantida pela santa fé de seus criadores, pode trazer muitas maravilhas para seus programas!
Código fonte do PS
para verificação e crítica .
PPS
Continuação .