La familia de funciones de ingeniería en Excel parece el rincón más sencillo de la referencia de funciones. DEC2BIN convierte un número en una cadena binaria. HEX2DEC realiza el proceso inverso. IMSUM suma dos números complejos. Cada una parece un simple ejercicio de formato, pero no lo es. Detrás de estos nombres se esconde una codificación de complemento a dos de diez bits que la mayoría de los desarrolladores no ha tocado desde las clases de arquitectura de computadoras, un formato de números complejos 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 a Excel con precisión no puede omitir ninguno de estos detalles.
Las funciones se dividen en tres grupos, y cada uno oculta una trampa diferente. La conversión de base se centra en números negativos y umbrales por base. La aritmética compleja consiste en 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 usted escribiría en la práctica.
Conversión de base y el complemento a dos de diez bits
El camino directo es la sección que todos esperan. DEC2BIN(9) devuelve \"1001\", y un segundo argumento opcional rellena el resultado con ceros a la izquierda hasta 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 usar un 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 representan 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, so una cadena binaria tiene signo solo cuando tiene exactamente diez caracteres de longitud y su valor es de al menos 512. La misma lógica se aplica a las demás bases. El sistema 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 de HotXLS aplica exactamente esta regla: acumula los dígitos, y solo cuando la cadena tiene diez caracteres de ancho y el valor acumulado está en o por encima del umbral medio, resta el módulo completo para recuperar el valor con signo. Una cadena de nueve caracteres siempre se considera no negativa, sin importar su tamaño.
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 desborda el límite positivo de la base o si el ancho solicitado es demasiado estrecho para contenerlo. Un valor negativo se introduce primero en el rango sumando el módulo completo, lo que lo convierte en un valor cuya representación en la base es siempre de diez dígitos, y luego se emiten los dígitos con ceros a la izquierda para cubrir el ancho. La comprobación única de rango compartido, los límites inferiores y superiores simétricos por base, es lo que mantiene a DEC2BIN, DEC2OCT y DEC2HEX consistentes entre sí en sus extremos.
Esto deja de lado las conversiones entre bases, aquellas como HEX2BIN y OCT2HEX que cambian de base sin pasar por el sistema decimal en el nombre de la función. La implementación no incluye una rutina separada para cada par ordenado; analiza la cadena de entrada para obtener un valor decimal con signo utilizando la base de origen, y luego da formato a ese valor decimal en la base de destino. El sistema decimal es el pivote. Una rutina de análisis y una rutina de formato, combinadas, cubren cada combinación, y dado que ambas mitades comparten la misma convención de diez dígitos con signo, un valor negativo sobrevive al proceso con su signo intacto.
Los números complejos son cadenas, por lo que el trabajo es el análisis
Excel no tiene un tipo de datos complejo. Un valor complejo es la cadena \"a+bi\", y cada función en la familia IM recibe esas cadenas 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 a 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 tratar de forma incorrecta. El primero es la unidad imaginaria sola. La cadena \"i\" significa una vez i, no cero y no representa un error, por lo que cuando el coeficiente delante del sufijo está vacío o es un signo más solitario, el analizador debe leerlo como el valor 1, y un signo menos solitario como -1. De lo contrario, IMSUM(\"i\",\"i\") deja de ser 2i. El segundo es la notación científica chocando con el signo que separa la parte real de la imaginaria. El analizador encuentra ese separador escaneando en busca de un signo más o menos, pero un número escrito como \"1.5E-3\" contiene un menos que pertenece al exponente. Por lo tanto, el escaneo se rehúsa 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 contenga la misma letra. El formateador aplica entonces 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 número 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, entre ellas IMSQRT, IMEXP, IMLN e IMPOWER, no trabajan 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 al sistema rectangular. 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. Proceder de cualquier otra manera implicaría volver a deducir cada identidad en forma rectangular, lo cual representa más código y menor estabilidad numérica cerca de los puntos de ramificación.
Operadores bit a bit y el desbordamiento que debe verificar primero
Excel 2013 incorporó 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 representa un error numérico. Ese límite es lo suficientemente amplio como para cubrir cualquier conjunto de banderas real y al mismo tiempo mantenerse dentro del rango representable con exactitud de un double, lo cual es importante porque Excel transfiere cada argumento numérico como un valor de punto flotante.
Las funciones de desplazamiento contienen la única regla de orden que realmente da problemas. Un desplazamiento a la izquierda puede producir un valor mucho mayor que su entrada, y si realiza primero la instrucción shl e inspecciona el resultado después, ya habrá desbordado el límite de Int64 y la prueba no tendrá sentido. La verificación debe realizarse antes del desplazamiento. HotXLS compara el operando contra el límite máximo desplazado a la derecha según la cantidad de desplazamiento, y solo si el operando cabe, realiza el desplazamiento a la izquierda real. Un desplazamiento de magnitud superior a 53 bits se rechaza de inmediato, y un desplazamiento negativo simplemente invierte la dirección, de modo que BITLSHIFT con una cantidad negativa se comporta como un desplazamiento a la derecha. El principio se generaliza mucho más allá de esta 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 incorporaciones posteriores a 2007 interactúan con un esquema de nomenclatura que no tiene nada que ver con lo que calculan y todo que ver con cómo los almacena Excel. El formato binario original de las hojas de trabajo asignaba a cada función integrada una posición numérica en una tabla fija. Las funciones inventadas después de que esa tabla se congelara no tienen posición. Para guardar tal función en un archivo y lograr que un Excel moderno la reconozca, el nombre se escribe con el prefijo _xlfn., de modo que BITAND se almacena como _xlfn.BITAND en el disco, aunque el usuario solo escriba BITAND.
La particularidad es que la regla no es uniforme. Algunas funciones más nuevas recibieron posiciones en la tabla y se escriben sin prefijo, mientras que unas pocas funciones ocultas heredadas también se escriben sin prefijo a pesar de su antigüedad. HotXLS mantiene una lista explícita de qué nombres necesitan el prefijo, lo agrega al escribir y lo elimina al leer, de modo que el texto de la fórmula que usted define y lee de vuelta es siempre el nombre limpio visible en Excel. Si define =BITLSHIFT(5,2), el archivo contiene _xlfn.BITLSHIFT y el valor se devuelve como 20 de todos modos. El prefijo es un detalle de almacenamiento que nunca debería filtrarse a las fórmulas con las que trabaja en su código.
Reunirlo en una hoja de trabajo
La superficie pública de todo esto es pequeña. Cree un TXLSXWorkbook, agregue una hoja de trabajo y escriba una fórmula en una celda a través de Cells[Row, Col].Formula y recalcule, o evalúe una expresión directamente con el método Calculate de la hoja de trabajo, que compila la fórmula contra esa hoja y devuelve un Variant. Los ejemplos anteriores utilizan Calculate porque muestra el resultado de una llamada de ingeniería individual sin el estado de la hoja circundante, pero las mismas funciones se evalúan de forma idéntica dentro de fórmulas de celda reales cuando el libro de trabajo se recalcula.
Las codificaciones son los aspectos que se deben tener en cuenta, no los puntos de llamada. Una cadena binaria tiene signo solo cuando alcanza los diez dígitos y solo cuando supera el umbral medio para su base. Un número complejo es texto, un coeficiente imaginario vacío equivale a uno, y el analizador pasa por alto la letra e del exponente. A la izquierda se verifica un desplazamiento antes de desplazarse. Entienda bien estos cuatro hechos y la familia de ingeniería dejará de ser una fuente de sorpresas causadas por errores de signo.
Si está integrando su propia matemática de dominio en el mismo motor, los mecanismos para registrar un controlador y devolver valores se cubren en nuestro artículo sobre la extensión del motor de fórmulas con funciones personalizadas, y cuando esas fórmulas deben hacer referencia a otras hojas por su nombre en lugar de por su dirección de celda, la guía práctica 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 tratadas en otras secciones de este blog.