Desafíos de posición adulta

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:
iteraciones4 4100100010,000100,000
doble0 00 00 00 00 0
flotar0 00.000000360.000010380.00018580.0001961
plantear0 00.000000730.000005340,00004110,0004468

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:
iteraciones4 4100100010,000100,000
doble0 00 00 00 00 0
flotar0 00,000280,01030,180,19
plantear0 00.002130,01880,162,45

Los valores de error son casi iguales. Adelante - 1,000,000:
iteraciones4 4100100010,000100,000
doble0 00 00.000000020.000000420.0000036
flotar0 00,3312,0185,8198,1
plantear0 08.1271,0769,210706.8

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:
iteraciones4 4100100010,000100,000
doble0.000002450,000015360,00020410,00409510,03621497
flotar0.000002456003,888111.81 836 254,01965083.0
plantear9216.01287208.714443543,7202630144.41784050328.2

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:
x = 0.5x = 1x = 2
sin (x)0.4794255386042030.84147098480789650,9092974268256817
doble0.4794255386042030.84147098480789650,909297426825681 6
flotar0.4794255 4950714110.84147095680 236820,9092974 066734314
plantear0,47 889610379934310.84 244372695684430.9 110429435968399

x = 3x = 4x = 5
sin (x)0.1411200080598672-0.7568024953079282-0.9589242746631385
doble0.14112000805 85958-0.75680249 60833886-0.958924 3758030122
flotar0.1411200 165748596-0.7568024 396896362-0.9589243 531227112
plantear0.14 44759201258421-0,7 614213190972805-0.9 691629931330681

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 .

Source: https://habr.com/ru/post/467735/


All Articles