A família de funções de engenharia no Excel parece o canto mais simples da referência de funções. DEC2BIN transforma um número em uma string binária. HEX2DEC faz o caminho inverso. IMSUM soma dois números complexos. Cada uma parece um mero exercício de formatação. Mas não são. Por trás desses nomes, há uma codificação de complemento de dois de dez bits que a maioria dos desenvolvedores não vê desde as aulas de arquitetura de computadores, um formato de número complexo que vive inteiramente dentro de strings e operadores bit a bit que estouram silenciosamente um inteiro de 64 bits se você fizer o deslocamento antes de verificar. Um motor de planilha que reproduz o Excel exatamente não pode ignorar nenhum desses detalhes.
As funções se dividem em três grupos, e cada grupo esconde uma armadilha diferente. A conversão de base envolve números negativos e limites por base. A aritmética complexa trata da análise e formatação de uma string. As operações bit a bit consistem em permanecer dentro dos limites de Int64. Este artigo percorre cada grupo conforme o HotXLS o implementa, com as chamadas de planilha que você realmente escreveria.
Conversão de base e o complemento de dois de dez bits
A direção direta é a parte que todos esperam. DEC2BIN(9) retorna "1001", e um segundo argumento opcional preenche o resultado com zeros à esquerda até uma largura fixa. A armadilha está na entrada negativa. O Excel não escreve um sinal de menos. Ele codifica o valor como uma string de complemento de dois de dez dígitos na base de destino, e é por isso que DEC2BIN(-5,10) retorna "1111111011" em vez de algo com um sinal. O argumento de posições é ignorado quando o valor é negativo, pois a codificação já está fixada em dez dígitos.
Dez dígitos é um orçamento fixo, e esse orçamento define o intervalo representável por base. Em binário, a magnitude que entra na metade negativa é 512, e o módulo de contorno é 1024, de modo que uma string binária tem sinal apenas quando tem exatamente dez caracteres de comprimento e seu valor é de pelo menos 512. A mesma ideia se aplica às outras bases. O octal usa um limite intermediário de 2^29 e um módulo completo de 2^30. O hexadecimal usa 2^39 e 2^40. O leitor do HotXLS aplica exatamente essa regra: ele acumula os dígitos e, somente quando a string tem dez caracteres de largura e o valor acumulado está no limite intermediário ou acima dele, ele subtrai o módulo completo para recuperar o valor com sinal. Uma string de nove caracteres é sempre não negativa, não importa o quão grande seja.
O codificador é a imagem espelhada. Um valor não negativo é convertido dígito por dígito e opcionalmente preenchido com zeros até a largura solicitada, sendo rejeitado se ultrapassar o limite positivo da base ou se a largura solicitada for muito estreita para contê-lo. Um valor negativo é primeiro trazido para o intervalo adicionando o módulo completo, o que o transforma em um valor cuja representação na base sempre tem dez dígitos, e então os dígitos são emitidos com zeros à esquerda para preencher a largura. A verificação de intervalo única e compartilhada, com limites inferiores e superiores simétricos por base, é o que mantém DEC2BIN, DEC2OCT e DEC2HEX consistentes entre si em seus limites.
Isso nos deixa com as conversões entre bases, como HEX2BIN e OCT2HEX, que mudam de base sem passar pelo decimal no nome da função. A implementação não carrega uma rotina separada para cada par ordenado. Ela analisa a string de entrada em um valor decimal com sinal usando a base de origem e, em seguida, formata esse valor decimal na base de destino. O decimal é o pivô. Uma rotina de análise e uma rotina de formatação, combinadas, cobrem todas as combinações e, como ambas as metades compartilham a mesma convenção com sinal de dez dígitos, um valor negativo sobrevive à viagem com seu sinal intacto.
Números complexos são strings, então o trabalho é a análise
O Excel não possui um tipo de dados complexo. Um valor complexo é a string "a+bi", e cada função da família IM recebe essas strings e retorna uma delas. A função COMPLEX constrói a string a partir de uma parte real e uma imaginária. As funções IMSUM, IMSUB, IMPRODUCT e IMDIV analisam seus argumentos, realizam a aritmética nas partes numéricas e formatam o resultado de volta em uma string. O trabalho numérico é álgebra básica. A dificuldade está inteiramente em transformar o texto em dois números de ponto flutuante de forma confiável, e é aí que o analisador interno mostra seu valor.
Dois detalhes nesse analisador são fáceis de errar. O primeiro é a unidade imaginária simples. A string "i" significa uma vez i, não zero e não um erro, portanto, quando o coeficiente na frente do sufixo está vazio ou é apenas um sinal de mais, o analisador deve lê-lo como o valor 1, e um sinal de menos isolado como -1. Pule isso e IMSUM("i","i") deixa de ser 2i. O segundo é a notação científica colidindo com o sinal que separa as partes real e imaginária. O analisador encontra esse separador procurando por um mais ou menos, mas um número escrito como "1.5E-3" contém um menos que pertence ao expoente. Portanto, a varredura se recusa a tratar um mais ou menos como o separador quando o caractere imediatamente anterior for e ou E. Sem essa proteção, a parte real seria dividida ao meio no sinal do expoente e a análise falharia em uma entrada perfeitamente válida.
O próprio sufixo é preservado em vez de ser normalizado. O Excel aceita tanto i quanto j, e o HotXLS lembra qual deles a entrada usou para que o resultado formatado carregue a mesma letra. A formatação então aplica as abreviações convencionais: uma parte imaginária de um é impressa apenas como o sufixo, menos um como -i, uma parte imaginária zero se reduz a um real simples, e uma parte real zero descarta o 0+ inicial.
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 elas IMSQRT, IMEXP, IMLN e IMPOWER, não funcionam em coordenadas retangulares. Elas convertem o valor analisado para a forma polar, aplicam a operação no módulo e argumento, e convertem de volta. Uma raiz quadrada divide o argumento pela metade e extrai a raiz do módulo. Uma potência multiplica o argumento e eleva o módulo. Fazer de qualquer outra forma significaria recalcular cada identidade na forma retangular, o que exige mais código e é menos estável numericamente perto dos cortes de ramificação.
Operadores bit a bit e o estouro que você deve verificar primeiro
O Excel 2013 adicionou BITAND, BITOR, BITXOR, BITLSHIFT e BITRSHIFT. Os operandos são limitados: cada um deve ser um inteiro não negativo não maior que 2^48 menos 1, e qualquer argumento fracionário ou negativo é um erro numérico. Esse limite é generoso o suficiente para cobrir qualquer conjunto de flags realista, permanecendo bem dentro do intervalo exatamente representável de um double, o que é importante porque o Excel passa cada argumento numérico como um valor de ponto flutuante.
As funções de deslocamento carregam a única regra de ordenação que realmente incomoda. Um deslocamento para a esquerda pode produzir um valor muito maior do que sua entrada e, se você realizar o shl primeiro e inspecionar o resultado depois, já terá estourado o Int64 e o teste será inútil. A verificação deve vir antes do deslocamento. O HotXLS compara o operando contra o limite máximo deslocado para a direita pela quantidade de deslocamento e, somente se o operando couber, realiza o deslocamento para a esquerda real. Uma magnitude de deslocamento além de 53 bits é rejeitada imediatamente, e um deslocamento negativo simplesmente inverte a direção, de modo que BITLSHIFT com uma contagem negativa se comporta como um deslocamento para a direita. O princípio se generaliza muito além desta função: quando existe uma proteção para evitar estouro, ela deve ser executada nas entradas, nunca no resultado que deveria 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 longa lista de outras adições pós-2007 interagem com um esquema de nomenclatura que não tem nada a ver com o que calculam e tudo a ver com a forma como o Excel os armazena. O formato de planilha binário original atribuía a cada função integrada um slot numérico em uma tabela fixa. As funções inventadas após o congelamento dessa tabela não possuem slot. Para salvar tal função em um arquivo e fazer com que um Excel moderno a reconheça, o nome é gravado com um prefixo _xlfn., de modo que BITAND é armazenado como _xlfn.BITAND no disco, embora o usuário digite apenas BITAND.
O detalhe é que a regra não é uniforme. Algumas funções mais novas receberam slots de tabela e são gravadas sem prefixo, enquanto algumas funções ocultas herdadas também são gravadas sem prefixo, apesar de sua idade. O HotXLS mantém uma lista de permissões explícita de quais nomes precisam do prefixo, adicionando-o na gravação e removendo-o na leitura, de modo que o texto da fórmula que você define e lê de volta é sempre o nome limpo voltado ao Excel. Você define =BITLSHIFT(5,2), o arquivo armazena _xlfn.BITLSHIFT e o valor retorna como 20 de qualquer maneira. O prefixo é um detalhe de armazenamento que nunca deve vazar para as fórmulas com as quais você trabalha no código.
Juntando tudo em uma planilha
A superfície pública para tudo isso é pequena. Crie um TXLSXWorkbook, adicione uma planilha e grave uma fórmula em uma célula por meio de Cells[Row, Col].Formula e recalcule, ou avalie uma expressão diretamente com o método Calculate da planilha, que compila a fórmula contra aquela planilha e retorna um Variant. Os exemplos acima usam Calculate porque ele mostra o resultado de uma única chamada de engenharia sem o estado da planilha ao redor, mas as mesmas funções são avaliadas de forma idêntica dentro de fórmulas de células reais quando a pasta de trabalho é recalculada.
As codificações são a parte que se deve ter em mente, não os locais de chamada. Uma string binária tem sinal apenas com dez dígitos e somente após o limite intermediário de sua base. Um número complexo é texto, um coeficiente imaginário vazio é um, e o analisador ignora o e de um expoente. Um deslocamento para a esquerda é verificado antes de ser executado. Acerte esses quatro fatos e a família de engenharia deixará de ser uma fonte de surpresas de inversão de sinal.
Se você estiver integrando sua própria matemática de domínio no mesmo motor, os mecanismos de registro de um manipulador e retorno de valores são abordados em nosso artigo sobre como estender o motor de fórmulas com funções personalizadas, e quando essas fórmulas precisam alcançar outras planilhas por nome em vez de endereço de célula, o passo a passo sobre nomes definidos e fórmulas entre planilhas mostra como as referências são resolvidas. As funções de engenharia descritas aqui são enviadas como parte do componente de planilha HotXLS para Delphi e C++Builder, junto com as APIs de leitura, gravação e cálculo abordadas em outras partes deste blog.