Artículo técnico

Recalculación incremental de fórmulas en HotXLS para Delphi

HotXLS, la biblioteca nativa de Excel para Delphi y C++Builder, realiza la recalculación incremental de fórmulas a través de TXLSXWorkbook.Recalculate. La primera llamada construye un grafo de dependencias de fórmulas y evalúa cada celda de fórmula; cada llamada posterior vuelve a evaluar únicamente las celdas afectadas por las escrituras de valores desde la última pasada, en orden topológico, en un solo recorrido cuyo costo es proporcional al número de celdas sucias en lugar del tamaño del libro de trabajo

Esa decisión de diseño es la diferencia entre un modelo financiero que responde a una suposición editada en milisegundos y uno que se paraliza durante segundos. Si genera informes donde un puñado de celdas de entrada alimenta miles de fórmulas derivadas, el resto de este artículo explica qué hace el grafo, qué funciones quedan fuera de la incrementalidad y cómo se informan las referencias circulares en lugar de crear 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, por lo que su único movimiento seguro después de cualquier edición es evaluar todo de nuevo. Peor aún, la estrategia recursiva clásica — cuando la fórmula A hace referencia a la fórmula B, evaluar B en el acto — vuelve a evaluar las celdas referenciadas incondicionalmente, ignorando cualquier valor almacenado en caché. Una cadena de n fórmulas, donde cada una hace referencia a la anterior, cuesta O(n²) evaluaciones por pasada completa, y una referencia circular hace que la recursividad caiga al vacío. Cada desarrollador de hojas de cálculo que ha conectado un modelo en cascada en un evaluador recursivo ha visto ocurrir ambos modos de falla

El propio Excel resolvió esto hace décadas con su cadena de cálculo: un ordenamiento de celdas de fórmulas mantenido de modo que una edición marca un pequeño conjunto de celdas como sucias 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órmulas compilados y reutilizado en las pasadas de recalculación. El punto no es la astucia; es que el costo de recalculación debe coincidir con el tamaño de su edición, no con el tamaño de su libro de trabajo

Cómo el grafo de dependencias convierte una edición en una sola pasada

El grafo de dependencias de HotXLS otorga a cada celda de fórmula un nodo, con aristas que van desde el precedente hasta el dependiente. Cuando su código escribe el valor de una celda, el libro de trabajo registra la celda como sucia; cuando se ejecuta Recalculate, el estado sucio se propaga a lo largo de las aristas a cada fórmula derivada, y el subgrafo sucio se evalúa exactamente una vez en orden topológico utilizando el algoritmo de Kahn. Debido a que una fórmula nunca se visita antes que sus precedentes, cada nodo necesita una sola evaluación — eso es lo que hace que la pasada sea O(sucias)

El orden topológico también soluciona el problema de recursividad en su raíz. Durante una pasada de recalculación, el motor cambia a un modo dedicado en el que cualquier referencia a otra celda de fórmula lee directamente el valor almacenado en caché de esa celda en lugar de volver a evaluarla — el ordenamiento garantiza que la caché ya esté actualizada. El mismo mecanismo significa que un ciclo de referencia no puede provocar una recursividad ilimitada: nada dentro de la pasada vuelve a entrar al 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;                 // growth assumption
    Model.Cells[2, 2].Formula := 'Inputs!B2*1000';    // XLSX formulas take no leading '='
    Model.Cells[3, 2].Formula := 'B2*(1+Inputs!B2)';
    // ... thousands more rows cascading off the same assumption ...

    Book.Recalculate;                 // first call: builds the graph, full evaluation

    Inputs.Cells[2, 2].Value := 0.07; // one edit marks one cell dirty
    Book.Recalculate;                 // second call: only the downstream chain runs
  finally
    Book.Free;
  end;
end;

Cada resultado va al valor Value almacenado en caché de la celda, por lo que después de que Recalculate regresa, usted lee las salidas de la misma manera 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, luego 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 la recalculación en cada pasada?

HotXLS trata a NOW, TODAY, RAND, OFFSET e INDIRECT como volátiles: cualquier fórmula que contenga una de ellas se vuelve a evaluar en cada pasada de Recalculate, cambie o no algo en los pasos previos. Las tres primeras son volátiles por la misma razón que lo son 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, por lo que el grafo no puede conocer estáticamente qué aristas dibujar para ellas

La misma regla conservadora se extiende a las referencias que el constructor del grafo no puede definir en un solo rectángulo. Una fórmula que pasa por un rango con nombre de múltiples áreas, o una que hace referencia a un libro de trabajo externo, se degrada igualmente a volátil y se vuelve a evaluar en cada pasada. La política es deliberada: una evaluación adicional cuesta un poco de tiempo, pero la falta de una arista de dependencia significa un valor desactualizado silencioso en un informe entregado, y ese es un fallo mucho peor. Si su modelo se apoya en nombres de alcance de libro de trabajo, el artículo complementario sobre nombres definidos y fórmulas entre hojas cubre cómo se resuelven los nombres de una sola área — estos participan en el grafo normalmente

La guía práctica se deriva directamente. Mantenga las rutas calientes de un modelo grande en referencias simples a celdas y rangos donde el grafo pueda hacer su trabajo, y aísle OFFSET e INDIRECT a los pocos lugares que genuinamente necesitan direccionamiento dinámico. A un modelo con mil fórmulas volátiles vuelve a ejecutar esas mil en cada pasada, sin importar qué tan pequeña haya sido la edición — exactamente el comportamiento que los usuarios de Excel conocen de los libros de trabajo que "se recalculan con cada pulsación de tecla"

¿Cómo informa HotXLS las referencias circulares?

TXLSXWorkbook.Recalculate devuelve lxOk en una pasada limpia y lxErrorRef cuando detecta un ciclo de referencia. Los miembros del ciclo se identifican durante el ordenamiento topológico — son los nodos que el algoritmo de Kahn nunca puede liberar — y se omiten en lugar de entrar en bucle: sus valores almacenados en caché permanecen tal como estaban, mientras que cada fórmula fuera del ciclo se evalúa normalmente en orden. El punto de llamada obtiene un código de error definido en lugar de un bloqueo

case Book.Recalculate of
  lxOk:
    SaveReport(Book);
  lxErrorRef:
    // a reference cycle exists; cycle members kept their previous
    // cached values and everything outside the cycle is up to date
    LogWarning('Circular reference detected - review model inputs');
end;

Encontrar qué celdas forman el ciclo es un trabajo de depuración, y el rastreador de evaluación de fórmulas es la herramienta adecuada para ello: rastree la fórmula sospechosa y la cadena de referencia que se pliega sobre sí misma se volverá visible paso a paso. Los ciclos en modelos reales son casi siempre un error de creación — una fila de resumen incluida accidentalmente en su propio rango SUM — por lo que un código de error evidente en el momento de la recalculación es precisamente lo que desea

Fórmulas de matriz, seguimiento de celdas sucias y cuándo se reconstruye el grafo

Las fórmulas de matriz CSE obtienen un solo 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 hace referencia a cualquier celda dentro del rango anclado — no solo al ancla superior izquierda — adquiere una arista de dependencia de ese nodo raíz. Los resultados escalares se transmiten a lo largo del rectángulo de la manera que prescriben las semánticas de matriz heredadas de Excel

El seguimiento de celdas sucias se conecta a los establecedores de propiedades comunes, por lo que nada cambia en su código. Escribir Value en una celda notifica al libro de trabajo y marca los dependientes como sucios; asignar una nueva Formula es un cambio estructural, por lo que marca todo el grafo como desactualizado, 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 la hoja. Cuando no hay un grafo activo — un libro de trabajo en el que nunca llama a Recalculate — las conexiones cuestan una sola comprobación de nil por asignación, por lo que las cargas de trabajo sencillas de lectura y escritura no se ven afectadas

Un límite que vale la pena exponer con honestidad: el grafo rastrea las dependencias entre celdas, por lo que una función definida por el usuario registrada a través de OnUserFunction se vuelve a evaluar cuando cambian las celdas que alimentan sus argumentos, como cualquier otra fórmula. Si está extendiendo el motor de esa manera, el artículo sobre funciones personalizadas en el motor de fórmulas de HotXLS recorre el contrato de devolución de llamada (callback) y cómo llegan los valores de los argumentos

La recalculación incremental es parte del motor XLSX estándar en el componente HotXLS Delphi Excel Component, junto con el calculador de fórmulas, los nombres definidos y el flujo de trabajo de importación/exportación que acelera. Si su aplicación Delphi o C++Builder mantiene modelos activos — hojas de precios, libros de trabajo de consolidación o cascadas de informes — Recalculate es la diferencia entre volver a calcular un libro de trabajo y volver a calcular una edición