En análisis numérico , el algoritmo de suma de Kahan , también conocido como suma compensada , [ 1 ] reduce significativamente el error numérico en el total obtenido al sumar una secuencia de números de coma flotante de precisión finita , en comparación con el método ingenuo. Esto se logra manteniendo una compensación acumulativa independiente (una variable para acumular pequeños errores), extendiendo así la precisión de la suma en la precisión de la variable de compensación.
En particular, simplemente sumandoLos números en secuencia tienen un error en el peor de los casos que crece proporcionalmente ay un error cuadrático medio que crece a medida quepara entradas aleatorias (los errores de redondeo forman un paseo aleatorio ). [ 2 ] Con la suma compensada, utilizando una variable de compensación con una precisión suficientemente alta, el límite de error en el peor de los casos es efectivamente independiente de, por lo que se puede sumar una gran cantidad de valores con un error que solo depende de la precisión de punto flotante del resultado. [ 2 ]
El algoritmo se atribuye a William Kahan ; [ 3 ] Ivo Babuška parece haber ideado un algoritmo similar de forma independiente (de ahí la suma de Kahan - Babuška ). [ 4 ] Técnicas similares anteriores son, por ejemplo, el algoritmo de línea de Bresenham , que registra el error acumulado en operaciones con enteros (aunque se documentó por primera vez aproximadamente al mismo tiempo [ 5 ] ) y la modulación delta-sigma . [ 6 ]
El algoritmo
En pseudocódigo , el algoritmo será:
función KahanSum(entrada) // Preparar el acumulador. var suma = 0.0 // Una compensación continua por la pérdida de bits de orden bajo. var c = 0.0 // El array input tiene elementos indexados desde input[1] hasta input[input.length]. for i = 1 to input.length do // c es cero la primera vez. var y = input[i] - c // Por desgracia, la suma es grande, y es pequeña, por lo que se pierden los dígitos de menor orden de y . var t = suma + y // (t - suma) cancela la parte de orden superior de y ; // restar y recupera el valor negativo (parte baja de y ) c = (t - suma) - y // Algebraicamente, c siempre debe ser cero. Cuidado // ¡Compiladores de optimización excesivamente agresivos! suma = t // La próxima vez, la parte baja perdida se agregará a y en un nuevo intento. siguiente i suma de retorno
Este algoritmo también puede reescribirse para utilizar el algoritmo Fast2Sum : [ 7 ]
función KahanSum2(entrada) // Preparar el acumulador. var suma = 0.0 // Una compensación continua por la pérdida de bits de orden bajo. var c = 0.0 // El array input tiene elementos indexados desde input[1] hasta input[input.length]. for i = 1 to input.length do // c es cero la primera vez. var y = input[i] + c // suma + c es una aproximación a la suma exacta. (suma,c) = Fast2Sum(suma,y) // La próxima vez, la parte baja perdida se agregará a y en un nuevo intento. siguiente i suma de retorno
Ejemplo resuelto
El algoritmo no exige ninguna elección específica de base , solo que la aritmética "normalice las sumas de punto flotante antes de redondear o truncar". [ 3 ] Las computadoras suelen usar aritmética binaria, pero para que el ejemplo sea más fácil de leer, se dará en decimal. Supongamos que estamos usando aritmética de punto flotante decimal de seis dígitos , sumha alcanzado el valor 10000.0, y los dos valores siguientes de input[i]son 3.14159 y 2.71828. El resultado exacto es 10005.85987, que se redondea a 10005.9. Con una suma simple, cada valor entrante se alinearía con sum, y se perderían muchos dígitos de orden bajo (por truncamiento o redondeo). El primer resultado, después del redondeo, sería 10003.1. El segundo resultado sería 10005,81828 antes del redondeo y 10005,8 después del redondeo. Esto no es correcto.
Sin embargo, con la suma compensada, obtenemos el resultado redondeado correcto de 10005,9.
Supongamos que ctiene un valor inicial de cero. Los ceros finales se muestran donde son significativos para el número de coma flotante de seis dígitos.
y = 3.14159 - 0.00000 y = input[i] - c t = 10000.0 + 3.14159 t = suma + y = 10003.14159 Normalización realizada, siguiente redondeo a seis dígitos. = 10003.1 Pocos dígitos de input[i] coincidieron con los de sum . ¡Se han perdido muchos dígitos! c = (10003.1 - 10000.0) - 3.14159 c = (t - suma) - y (Nota: ¡Primero se deben evaluar los paréntesis!) = 3,10000 - 3,14159 La parte asimilada de y menos la y completa original . = -0,0415900 Debido a que c está cerca de cero, la normalización conserva muchos dígitos después del punto flotante. suma = 10003.1 suma = t
La suma es tan grande que solo se acumulan los dígitos de orden superior de los números de entrada. Pero en el siguiente paso, cuna aproximación del error de ejecución contrarresta el problema.
y = 2,71828 - (-0,0415900) La mayoría de los dígitos coinciden, ya que c tiene un tamaño similar al de y . = 2,75987 El déficit (pérdida de dígitos de orden inferior) de la iteración anterior se ha restablecido correctamente. t = 10003.1 + 2.75987 Pero aún así, solo unos pocos cumplen con los dígitos de la suma . = 10005.85987 Normalización realizada, siguiente ronda a seis dígitos. = 10005.9 Nuevamente, se han perdido muchos dígitos, pero c ayudó a corregir el redondeo. c = (10005.9 - 10003.1) - 2.75987 Estime el error acumulado, basado en la y ajustada . = 2,80000 - 2,75987 Como era de esperar, las partes de orden bajo se pueden conservar en c con efectos de redondeo nulos o mínimos. = 0,0401300 En esta iteración, t fue un poco demasiado alto, el exceso se restará en la siguiente iteración. suma = 10005.9 El resultado exacto es 10005.85987, la suma es correcta, redondeada a 6 dígitos.
El algoritmo realiza la suma con dos acumuladores: sumuno almacena la suma y otro cacumula las partes no asimiladas en sum, para ajustar la parte de orden inferior de sumla siguiente vez. Por lo tanto, la suma procede con "dígitos de guarda" en c, lo cual es mejor que no tener ninguno, pero no es tan bueno como realizar los cálculos con el doble de precisión de la entrada. Sin embargo, simplemente aumentar la precisión de los cálculos no es práctico en general; si inputya está en doble precisión, pocos sistemas proporcionan precisión cuádruple , y si lo hicieran, inputpodría entonces estar en precisión cuádruple.
Exactitud
Es necesario un análisis cuidadoso de los errores en la suma compensada para apreciar sus características de precisión. Si bien es más precisa que la suma ingenua, aún puede generar grandes errores relativos para sumas mal condicionadas.
Supongamos que uno está sumandovalores, paraLa suma exacta es
- (calculado con precisión infinita).
Con la suma compensada, se obtiene en cambiodonde el errorestá delimitado por [ 2 ]
dóndees la precisión de la máquina de la aritmética que se emplea (por ejemplo(para el estándar IEEE de punto flotante de doble precisión ). Por lo general, la cantidad de interés es el error relativo., que por lo tanto está delimitado superiormente por
En la expresión para el límite de error relativo, la fracciónes el número de condición del problema de suma. Esencialmente, el número de condición representa la sensibilidad intrínseca del problema de suma a los errores, independientemente de cómo se calcule. [ 8 ] El límite de error relativo de cada método de suma ( estable hacia atrás ) mediante un algoritmo fijo en precisión fija (es decir, no aquellos que utilizan aritmética de precisión arbitraria , ni algoritmos cuyos requisitos de memoria y tiempo cambian en función de los datos), es proporcional a este número de condición. [ 2 ] Un problema de suma mal condicionado es aquel en el que esta razón es grande, y en este caso incluso la suma compensada puede tener un gran error relativo. Por ejemplo, si los sumandosson números aleatorios no correlacionados con media cero, la suma es un paseo aleatorio y el número de condición crecerá proporcionalmente aPor otro lado, para entradas aleatorias con media distinta de cero, el número de condición tiende asintóticamente a una constante finita comoSi todas las entradas son no negativas , entonces el número de condición es 1.
Dado un número de condición, el error relativo de la suma compensada es efectivamente independiente de. En principio, existe elque crece linealmente con, pero en la práctica este término es efectivamente cero: puesto que el resultado final se redondea a una precisión, elEl término se redondea a cero, a menos quees aproximadamenteo mayor. [ 2 ] En doble precisión, esto corresponde a unde aproximadamente, mucho mayores que la mayoría de las sumas. Por lo tanto, para un número de condición fijo, los errores de la suma compensada son efectivamente, independientemente de.
En comparación, el límite de error relativo para la suma ingenua (simplemente sumar los números en secuencia, redondeando en cada paso) crece a medida quemultiplicado por el número de condición. [ 2 ] Este error en el peor de los casos rara vez se observa en la práctica, sin embargo, porque solo ocurre si todos los errores de redondeo están en la misma dirección. En la práctica, es mucho más probable que los errores de redondeo tengan un signo aleatorio, con media cero, de modo que formen un paseo aleatorio; en este caso, la suma ingenua tiene un error relativo cuadrático medio que crece comomultiplicado por el número de condición. [ 9 ] Sin embargo, esto sigue siendo mucho peor que la suma compensada. No obstante, si la suma se puede realizar con el doble de precisión, entonceses reemplazado pory la suma ingenua tiene un error en el peor de los casos comparable altérmino en la suma compensada con la precisión original.
Del mismo modo, elque aparece enarriba es un límite del peor caso que ocurre solo si todos los errores de redondeo tienen el mismo signo (y son de la máxima magnitud posible). [ 2 ] En la práctica, es más probable que los errores tengan un signo aleatorio, en cuyo caso los términos ense reemplazan por un paseo aleatorio, en cuyo caso, incluso para entradas aleatorias con media cero, el errorcrece solo como(ignorando eltérmino), la misma tasa la sumacrece, cancelando elfactores al calcular el error relativo. Por lo tanto, incluso para sumas asintóticamente mal condicionadas, el error relativo para la suma compensada a menudo puede ser mucho menor de lo que podría sugerir un análisis del peor caso.
Mejoras adicionales
Precisión
Neumaier [ 10 ] introdujo una versión mejorada del algoritmo de Kahan, que denomina "algoritmo de Kahan-Babuška mejorado", el cual también abarca el caso en que el siguiente término a sumar es mayor en valor absoluto que la suma acumulada, intercambiando efectivamente el papel de lo que es grande y lo que es pequeño. En pseudocódigo , el algoritmo es:
función KahanBabushkaNeumaierSum(input) var sum = 0.0 var c = 0.0 // Una compensación continua por la pérdida de bits de orden bajo. para i = 1 hasta input.length hacer var t = sum + input[i] si |sum| >= |input[i]| entonces c += (sum - t) + input[i] // Si sum es mayor, se pierden los dígitos de menor orden de input[i] . si no c += (input[i] - t) + sum // Si no , se pierden los dígitos de menor orden de sum . fin si suma = t a continuación yo devolver suma + c // La corrección se aplica solo una vez al final.
Esta mejora es similar a la versión Fast2Sum del algoritmo de Kahan con Fast2Sum reemplazado por 2Sum .
Para muchas secuencias de números, ambos algoritmos coinciden, pero un ejemplo sencillo debido a Peters [ 11 ] muestra cómo pueden diferir: sumandoEn precisión doble, el algoritmo de Kahan produce 0,0, mientras que el algoritmo de Neumaier produce el valor correcto 2,0.
También son posibles modificaciones de orden superior con mayor precisión. Por ejemplo, una variante sugerida por Klein, [ 12 ] que denominó "algoritmo iterativo de Kahan-Babuška" de segundo orden. En pseudocódigo , el algoritmo es:
función KahanBabushkaKleinSum(entrada) var suma = 0,0 var cs = 0,0 var ccs = 0,0 para i = 1 hasta input.length hacer var c, cc var t = sum + input[i] si |sum| >= |input[i]| entonces c = (suma - t) + entrada[i] demás c = (input[i] - t) + suma fin si suma = t t = cs + c si |cs| >= |c| entonces cc = (cs - t) + c demás cc = (c - t) + cs fin si cs = t ccs = ccs + cc bucle finaldevolver suma + (cs + ccs)
Velocidad
En el algoritmo de suma de Kahan, cada iteración del bucle depende del resultado de una iteración anterior ( dependencia de datos por bucle ). Esto reduce la ganancia de rendimiento de los diseños modernos de procesadores superescalares . Para reducir esta penalización, se pueden dividir las variables acumuladoras s y c en varias copias, cada una trabajando con una porción de la entrada. Estas copias se pueden acumular en paralelo utilizando una CPU superescalar, SIMD o incluso multiprocesamiento . Finalmente, las copias separadas se acumulan juntas utilizando el algoritmo escalar (no paralelo) de Kahan. [ 13 ] [ 14 ]
Alternativas
Aunque el algoritmo de Kahan lograEl crecimiento del error al sumar n números es solo ligeramente peor.El crecimiento se puede lograr mediante la suma por pares : se divide recursivamente el conjunto de números en dos mitades, se suma cada mitad y luego se suman las dos sumas. [ 2 ] Esto tiene la ventaja de requerir el mismo número de operaciones aritméticas que la suma ingenua (a diferencia del algoritmo de Kahan, que requiere cuatro veces la aritmética y tiene una latencia cuatro veces mayor que una suma simple) y se puede calcular en paralelo. El caso base de la recursión podría ser, en principio, la suma de solo uno (o cero) números, pero para amortizar la sobrecarga de la recursión, normalmente se usaría un caso base mayor. El equivalente de la suma por pares se usa en muchos algoritmos de transformada rápida de Fourier (FFT) y es responsable del crecimiento logarítmico de los errores de redondeo en esas FFT. [ 15 ] En la práctica, con errores de redondeo de signos aleatorios, los errores cuadráticos medios de la suma por pares crecen como. [ 9 ] La suma por pares es utilizada por NumPy y Julia, entre otros paquetes numéricos notables.
Otra alternativa es utilizar aritmética de precisión arbitraria , que en principio no necesita ningún redondeo, aunque a un coste computacional mucho mayor.
- Una forma de realizar sumas correctamente redondeadas con precisión arbitraria es extender de forma adaptativa utilizando múltiples componentes de punto flotante (método de Shewchuk). Esto minimizará el costo computacional en casos comunes donde no se requiere alta precisión. [ 16 ] Este estilo de suma es utilizado por la función de Python
math.fsum. [ 11 ] - Otro método que utiliza únicamente aritmética de enteros, pero un acumulador grande, fue descrito por Kirchner y Kulisch ; [ 17 ] una implementación de hardware fue descrita por Müller, Rüb y Rülling. [ 18 ]
Posible invalidación por optimización del compilador
En principio, un compilador optimizador suficientemente agresivo podría destruir la efectividad de la suma de Kahan: por ejemplo, si el compilador simplificara las expresiones según las reglas de asociatividad de la aritmética real, podría "simplificar" el segundo paso de la secuencia.
t = sum + y;c = (t - sum) - y;
a
c = ((sum + y) - sum) - y;
y luego a
c = 0;
eliminando así la compensación de errores. [ 19 ] En la práctica, muchos compiladores no utilizan reglas de asociatividad (que son solo aproximadas en aritmética de punto flotante) en simplificaciones, a menos que se les indique explícitamente que lo hagan mediante opciones del compilador que habilitan optimizaciones "inseguras", [ 20 ] [ 21 ] [ 22 ] [ 23 ] aunque el compilador Intel C++ es un ejemplo que permite transformaciones basadas en la asociatividad por defecto. [ 24 ] La versión original K&R C del lenguaje de programación C permitía al compilador reordenar expresiones de punto flotante de acuerdo con reglas de asociatividad de aritmética real, pero el estándar ANSI C posterior prohibió el reordenamiento para que C fuera más adecuado para aplicaciones numéricas (y más similar a Fortran , que también prohíbe el reordenamiento), [ 25 ] aunque en la práctica las opciones del compilador pueden volver a habilitar el reordenamiento, como se mencionó anteriormente.
Una forma portátil de inhibir localmente dichas optimizaciones es dividir una de las líneas de la formulación original en dos declaraciones y hacer que dos de los productos intermedios sean volátiles :
función KahanSum(entrada) var suma = 0,0 var c = 0,0 para i = 1 hasta input.length hacer var y = input[i] - c volatile var t = sum + y volatile var z = t - sum c = z - y suma = t a continuación yo suma de retorno
Apoyo de las bibliotecas
En general, las funciones de suma integradas en los lenguajes de programación no suelen garantizar que se emplee un algoritmo de suma específico, y mucho menos la suma de Kahan. El estándar BLAS para subrutinas de álgebra lineal evita explícitamente imponer un orden de operaciones computacional determinado por motivos de rendimiento [ 26 ] , y las implementaciones de BLAS no suelen utilizar la suma de Kahan.
La biblioteca estándar del lenguaje de programación Python utiliza la suma de Neumaier en la función integrada "sum()" a partir de CPython 3.12. [ 27 ] NumPy no garantiza el orden de la suma, aunque la suma parcial por pares se utiliza "a menudo". [ 28 ]
En el lenguaje Julia , la implementación predeterminada de la sumfunción realiza una suma por pares para obtener una alta precisión con un buen rendimiento, [ 29 ] pero una biblioteca externa proporciona una implementación de la variante de Neumaier denominada sum_kbnpara los casos en los que se necesita mayor precisión. [ 30 ]
En el lenguaje C# , el paquete NuGet HPCsharp implementa la variante de Neumaier y la suma por pares : ambas como escalares, en paralelo de datos usando instrucciones de procesador SIMD y en paralelo multinúcleo. [ 31 ]
Véase también
- Algoritmos para calcular la varianza , que incluye la suma estable.
Referencias
- ↑ Estrictamente hablando, también existen otras variantes de suma compensada: véase Higham, Nicholas (2002). Accuracy and Stability of Numerical Algorithms (2.ª ed.) . SIAM. pp. 110–123 . ISBN 978-0-89871-521-7.
- 1 2 3 4 5 6 7 8 Higham, Nicholas J. (1993), "La precisión de la suma de punto flotante", SIAM Journal on Scientific Computing , 14 (4): 783– 799, Bibcode : 1993SJSC...14..783H , CiteSeerX 10.1.1.43.3535 , doi : 10.1137/0914050 , S2CID 14071038 .
- 1 2 Kahan, William (enero de 1965), "Observaciones adicionales sobre la reducción de errores de truncamiento" (PDF) , Communications of the ACM , 8 (1): 40, doi : 10.1145/363707.363723 , S2CID 22584810 , archivado del original (PDF) el 9 de febrero de 2018 .
- ↑ Babuska, I.: Estabilidad numérica en el análisis matemático. Inf. Proc. ˇ 68, 11–23 (1969)
- ↑ Bresenham, Jack E. (enero de 1965). "Algoritmo para el control informático de un trazador digital" (PDF) . IBM Systems Journal . 4 (1): 25–30 . doi : 10.1147/sj.41.0025 . S2CID 41898371 .
- ↑ Inose, H.; Yasuda, Y.; Murakami, J. (septiembre de 1962). "Un sistema de telemetría mediante manipulación de código: modulación ΔΣ". IRE Transactions on Space Electronics and Telemetry . SET-8: 204–209 . doi : 10.1109/IRET-SET.1962.5008839 . S2CID 51647729 .
- ^ Müller, Jean-Michel; Brunie, Nicolás; de Dinechin, Florent; Jeannerod, Claude-Pierre; Joldes, Mioara; Lefèvre, Vicente; Melquiond, Guillaume; Revol, Nathalie ; Torres, Serge (2018) [2010]. Manual de aritmética de coma flotante (2 ed.). Birkhäuser . pag. 179.doi : 10.1007 /978-3-319-76526-6 . ISBN 978-3-319-76525-9. LCCN 2018935254 .
- ↑ Trefethen, Lloyd N.; Bau, David (1997). Álgebra lineal numérica . Filadelfia: SIAM. ISBN 978-0-89871-361-9.
- 1 2 Manfred Tasche y Hansmartin Zeuner, Manual de métodos analítico-computacionales en matemáticas aplicadas , Boca Raton, FL: CRC Press, 2000.
- ^ Neumaier, A. (1974). "Rundungsfehleranalyse einiger Verfahren zur Summation endlicher Summen" [ Análisis de errores de redondeo de algunos métodos para sumar sumas finitas ] (PDF) . Zeitschrift für Angewandte Mathematik und Mechanik (en alemán). 54 (1): 39– 51. Bibcode : 1974ZaMM...54...39N . doi : 10.1002/zamm.19740540106 . Archivado desde el original (PDF) el 21 de septiembre de 2015.
- 1 2
- ↑ A., Klein (2006). "Un algoritmo generalizado de suma de Kahan-Babuška". Computing . 76 ( 3–4 ). Springer-Verlag: 279–293 . doi : 10.1007/s00607-005-0139-x . S2CID 4561254 .
- ↑ "Suma rápida y precisa de números de coma flotante" .
- ↑ Dmitruk, Beata; Stpiczyński, Przemysław (2023). "Implementaciones vectorizadas paralelas de algoritmos de suma compensada". Procesamiento paralelo y matemáticas aplicadas . 13827 : 63–74 . doi : 10.1007/978-3-031-30445-3_6 .
- ↑ Johnson, SG; Frigo, MC Sidney Burns (ed.). "Transformadas rápidas de Fourier: Implementación de FFT en la práctica" . Archivado del original el 20 de diciembre de 2008.
- ↑ Richard Shewchuk, Jonathan (octubre de 1997). "Aritmética de punto flotante de precisión adaptativa y predicados geométricos robustos rápidos" (PDF) . Geometría discreta y computacional . 18 (3): 305–363 . doi : 10.1007/PL00009321 . S2CID 189937041 .
- ↑ Kirchner, R.; Kulisch, U. (junio de 1988). "Aritmética precisa para procesadores vectoriales" . Journal of Parallel and Distributed Computing . 5 (3): 250– 270. doi : 10.1016/0743-7315(88)90020-2 .
- ↑ Muller, M.; Rub, C.; Rulling, W. (1991). Acumulación exacta de números de punto flotante . Actas del 10.º Simposio IEEE sobre Aritmética Computacional. págs. 64–69 . doi : 10.1109/ARITH.1991.145535 .
- ↑ Goldberg, David (marzo de 1991), "Lo que todo científico informático debería saber sobre la aritmética de punto flotante" (PDF) , ACM Computing Surveys , 23 (1): 5–48 , doi : 10.1145/103162.103163 , S2CID 222008826 .
- ↑ Manual de la colección de compiladores GNU , versión 4.4.3: 3.10 Opciones que controlan la optimización , -fassociative-math (21 de enero de 2010).
- ↑ Manual de usuario de Compaq Fortran para sistemas Tru64 UNIX y Linux Alpha Archivado el 7 de junio de 2011 en Wayback Machine , sección 5.9.7 Optimizaciones de reordenamiento aritmético (consultado en marzo de 2010).
- ^ Börje Lindh, Optimización del rendimiento de las aplicaciones , Sun BluePrints OnLine (marzo de 2002).
- ↑ Eric Fleegal, " Optimización de punto flotante en Microsoft Visual C++ ", Artículos técnicos de Microsoft Visual Studio (junio de 2004).
- ↑ Martyn J. Corden, " Consistencia de los resultados de punto flotante utilizando el compilador de Intel ", informe técnico de Intel (18 de septiembre de 2009).
- ↑ MacDonald, Tom (1991). "C para computación numérica". Journal of Supercomputing . 5 (1): 31– 48. doi : 10.1007/BF00155856 . S2CID 27876900 .
- ↑ Foro técnico de BLAS , sección 2.7 (21 de agosto de 2001), archivado en Wayback Machine .
- ↑
- ^ "numpy.sum — Manual de NumPy v2.4" . numpy.org .
- ↑ RFC: usar suma por pares para sum, cumsum y cumprod , github.com/JuliaLang/julia solicitud de extracción n.° 4039 (agosto de 2013).
- ↑ Biblioteca KahanSummation en Julia.
- ↑ Paquete NuGet HPCsharp de algoritmos de alto rendimiento .
Enlaces externos
- Resumen en coma flotante, Dr. Dobb's Journal, septiembre de 1996
- aritmética informática
- Punto flotante
- Análisis numérico