La familia de ingeniería en Excel se lee como el rincón más fácil de la referencia de funciones. DEC2BIN convierte un número en una cadena binaria. HEX2DEC lo convierte de vuelta. IMSUM suma dos números complejos. Cada una parece un ejercicio de formato. No lo son. Detrás de estos nombres se encuentra una codificación en complemento a dos de diez bits que la mayoría de los desarrolladores no han tocado desde la clase de arquitectura de computadoras, un formato de número complejo que vive completamente dentro de cadenas, y operadores bit a bit que desbordarán silenciosamente un entero de 64 bits si realiza el desplazamiento antes de verificarlo. Un motor de hoja de cálculo que reproduzca Excel exactamente no puede simplificar nada de esto.
Las funciones se dividen en tres grupos, y cada grupo esconde una trampa diferente. La conversión de base se trata de números negativos y umbrales por base. La aritmética compleja se trata de analizar y dar formato a una cadena. Las operaciones bit a bit se tratan de mantenerse dentro de los límites de Int64. Este artículo recorre cada grupo tal como lo implementa HotXLS, con las llamadas de hoja de cálculo que escribiría realmente.
Conversión de base y el complemento a dos de diez bits
La dirección directa es la parte que todos esperan. DEC2BIN(9) da "1001", y un segundo argumento opcional rellena el resultado a la izquierda a un ancho fijo. La trampa es la entrada negativa. Excel no escribe un signo menos. Codifica el valor como una cadena de complemento a dos de diez dígitos en la base de destino, razón por la cual DEC2BIN(-5,10) devuelve "1111111011" en lugar de algo con signo. El argumento de posiciones se ignora una vez que el valor es negativo, porque la codificación ya está fijada en diez dígitos.
Diez dígitos es un presupuesto fijo, y ese presupuesto establece el rango representable por base. En binario, la magnitud que pasa a la mitad negativa es 512, y el módulo de envoltura es 1024, por lo que una cadena binaria tiene signo solo cuando tiene exactamente diez caracteres de longitud y su valor es de al menos 512. La misma idea se escala con la base. El octal utiliza un umbral medio de 2^29 y un módulo completo de 2^30. El hexadecimal utiliza 2^39 y 2^40. El lector HotXLS aplica exactamente esta regla: acumula los dígitos, y solo cuando la cadena tiene diez caracteres de ancho y el valor acumulado se sitúa en o por encima del umbral medio, resta el módulo completo para recuperar el valor con signo. Una cadena de nueve caracteres siempre es no negativa, sin importar lo grande que sea.
El codificador es la imagen especular. Un valor no negativo se convierte dígito por dígito y, opcionalmente, se rellena con ceros hasta el ancho solicitado, y se rechaza si se desborda el límite positivo de la base o si el ancho solicitado es demasiado estrecho para contenerlo. Un valor negativo se lleva primero al rango sumando el módulo completo, lo que lo convierte en un valor cuya representación en base es siempre de diez dígitos, y luego se emiten los dígitos con ceros a la izquierda para llenar el ancho. La única verificación de rango compartida, los límites inferior y superior simétricos por base, es lo que mantiene a DEC2BIN, DEC2OCT y DEC2HEX consistentes entre sí en sus extremos.
Eso deja las conversiones entre bases cruzadas, aquellas como HEX2BIN y OCT2HEX que cambian de base sin pasar por decimal en el nombre de la función. La implementación no lleva una rutina separada para cada par ordenado. Analiza la cadena de entrada en un valor decimal con signo utilizando la base de origen, y luego da formato a ese valor decimal en la base de destino. El decimal es el pivote. Una rutina de análisis y una de formato, compuestas, cubren cada combinación y, debido a que ambas mitades comparten la misma convención con signo de diez dígitos, un valor negativo sobrevive al viaje con su signo intacto.
Los números complejos son cadenas, por lo que el trabajo es analizarlos
Excel no tiene un tipo de datos complejo. Un valor complejo es la cadena "a+bi", y cada función de la familia IM toma esas cadenas de entrada y devuelve una. COMPLEX construye la cadena a partir de una parte real y una imaginaria. IMSUM, IMSUB, IMPRODUCT e IMDIV analizan sus argumentos, realizan la aritmética en las partes numéricas y dan formato al resultado de vuelta en una cadena. El trabajo numérico es álgebra básica. La dificultad radica completamente en convertir el texto en dos números de punto flotante de manera confiable, y ahí es donde el analizador interno demuestra su valor.
Dos detalles en ese analizador son fáciles de equivocar. El primero es la unidad imaginaria simple. La cadena "i" significa uno por i, no cero y no un error, so cuando el coeficiente frente al sufijo está vacío o es un signo más solo, el analizador tiene que leerlo como el valor 1, y un menos solo como -1. Omita eso y IMSUM("i","i") deja de ser 2i. El segundo es la notación científica que colisiona con el signo que separa las partes real e imaginaria. El analizador encuentra ese separador buscando un más o un menos, pero un número escrito como "1.5E-3" contiene un menos que pertenece al exponente. Por lo tanto, el escaneo se niega a tratar un más o un menos como el separador cuando el carácter inmediatamente anterior es e o E. Sin esa protección, la parte real se dividiría a la mitad en el signo del exponente y el análisis fallaría en una entrada perfectamente válida.
El sufijo mismo se conserva en lugar de normalizarse. Excel acepta tanto i como j, y HotXLS recuerda cuál utilizó la entrada para que el resultado con formato lleve la misma letra. El formateo aplica luego las abreviaturas convencionales: una parte imaginaria de uno se imprime solo como el sufijo, menos uno como -i, una parte imaginaria de cero se reduce a un real simple y una parte real de cero omite el 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;
Las funciones complejas trascendentes, IMSQRT, IMEXP, IMLN e IMPOWER entre ellas, no funcionan en coordenadas rectangulares. Convierten el valor analizado a forma polar, aplican la operación sobre el módulo y el argumento, y vuelven a convertir. Una raíz cuadrada reduce a la mitad el argumento y toma la raíz del módulo. Una potencia multiplica el argumento y eleva el módulo. Hacerlo de cualquier otra manera significaría volver a derivar cada identidad en forma rectangular, lo que representa más código y menos estabilidad numérica cerca de los cortes de rama.
Operadores bit a bit y el desbordamiento que debe verificar primero
Excel 2013 añadió BITAND, BITOR, BITXOR, BITLSHIFT y BITRSHIFT. Los operandos están restringidos: cada uno debe ser un entero no negativo no mayor que 2^48 menos 1, y cualquier argumento fraccionario o negativo es un error numérico. Ese límite es lo suficientemente generoso como para cubrir cualquier conjunto de banderas realista y al mismo tiempo mantenerse dentro del rango representable exactamente de un double, lo cual importa porque Excel pasa cada argumento numérico como un valor de punto flotante.
Las funciones de desplazamiento llevan la única regla de ordenación que realmente causa problemas. Un desplazamiento a la izquierda puede producir un valor mucho mayor que su entrada, y si realiza el shl primero e inspecciona el resultado después, ya habrá desbordado Int64 y la prueba no tendrá sentido. La verificación debe venir antes del desplazamiento. HotXLS compara el operando con el límite máximo de 2^48 desplazado a la derecha por la cantidad de desplazamiento, y solo si el operando encaja, realiza el desplazamiento a la izquierda real. Un desplazamiento superior a 53 bits se rechaza por completo, y un desplazamiento negativo simplemente invierte la dirección, por lo que BITLSHIFT con un recuento negativo se comporta como un desplazamiento a la derecha. El principio se generaliza mucho más allá de esta única función: cuando existe una protección para evitar el desbordamiento, debe ejecutarse en las entradas, nunca en el resultado que debía 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
Funciones futuras y el prefijo de nombre _xlfn
Los operadores bit a bit y una larga lista de otras adiciones posteriores a 2007 interactúan con un esquema de nombres que no tiene nada que ver con lo que calculan y sí con cómo los almacena Excel. El formato original de hoja de cálculo binaria asignaba a cada función integrada una ranura numérica en una tabla fija. Las funciones inventadas después de que esa tabla se congelara no tienen ranura. Para guardar dicha función en un archivo y hacer que un Excel moderno la reconozca, el nombre se escribe con un prefijo _xlfn., de modo que BITAND se almacena como _xlfn.BITAND en el disco aunque el usuario solo escriba BITAND.
La trampa es que la regla no es uniforme. Algunas funciones más nuevas se les asignaron ranuras de tabla y se escriben directamente, mientras que unas pocas funciones ocultas heredadas también se escriben sin prefijo a pesar de su antigüedad. HotXLS mantiene una lista blanca explícita de qué nombres necesitan el prefijo, lo añade al escribir y lo elimina al leer, por lo que el texto de la fórmula que establece y lee es siempre el nombre limpio orientado a Excel. Establece =BITLSHIFT(5,2), el archivo contiene _xlfn.BITLSHIFT y el valor vuelve como 20 de todos modos. El prefijo es un detalle de almacenamiento que nunca debería filtrarse en las fórmulas con las que trabaja en el código.
Unirlo todo en una hoja de cálculo
La superficie pública para todo esto es pequeña. Crea un TXLSXWorkbook, añade una hoja de cálculo y escribe una fórmula en una celda a través de Cells[Row, Col].Formula y recalcula, o evalúa una expresión directamente con el método Calculate de la hoja de cálculo, que compila la fórmula contra esa hoja y devuelve un Variant. Los ejemplos anteriores utilizan Calculate porque muestra el resultado de una sola llamada de ingeniería sin el estado de la hoja circundante, pero las mismas funciones se evalúan de manera idéntica dentro de fórmulas de celdas reales cuando la hoja de cálculo recalcula.
Las codificaciones son la parte a tener en cuenta, no los puntos de llamada. Una cadena binaria tiene signo solo en diez dígitos y solo después del umbral medio para su base. Un número complejo es texto, un coeficiente imaginario vacío es uno y el analizador pasa por encima de la e de un exponente. Se comprueba un desplazamiento a la izquierda antes de realizar el desplazamiento. Entienda bien esos cuatro hechos y la familia de ingeniería dejará de ser una fuente de sorpresas por diferencias de signo.
Si está conectando sus propias matemáticas de dominio en el mismo motor, la mecánica de registrar un controlador y devolver valores se cubre en nuestro artículo sobre la extensión del motor de fórmulas con funciones personalizadas, y cuando esas fórmulas tienen que llegar a través de hojas por nombre en lugar de por dirección de celda, el tutorial sobre nombres definidos y fórmulas entre hojas muestra cómo se resuelven las referencias. Las funciones de ingeniería descritas aquí se distribuyen como parte del componente de hoja de cálculo HotXLS para Delphi y C++Builder, junto con las API de lectura, escritura y cálculo cubiertas en otras partes de este blog.