HotXLS, la librería Excel nativa para Delphi y C++Builder, realiza el recálculo incremental de fórmulas mediante TXLSXWorkbook.Recalculate. La primera llamada construye un grafo de dependencias de fórmulas y evalúa todas las celdas con fórmula; cada llamada posterior reevalúa solo las celdas afectadas por las escrituras de valores desde la última pasada, en orden topológico, en un único barrido cuyo coste es proporcional al número de celdas sucias y no al tamaño del libro
Esa única decisión de diseño es la diferencia entre un modelo financiero que responde a un supuesto editado en milisegundos y uno que se atasca durante segundos. Si genera informes en los que un puñado de celdas de entrada alimenta miles de fórmulas aguas abajo, el resto de este artículo explica qué hace el grafo, qué funciones renuncian a la incrementalidad y cómo se informan las referencias circulares en lugar de entrar en un bucle infinito
¿Por qué cambiar una celda recalcula cien mil fórmulas?
Un motor de fórmulas ingenuo no recuerda quién depende de quién, así que su única jugada segura tras cualquier edición es evaluarlo todo de nuevo. Peor aún, la estrategia recursiva clásica, cuando la fórmula A referencia la fórmula B, evaluar B en el acto, reevalúa las celdas referenciadas de forma incondicional, ignorando cualquier valor en caché. Una cadena de n fórmulas, cada una referenciando la anterior, cuesta O(n²) evaluaciones por pasada completa, y una referencia circular lanza la recursión al vacío. Todo desarrollador de hojas de cálculo que haya conectado un modelo en cascada a un evaluador recursivo ha visto ocurrir ambos modos de fallo
El propio Excel resolvió esto hace décadas con su cadena de cálculo: una ordenación de las celdas con fórmula mantenida de modo que una edición marca como sucio un conjunto pequeño de celdas y el motor recorre solo la cola afectada de la cadena. HotXLS aplica la misma idea como un grafo de dependencias explícito, construido una vez a partir de los árboles de fórmula compilados y reutilizado entre pasadas de recálculo. La cuestión no es la astucia; es que el coste del recálculo debe seguir el tamaño de su edición, no el tamaño de su libro
Cómo el grafo de dependencias convierte una edición en una sola pasada
El grafo de dependencias de HotXLS da a cada celda con fórmula un nodo, con aristas que van del precedente al dependiente. Cuando su código escribe el valor de una celda, el libro registra esa celda como sucia; cuando se ejecuta Recalculate, la suciedad se propaga por las aristas hasta cada fórmula aguas abajo, y el subgrafo sucio se evalúa exactamente una vez en orden topológico usando el algoritmo de Kahn. Como una fórmula nunca se visita antes que sus precedentes, cada nodo necesita una única evaluación; eso es lo que hace que la pasada sea O(sucias)
El orden topológico también corrige el problema de la recursión en su raíz. Durante una pasada de recálculo el motor cambia a un modo dedicado en el que cualquier referencia a otra celda con fórmula lee directamente el valor en caché de esa celda en lugar de reevaluarla; la ordenación garantiza que la caché ya está fresca. El mismo mecanismo implica que un ciclo de referencias no puede desencadenar una recursión sin límite: nada dentro de la pasada vuelve a entrar en el evaluador para una celda vecina
var
Book: TXLSXWorkbook;
Inputs, Model: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Inputs := Book.Sheets.Add('Inputs');
Model := Book.Sheets.Add('Model');
Inputs.Cells[2, 2].Value := 0.05; // supuesto de crecimiento
Model.Cells[2, 2].Formula := 'Inputs!B2*1000'; // las fórmulas XLSX no llevan '=' inicial
Model.Cells[3, 2].Formula := 'B2*(1+Inputs!B2)';
// ... miles de filas más en cascada a partir del mismo supuesto ...
Book.Recalculate; // primera llamada: construye el grafo, evaluación completa
Inputs.Cells[2, 2].Value := 0.07; // una edición marca una celda como sucia
Book.Recalculate; // segunda llamada: solo se ejecuta la cadena aguas abajo
finally
Book.Free;
end;
end;
Cada resultado aterriza en el Value en caché de la celda, así que cuando Recalculate devuelve el control lee las salidas igual que lee cualquier otra celda. En un bucle de generación de informes el patrón es exactamente el código anterior: cargue o construya el modelo una vez, y después alterne entre escribir unas pocas celdas de entrada y llamar a Recalculate, pagando solo por las fórmulas que realmente dependen de lo que cambió
¿Qué funciones de Excel fuerzan el recálculo en cada pasada?
HotXLS trata NOW, TODAY, RAND, OFFSET e INDIRECT como volátiles: cualquier fórmula que contenga una de ellas se reevalúa en cada pasada de Recalculate, haya cambiado o no algo aguas arriba. Las tres primeras son volátiles por la misma razón que en Excel: su resultado depende del momento de la evaluación, no de otras celdas. OFFSET e INDIRECT son volátiles por una razón más sutil: las celdas que leen se calculan en tiempo de ejecución, así que el grafo no puede saber estáticamente qué aristas dibujar para ellas
La misma regla conservadora se extiende a las referencias que el constructor del grafo no puede fijar a un único rectángulo. Una fórmula que pasa por un rango con nombre de varias áreas, o que referencia un libro externo, se degrada igualmente a volátil y se reevalúa en cada pasada. La política es deliberada: una evaluación extra cuesta un poco de tiempo, pero una arista de dependencia ausente significa un valor obsoleto en silencio dentro de un informe entregado, y ese es el fallo mucho peor. Si su modelo se apoya en nombres de ámbito libro, el artículo complementario sobre nombres definidos y fórmulas entre hojas explica cómo se resuelven los nombres de una sola área; esos participan en el grafo con normalidad
La guía práctica se deduce directamente. Mantenga las rutas calientes de un modelo grande sobre referencias simples a celdas y rangos, donde el grafo puede hacer su trabajo, y ponga en cuarentena OFFSET e INDIRECT en los pocos lugares que necesitan de verdad direccionamiento dinámico. Un modelo con mil fórmulas volátiles vuelve a ejecutar esas mil en cada pasada por pequeña que sea la edición, exactamente el comportamiento que los usuarios de Excel conocen de los libros que «recalculan con cada pulsación de tecla»
¿Cómo informa HotXLS de las referencias circulares?
TXLSXWorkbook.Recalculate devuelve lxOk en una pasada limpia y lxErrorRef cuando detecta un ciclo de referencias. Los miembros del ciclo se identifican durante la ordenación topológica, son los nodos que el algoritmo de Kahn nunca puede liberar, y se omiten en lugar de entrar en bucle: sus valores en caché se quedan como estaban, mientras que todas las fórmulas fuera del ciclo se evalúan con normalidad y en orden. Su punto de llamada obtiene un código de error definido en lugar de un cuelgue
case Book.Recalculate of
lxOk:
SaveReport(Book);
lxErrorRef:
// existe un ciclo de referencias; los miembros del ciclo conservaron sus
// valores en caché anteriores y todo lo que está fuera del ciclo está al día
LogWarning('Circular reference detected - review model inputs');
end;
Averiguar qué celdas forman el ciclo es una tarea de depuración, y el trazador de evaluación de fórmulas es la herramienta adecuada para ello: trace la fórmula sospechosa y la cadena de referencias que se pliega sobre sí misma se hace visible paso a paso. Los ciclos en modelos reales son casi siempre un error de autoría, una fila de resumen incluida por accidente en su propio rango SUM, así que un código de error bien visible en el momento del recálculo es precisamente lo que quiere
Fórmulas matriciales, seguimiento de suciedad y cuándo se reconstruye el grafo
Las fórmulas matriciales CSE reciben un nodo para todo el rectángulo anclado, no un nodo por celda. La fórmula raíz se evalúa una vez por pasada; la matriz resultante se escribe directamente en cada celda miembro, y una fórmula que referencie cualquier celda dentro del rango anclado, no solo el ancla superior izquierda, adquiere una arista de dependencia desde ese nodo raíz. Los resultados escalares se difunden por el rectángulo como prescribe la semántica matricial heredada de Excel
El seguimiento de suciedad se engancha a los setters de propiedad ordinarios, así que nada cambia en su código. Escribir Value en una celda notifica al libro y marca los dependientes como sucios; asignar una nueva Formula es un cambio estructural, así que marca todo el grafo como obsoleto, y el siguiente Recalculate lo reconstruye antes de evaluar. Añadir, eliminar o mover hojas también invalida el grafo, ya que la identidad del nodo codifica el índice de hoja. Cuando no hay ningún grafo activo, un libro sobre el que nunca llama a Recalculate, los ganchos cuestan una única comprobación de nil por asignación, así que las cargas de trabajo simples de lectura y escritura no se ven afectadas
Un límite que conviene declarar con sinceridad: el grafo sigue las dependencias entre celdas, así que una función definida por el usuario registrada mediante OnUserFunction se reevalúa cuando cambian las celdas que alimentan sus argumentos, como cualquier otra fórmula. Si está ampliando el motor de esa manera, el artículo sobre funciones personalizadas en el motor de fórmulas de HotXLS recorre el contrato del callback y cómo llegan los valores de los argumentos
El recálculo incremental forma parte del motor XLSX estándar del componente Excel HotXLS para Delphi, junto con la calculadora de fórmulas, los nombres definidos y la canalización de importación y exportación que acelera. Si su aplicación Delphi o C++Builder mantiene modelos vivos, hojas de precios, libros de consolidación, cascadas de informes, Recalculate es la diferencia entre recalcular un libro y recalcular una edición