Habré ya tenía varios artículos (
uno ,
dos ,
dos y medio ) dedicados al nuevo formato de punto flotante Posit, cuyos autores lo presentan como superior al flotador estándar IEEE 754 en todos los aspectos. El nuevo formato también encontró críticos (
uno ,
dos ) que afirman que los defectos de Posit superan sus méritos. Pero, ¿qué pasa si realmente tenemos un nuevo formato revolucionario, y la crítica es simplemente causada por la envidia y la incompetencia de quienes critican? Bueno, la mejor manera de averiguarlo es tomar y calcular usted mismo.
Introduccion
Las ventajas del nuevo formato se demuestran mediante ejemplos simples con la suma / multiplicación de números de un orden similar, lo que resulta en una precisión de uno o dos dígitos más. Sin embargo, en cálculos reales, más / menos un bit del error de una sola operación no importa, ya que de todos modos lo calculamos con precisión limitada. La
acumulación de errores como resultado de la secuencia de operaciones es importante, cuando después de un tiempo los errores en los dígitos inferiores comienzan a conducir al error en los más antiguos. Esto es lo que intentaremos experimentar.
Preparación
Para probar, tomé la implementación Posit con un título pathos
desde aquí . Para compilarlo en Visual Studio, tuvimos que agregar la línea #define CLZ (n) __lzcnt (n) en el archivo util.h y reemplazar 0.f / 0.f en el archivo posit.cpp con std :: numeric_limits <float> :: quiet_NaN () Por cierto, las implementaciones para funciones matemáticas elementales (bueno, excepto la raíz) tampoco se encontraron en esta biblioteca; esta es otra razón para sospechar que algo estaba mal.
Prueba 1. Multiplicación de números complejos
La transformación de Fourier, calculada usando aritmética compleja, se usa siempre que sea posible. Al principio, quería probar Posit en la transformación de Fourier; pero dado que su precisión depende bastante de la implementación, para realizar las pruebas correctas tendrá que considerar todos los algoritmos básicos, lo que requiere bastante tiempo; por lo tanto, puede comenzar con una operación más simple: la multiplicación de números complejos.
Si tomamos un determinado vector y lo rotamos 360 veces 1 °, entonces al final deberíamos obtener el mismo vector original. De hecho, el resultado será ligeramente diferente debido a la acumulación de errores, y cuanto mayor sea el número de vueltas, mayor será el error. Entonces, usando este código simple
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;
rotamos un vector con diferentes tipos de datos, y consideraremos el error como la desviación cuadrática media del vector resultante del original (que también puede interpretarse como la longitud del vector de diferencia).
Primero, tome el vector unitario como el más favorable de Posit:
Todavía no hay un líder obvio aquí: la ventaja es dos veces mayor que la de uno u otro. Aumente la longitud del vector girado a 1000:
Los valores de error son casi iguales. Adelante - 1,000,000:
Aquí Posit ya se está quedando atrás con confianza, y el doble error comienza a arrastrarse en el flotador. Tomemos una longitud aún mayor: 10
10 para apreciar plenamente las ventajas de los formatos de punto flotante:
Aquí lo más interesante al principio, en 4 iteraciones, cuando el flotador da un error proporcional al doble, y Posit ya tiene un resultado completamente incorrecto.
Prueba 2. Cálculo de un polinomio racional
Como no había funciones matemáticas en la biblioteca original, intentaremos hacer algo por nuestra cuenta. Muchas funciones se aproximan poco al expandirse en una serie de Taylor, y son más convenientes de calcular mediante la aproximación mediante un polinomio racional. Esta aproximación se puede obtener de varias maneras, incluso de manera puramente analítica, a través de la
aproximación de Padé . Lo llevaremos a prueba, además, con coeficientes suficientemente grandes para que también se redondeen antes del cálculo.
Usando Wolfram Mathematica y el comando PadeApproximant [Sin [x], {x, 0, {11, 11}}]
obtenemos un polinomio tan racional para aproximar el seno, que proporciona una precisión doble en el rango de aproximadamente -2 a 2:
\ frac {- \ frac {481959816488503 x ^ {11}} {363275871831577908403200} + \ frac {23704595077729 x ^ 9} {42339845201815607040} - \ frac {2933434889971 x ^ 7} {33603051747472704} ^47705 } {617703157122660} - \ frac {109061004303 x ^ 3} {722459832892} + x} {\ frac {37291724011 x ^ {10}} {11008359752472057830400} + \ frac {3924840709 x ^ 8} {2016183104848362240} x ^ 6} {168015258737363520} + \ frac {1679739379 x ^ 4} {13726736824948} + \ frac {34046903537 x ^ 2} {2167379498676} +1}
El esquema de Horner generalmente se usa directamente para los cálculos con el fin de ahorrar en los cálculos. En nuestro caso (usando HornerForm) se verá así
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))))); }
A ver:
Como puede ver, la situación con Posit aquí parece deplorable: apenas se marcan dos números significativos.
Conclusión
Desafortunadamente, no ocurrió un milagro y la revolución se canceló. La ventaja de Posit demostrada en cálculos individuales no es más que un truco, cuyo precio es una disminución catastrófica de la precisión en cálculos reales "pesados". La única razón por la que tiene sentido usar Posit en lugar de IEEE 754 float o punto fijo es religiosa. ¡Usar el formato mágico, cuya precisión está garantizada por la santa fe de sus creadores, puede traer muchas maravillas a sus programas!
Código fuente de PS
para verificación y crítica .
PPS
Continuado .