Artículo técnico

Recálculo incremental de fórmulas en HotXLS para Delphi

HotXLS, la biblioteca nativa de Excel para Delphi y C++Builder, realiza el recálculo incremental de fórmulas a través de TXLSXWorkbook.Recalculate. La primera llamada crea un gráfico de dependencias de fórmulas y evalúa cada celda con fórmula; cada llamada posterior vuelve a evaluar 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 (dirty) en lugar del tamaño del libro de trabajo

Esa única decisión de diseño es la diferencia entre un modelo financiero que responde a una hipótesis editada en milisegundos y uno que se detiene durante segundos. Si genera informes donde un puñado de celdas de entrada alimentan miles de fórmulas descendentes, el resto de este artículo explica qué hace el gráfico, qué funciones optan por no participar en 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, 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, cada una de las cuales hace referencia a la anterior, cuesta O(n²) evaluaciones por pasada completa, y una referencia circular envía la recursividad al abismo. 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

Cómo el gráfico de dependencias convierte una edición en una sola pasada

El gráfico de dependencias de HotXLS le da a cada celda con 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, la suciedad se propaga a lo largo de las aristas a cada fórmula descendente, y el subgráfico sucio se evalúa exactamente una vez en orden topológico utilizando el algoritmo de Kahn. Debido a que nunca se visita una fórmula antes de 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 recálculo, el motor cambia a un modo dedicado en el que cualquier referencia a otra celda con fórmula lee el valor almacenado en caché de esa celda directamente en lugar de volver a evaluarlo; el orden garantiza que la caché ya esté actualizada. El mismo mecanismo significa que un ciclo de referencia no puede desencadenar 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 aterriza en el campo Value almacenado en caché de la celda, por lo que después de que Recalculate devuelve, 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 cree 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 obligan a realizar 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 vuelve a evaluar en cada pasada de Recalculate, haya cambiado o no algo en la fase anterior. 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, por lo que el gráfico no puede saber estáticamente qué aristas dibujar para ellas

La misma regla conservadora se extiende a las referencias que el constructor de gráficos no puede fijar en un único 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, también se degrada 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 una arista de dependencia faltante significa un valor silenciosamente desactualizado en un informe entregado, y ese es el peor fallo. Si su modelo se apoya en nombres de ámbito 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: aquellos participan en el gráfico normalmente

La guía práctica se deriva directamente de esto. Mantenga las rutas calientes (hot paths) de un modelo grande en referencias de celdas y rangos simples donde el gráfico pueda hacer su trabajo, y ponga en cuarentena OFFSET e INDIRECT en los pocos lugares que genuinamente necesitan direccionamiento dinámico. Un modelo con mil fórmulas volátiles vuelve a ejecutar esas mil en cada pasada, sin importar cuán 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 de 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 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 almacenados en caché permanecen como estaban, mientras que cada fórmula fuera del ciclo se evalúa normalmente en orden. El sitio de la 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 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 hará visible paso a paso. Los ciclos en los modelos reales casi siempre son un error de autoría (una fila de resumen incluida accidentalmente en su propio rango SUM), por lo que un código de error sonoro en el momento del recálculo es precisamente lo que desea

Fórmulas de matriz, seguimiento de suciedad y cuándo se reconstruye el gráfico

Las fórmulas de matriz CSE obtienen 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 hace referencia a cualquier celda dentro del rango anclado (no solo al anclaje superior izquierdo) recoge una arista de dependencia de ese nodo raíz. Los resultados escalares se difunden por todo el rectángulo de la forma en que lo prescriben las semánticas de matrices heredadas de Excel

El seguimiento de suciedad se conecta a los establecedores de propiedades ordinarios, por lo que nada cambia en su código. Escribir Value en una celda notifica al libro de trabajo y marca a los dependientes como sucios; asignar una nueva Formula es un cambio estructural, por lo que marca todo el gráfico como desactualizado, y el próximo Recalculate lo reconstruye antes de evaluar. Añadir, eliminar o mover hojas también invalida el gráfico, ya que la identidad del nodo codifica el índice de la hoja. Cuando no hay ningún gráfico activo (un libro de trabajo en el que nunca llama a Recalculate), los ganchos (hooks) cuestan una sola comprobación de nil por asignación, por lo que las cargas de trabajo de lectura y escritura normales no se ven afectadas

Lo que las tres pasadas no harán

Un detalle que vale la pena exponer con honestidad: el gráfico de dependencias rastrea las relaciones 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 HotXLS detalla el contrato de devolución de llamada (callback) y cómo llegan los valores de los argumentos

El recálculo incremental forma parte del motor XLSX estándar en el HotXLS Delphi Excel Component, junto con el calculador de fórmulas, los nombres definidos y la canalización de importación/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 volver a calcular un libro de trabajo y volver a calcular una edición