Technical Article

Funções de Engenharia em Delphi: Conversão de Bases, Complexos

A família de engenharia no Excel assemelha-se à secção mais simples da referência de funções. O DEC2BIN converte um número numa cadeia de texto binária. O HEX2DEC faz o processo inverso. O IMSUM soma dois números complexos. Cada uma parece um exercício de formatação simples. Mas não são. Por trás destes nomes reside uma codificação de complemento para dois de dez bits, algo em que a maioria dos programadores não mexe desde as aulas de arquitetura de computadores, um formato de números complexos que existe inteiramente dentro de cadeias de texto, e operadores bit a bit que podem transbordar silenciosamente um inteiro de 64 bits se aplicar o desvio antes de validar. Um motor de folha de cálculo que reproduza o Excel com exatidão não pode simplificar nenhum destes detalhes.

As funções dividem-se em três grupos, e cada grupo oculta uma armadilha diferente. A conversão de bases prende-se com números negativos e limites específicos de cada base. A aritmética complexa consiste na análise (parsing) e formatação de cadeias de texto. As operações bit a bit constam em permanecer dentro dos limites de Int64. Este artigo percorre cada grupo tal como o HotXLS o implementa, apresentando as chamadas de folha de cálculo que escreveria na prática.

Conversão de bases e o complemento para dois de dez bits

O sentido direto corresponde ao que todos esperam. DEC2BIN(9) devolve "1001", e um segundo argumento opcional preenche o resultado à esquerda até uma largura fixa. A armadilha reside no valor de entrada negativo. O Excel não escreve um sinal de menos. Codifica o valor como uma cadeia de texto de complemento para dois de dez dígitos na base de destino, razão pela qual DEC2BIN(-5,10) devolve "1111111011" em vez de algo acompanhado de um sinal. O argumento de posições é ignorado quando o valor é negativo, pois a codificação está já fixada em dez dígitos.

Dez dígitos constituem um orçamento fixo, e esse orçamento define o intervalo representável por base. Em binário, a magnitude que transita para a metade negativa é 512, e o módulo de rotação é 1024, pelo que uma cadeia binária tem sinal apenas quando possui exatamente dez caracteres de comprimento e o seu valor é de, pelo menos, 512. A mesma lógica aplica-se às outras bases. A octal utiliza um limite médio de 2^29 e um módulo total de 2^30. A hexadecimal utiliza 2^39 e 2^40. O leitor do HotXLS aplica exatamente esta regra: acumula os dígitos e, apenas quando a cadeia de texto tem dez caracteres de largura e o valor acumulado é igual ou superior ao limite médio, subtrai o módulo total para recuperar o valor com sinal. Uma cadeia de nove caracteres é sempre não negativa, independentemente do seu tamanho.

O codificador funciona de forma inversa. Um valor não negativo é convertido dígito a dígito e, opcionalmente, preenchido com zeros até à largura solicitada, sendo rejeitado se ultrapassar o limite positivo da base ou se a largura solicitada for demasiado estreita para o conter. Um valor negativo é primeiro enquadrado no intervalo adicionando o módulo total, o que o converte num valor cuja representação na base tem sempre dez dígitos, sendo estes depois emitidos com zeros à esquerda para preencher a largura. A validação de intervalo partilhada, os limites simétricos inferior e superior por base, é o que mantém as funções DEC2BIN, DEC2OCT e DEC2HEX coerentes entre si nos seus limites de atuação.

That leaves the cross-base conversions, the ones such as HEX2BIN and OCT2HEX that change base without passing through decimal in the function name. The implementation does not carry a separate routine for every ordered pair. It parses the input string into a signed decimal value using the source base, then formats that decimal value into the destination base. Decimal is the pivot. One parse routine and one format routine, composed, cover every combination, and because both halves share the same ten-digit signed convention, a negative value survives the trip with its sign intact.

Números complexos são cadeias de texto, pelo que o trabalho reside na análise

O Excel não possui um tipo de dados complexo. Um valor complexo é a cadeia de texto "a+bi", e todas as funções da família IM recebem essas cadeias e devolvem uma. A função COMPLEX constrói a cadeia de texto a partir de uma parte real e de uma parte imaginária. As funções IMSUM, IMSUB, IMPRODUCT e IMDIV analisam os seus argumentos, executam a aritmética nas partes numéricas e formatam o resultado de volta numa cadeia de texto. O processamento numérico traduz-se em álgebra elementar. A dificuldade reside unicamente em converter o texto em dois números de vírgula flutuante de forma fiável, e é aí que o analisador interno demonstra a sua utilidade.

Há dois pormenores que são fáceis de errar nesse analisador. O primeiro é a unidade imaginária simples. A cadeia "i" representa uma vez i, e não zero nem um erro, pelo que, quando o coeficiente à frente do sufixo está vazio ou é um sinal de mais isolado, o analisador tem de o ler como o valor 1, e um sinal de menos isolado como -1. Omitir isto faria com que IMSUM("i","i") deixasse de resultar em 2i. O segundo pormenor é a colisão da notação científica com o sinal que separa as partes real e imaginária. O analisador localiza esse separador procurando um sinal de mais ou de menos, mas um número escrito como "1.5E-3" contém um sinal de menos que pertence ao expoente. A pesquisa recusa, portanto, tratar um sinal de mais ou de menos como o separador quando o carácter imediatamente anterior é e ou E. Sem esta proteção, a parte real seria dividida a meio no sinal do expoente e a análise falharia num dado de entrada perfeitamente válido.

O sufixo propriamente dito é preservado em vez de ser normalizado. O Excel aceita tanto o i como o j, e o HotXLS recorda qual deles foi usado na entrada para que o resultado formatado exiba a mesma letra. A formatação aplica depois as simplificações convencionais: uma parte imaginária de um é impressa apenas como o sufixo, menos um como -i, uma parte imaginária zero reduz-se a um real simples, e uma parte real zero omite o prefixo 0+.

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Engineering');
    // Negative input: a ten-bit two's complement, places argument ignored.
    Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
    // Complex multiply on two "a+bi" strings.
    Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
  finally
    Book.Free;
  end;
end;

As funções complexas transcendentais, entre as quais IMSQRT, IMEXP, IMLN e IMPOWER, não operam em coordenadas retangulares. Convertem o valor analisado para a forma polar, aplicam a operação no módulo e argumento, e convertem-no de volta. Uma raiz quadrada reduz o argumento para metade e extrai a raiz do módulo. Uma potência multiplica o argumento e eleva o módulo. Proceder de outra forma exigiria deduzir novamente cada identidade na forma retangular, o que representaria mais código e menor estabilidade numérica junto aos pontos de ramificação.

Operadores bit a bit e o transbordo que deve validar primeiro

O Excel 2013 adicionou as funções BITAND, BITOR, BITXOR, BITLSHIFT e BITRSHIFT. Os operandos possuem restrições: cada um deve ser um inteiro não negativo não superior a 2^48 menos 1, e qualquer argumento fracionário ou negativo constitui um erro numérico. Esse limite máximo é suficientemente generoso para cobrir qualquer conjunto realista de sinalizações, permanecendo perfeitamente dentro do intervalo de referência de um double, o que é importante dado que o Excel transmite todos os argumentos numéricos como valores de vírgula flutuante.

As funções de desvio contêm a única regra de ordenação que realmente causa problemas. Um desvio à esquerda pode produzir um valor muito superior ao de entrada e, se executar a operação shl primeiro e inspecionar o resultado depois, já terá transbordado o limite de Int64 e o teste torna-se inútil. A validação tem de ocorrer antes do desvio. O HotXLS compara o operando com o limite máximo desviado à direita pela quantidade do desvio e, apenas se o operando couber, executa o desvio à esquerda real. Uma magnitude de desvio além de 53 bits é rejeitada de imediato, e um desvio negativo simplesmente inverte a direção, pelo que o BITLSHIFT com uma contagem negativa se comporta como um desvio à direita. O princípio generaliza-se muito para além desta função específica: quando existe uma proteção para evitar transbordos, esta deve atuar sobre as entradas e nunca sobre o resultado que se pretendia proteger.

// Bitwise calls evaluate the same way through Calculate.
Sheet.Cells[3, 1].Value := Sheet.Calculate('=BITAND(13,11)');    // 9
Sheet.Cells[4, 1].Value := Sheet.Calculate('=BITLSHIFT(5,2)');   // 20
Sheet.Cells[5, 1].Value := Sheet.Calculate('=BITRSHIFT(40,3)');  // 5

Funções futuras e o prefixo de nome _xlfn

Os operadores bit a bit e uma vasta lista de outras adições posteriores a 2007 interagem com um esquema de nomenclatura que nada tem a ver com o que calculam, mas sim com a forma como o Excel os guarda. O formato de folha de cálculo binário original atribuía a cada função integrada uma posição numérica numa tabela fixa. As funções criadas após o congelamento dessa tabela não têm posição atribuída. Para gravar uma função desse tipo num ficheiro de modo a que um Excel moderno a reconheça, o nome é escrito com um prefixo _xlfn., pelo que o BITAND é guardado como _xlfn.BITAND no disco, mesmo que o utilizador digite apenas BITAND.

O problema é que a regra não é uniforme. A algumas funções mais recentes foram atribuídas posições na tabela e são escritas de forma simples, enquanto algumas funções ocultas legadas são também escritas sem prefixo, apesar da sua antiguidade. HotXLS mantém uma lista de permissões (whitelist) explícita que define quais os nomes que necessitam do prefixo, adicionando-o na escrita e removendo-o na leitura, para que o texto da fórmula que define e lê de volta corresponda sempre ao nome limpo apresentado no Excel. Define =BITLSHIFT(5,2), o ficheiro contém _xlfn.BITLSHIFT e o valor regressa como 20 em qualquer situação. O prefixo é um detalhe de armazenamento que nunca deve transparecer para as fórmulas com que trabalha no código.

Reunir as peças numa folha de cálculo

A interface pública para tudo isto é reduzida. Cria um TXLSXWorkbook, adiciona uma folha de cálculo e escreve uma fórmula numa célula através de Cells[Row, Col].Formula e recalcula, ou avalia uma expressão diretamente com o método Calculate da folha de cálculo, que compila a fórmula face a essa folha e devolve um Variant. Os exemplos acima utilizam o Calculate porque exibe o resultado de uma chamada de engenharia única sem o estado circundante da folha, mas as mesmas funções são avaliadas de forma idêntica em fórmulas de células reais quando o livro de trabalho (workbook) é recalculado.

As codificações são o elemento a ter em conta, e não os locais das chamadas. Uma cadeia de texto binária tem sinal apenas aos dez dígitos e somente acima do limite médio para a sua base. Um número complexo é texto, um coeficiente imaginário vazio equivale a um, e o analisador ignora o e de um expoente. Um desvio à esquerda é validado antes de ser executado. Compreenda corretamente estes quatro factos e a família de engenharia deixará de ser uma fonte de surpresas de inversão de sinais.

Se estiver a integrar a sua própria matemática de domínio no mesmo motor, o processo para registar um processador e devolver valores é coberto no nosso artigo sobre como estender o motor de fórmulas com funções personalizadas, e quando essas fórmulas têm de aceder a outras folhas por nome e não por endereço de célula, o guia de nomes definidos e fórmulas entre folhas descreve como as referências se resolvem. As funções de engenharia descritas aqui são fornecidas como parte do HotXLS Component para Delphi e C++Builder, a par das APIs de leitura, escrita e cálculo abrangidas noutros locais deste blogue.