Artigo Técnico

Recálculo Incremental de Fórmulas no HotXLS para Delphi

O HotXLS, a biblioteca nativa do Excel para Delphi e C++Builder, realiza o recálculo incremental de fórmulas por meio do método TXLSXWorkbook.Recalculate. A primeira chamada constrói um gráfico de dependência de fórmulas e avalia cada célula com fórmula; cada chamada posterior reavalia apenas as células afetadas por gravações de valores desde a última passagem, em ordem topológica, em uma única varredura cujo custo é proporcional ao número de células sujas (dirty cells) em vez do tamanho da pasta de trabalho

Essa única decisão de design é a diferença entre um modelo financeiro que responde a uma premissa editada em milissegundos e um que trava por segundos. Se você gera relatórios nos quais um punhado de células de entrada alimenta milhares de fórmulas a jusante (downstream), o restante deste artigo explica o que o gráfico faz, quais funções optam por não participar da incrementalidade e como as referências circulares são relatadas em vez de entrar em loop eterno

Por que alterar uma única célula recalcula cem mil fórmulas?

Um mecanismo de fórmulas ingênuo não tem memória de quem depende de quem, portanto seu único movimento seguro após qualquer edição é avaliar tudo novamente. Pior ainda, a estratégia recursiva clássica — quando a fórmula A faz referência à fórmula B, avalia-se B no ato — reavalia as células referenciadas incondicionalmente, ignorando qualquer valor em cache. Uma cadeia de n fórmulas, cada uma referenciando a anterior, custa O(n²) avaliações por passagem completa, e uma referência circular joga a recursão no abismo. Todo desenvolvedor de planilhas que conectou um modelo em cascata a um avaliador recursivo já assistiu a ambos os modos de falha acontecerem

O próprio Excel resolveu isso décadas atrás com sua cadeia de cálculo: uma ordenação de células com fórmulas mantida para que uma edição marque um pequeno conjunto de células como sujas e o mecanismo percorra apenas a cauda afetada da cadeia. O HotXLS aplica a mesma ideia como um gráfico de dependência explícito, construído uma única vez a partir das árvores de fórmulas compiladas e reutilizado em todas as passagens de recálculo. O ponto não é a inteligência; é que o custo do recálculo deve acompanhar o tamanho da sua edição, e não o tamanho da sua pasta de trabalho

Como o gráfico de dependência transforma uma edição em uma única passagem

O gráfico de dependência do HotXLS dá a cada célula com fórmula um nó, com arestas partindo do precedente para o dependente. Quando o seu código grava o valor de uma célula, a pasta de trabalho registra a célula como suja; quando o Recalculate é executado, a sujeira se propaga ao longo das arestas para cada fórmula a jusante, e o subgráfico sujo é avaliado exatamente uma vez em ordem topológica usando o algoritmo de Kahn. Como uma fórmula nunca é visitada antes de seus precedentes, cada nó necessita de uma única avaliação — é isso que torna a passagem O(sujo)

A ordem topológica também corrige o problema de recursão em sua raiz. Durante uma passagem de recálculo, o mecanismo muda para um modo dedicado no qual qualquer referência a outra célula com fórmula lê o valor em cache dessa célula diretamente, em vez de reavaliá-lo — a ordenação garante que o cache já esteja atualizado. Esse mesmo mecanismo significa que um ciclo de referência não possa desencadear recursão ilimitada: nada dentro da passagem reentra no avaliador para uma célula vizinha

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 vai para a propriedade Value em cache da célula, de modo que, após o retorno de Recalculate, você leia as saídas da mesma forma que lê qualquer outra célula. Em um loop de geração de relatórios, o padrão é exatamente o código acima: carregue ou construa o modelo uma vez e, em seguida, alterne entre gravar algumas células de entrada e chamar Recalculate, pagando apenas pelas fórmulas que realmente dependem do que foi alterado

Quais funções do Excel forçam o recálculo a cada passagem?

O HotXLS trata NOW, TODAY, RAND, OFFSET e INDIRECT como voláteis: qualquer fórmula que contenha um deles é reavaliada em cada passagem de Recalculate, independentemente de qualquer alteração a montante (upstream). As três primeiras são voláteis pelo mesmo motivo que no Excel — seu resultado depende do momento da avaliação, e não de outras células. As funções OFFSET e INDIRECT são voláteis por uma razão mais sutil: as células que elas leem são computadas em tempo de execução, de modo que o gráfico não possa saber estaticamente quais arestas desenhar para elas

A mesma regra conservadora se estende a referências que o construtor do gráfico não consegue fixar em um único retângulo. Uma fórmula que passa por um intervalo nomeado de múltiplas áreas (multi-area named range), ou uma que faz referência a uma pasta de trabalho externa, é igualmente rebaixada a volátil e reavaliada a cada passagem. A política é deliberada: uma avaliação extra custa um pouco de tempo, mas uma aresta de dependência ausente significa um valor silenciosamente desatualizado em um relatório enviado, e essa é uma falha muito pior. Se o seu modelo depende de nomes com escopo de pasta de trabalho, o artigo complementar sobre nomes definidos e fórmulas entre planilhas aborda como os nomes de área única se resolvem — estes participam do gráfico normalmente

A orientação prática segue diretamente disso. Mantenha os caminhos críticos de um modelo grande em referências simples de células e intervalos, onde o gráfico pode fazer seu trabalho, e coloque OFFSET e INDIRECT em quarentena nos poucos locais que genuinamente precisam de endereçamento dinâmico. Um modelo com mil fórmulas voláteis reexecuta essas mil a cada passagem, não importa quão pequena tenha sido a edição — exatamente o comportamento que os usuários do Excel conhecem de pastas de trabalho que "recalculam a cada pressionamento de tecla"

Como o HotXLS relata referências circulares?

O TXLSXWorkbook.Recalculate retorna lxOk em uma passagem limpa e lxErrorRef quando detecta um ciclo de referência. Os membros do ciclo são identificados durante a ordenação topológica — são os nós que o algoritmo de Kahn nunca consegue liberar — e são ignorados em vez de entrar em loop: seus valores em cache permanecem como estavam, enquanto todas as fórmulas fora do ciclo ainda são avaliadas normalmente em ordem. O seu local de chamada recebe um código de erro definido em vez de um travamento

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 quais células formam o ciclo é um trabalho de depuração, e o rastreador de avaliação de fórmulas é a ferramenta certa para isso: rastreie a fórmula suspeita e a cadeia de referência que se dobra sobre si mesma se tornará visível passo a passo. Ciclos em modelos reais são quase sempre um erro de autoria — uma linha de resumo incluída acidentalmente em seu próprio intervalo SUM —, de modo que um código de erro evidente no momento do recálculo é precisamente o que você deseja

Fórmulas de matriz, rastreamento de sujeira e quando o gráfico reconstrói

Fórmulas de matriz CSE ganham um nó para todo o retângulo ancorado, e não um nó por célula. A fórmula raiz é avaliada uma vez por passagem; a matriz resultante é gravada diretamente em cada célula membro, e uma fórmula que referencia qualquer célula dentro do intervalo ancorado — e não apenas a âncora superior esquerda — obtém uma aresta de dependência a partir desse nó raiz. Resultados escalares são propagados pelo retângulo da forma que as semânticas de matriz legadas do Excel prescrevem

O rastreador de sujeira (dirty tracking) vincula-se aos definidores de propriedade comuns, de modo que nada em seu código muda. Gravar o Value em uma célula notifica a pasta de trabalho e marca os dependentes como sujos; atribuir uma nova Formula é uma alteração estrutural, portanto marca todo o gráfico como desatualizado, e o próximo Recalculate o reconstrói antes de avaliar. Adicionar, excluir ou mover planilhas também invalida o gráfico, uma vez que a identidade do nó codifica o índice da planilha. Quando nenhum gráfico está ativo — uma pasta de trabalho na qual você nunca chama o Recalculate —, os vínculos custam uma única verificação de nulo (nil check) por atribuição, portanto fluxos normais de leitura e gravação não são afetados

Um limite que vale a pena expor honestamente: o gráfico rastreia dependências entre células, de modo que uma função definida pelo usuário registrada por meio do OnUserFunction seja reavaliada quando as células que alimentam seus argumentos mudarem, como qualquer outra fórmula. Se você estiver estendendo o mecanismo dessa forma, o artigo sobre funções personalizadas no mecanismo de fórmulas do HotXLS descreve o contrato de callback e como os valores dos argumentos chegam

O recálculo incremental faz parte do mecanismo XLSX padrão no HotXLS Delphi Excel Component, juntamente com a calculadora de fórmulas, nomes definidos e o pipeline de importação/exportação que ele acelera. Se o seu aplicativo Delphi ou C++Builder mantém modelos ativos — planilhas de preços, pastas de trabalho de consolidação ou fluxos de relatórios —, o Recalculate é a diferença entre computar novamente uma pasta de trabalho inteira ou computar novamente uma edição