El sitio web de un carca
No es sitio de moda. Sólo un 'dinosaurio' jugando.
Fraccionario-exponencial matemáticas de enteros

Fraccionario-exponencial matemáticas de enteros (fx) es una técnica de programación para almacenar y calcular los números fraccionarios y exponenciales sin las inexactitudes inherentes a los números de punto flotante (Vea nota de "Inexactitudes de punto flotante" al derecho). Puede ser preciso porque todos los valores numéricos se almacenan internamente como enteros. Esta página examina el uso de la técnica fx en C.

Inexactitudes de punto flotante

2299.500 * 1837.500 = 4225331.000

¿Puede usted ver algo erróneo en esta ecuación? Pista: el producto de la multiplicación de dos números fraccionarios ambos de los cuales terminan en .5 debe también ser fraccionario, terminando con .25 o .75; el producto no puede ser un número entero. (En este caso, el producto de 2299.5 por 1837.5 es 4225331.25) La ecuación inexacta anterior fue generada por la computadora Dell vieja y confiable de mi casa. Específicamente, es la salida de este código C compilado con gcc 3.3.5:

float a, b, x;
a = 2299.5;
b = 1837.5;
x = a * b;
printf("%.3f * %.3f = %.3f\n",a,b,x);

El problema no está en el equipo ni el código ni el compilador, sino en la naturaleza de los números de punto flotante. Es cierto que se podría corregir el ejemplo específico anterior usando el tipo double, o usar una implementación diferente del tipo float. Pero en algún momento de algún otro tipo de cálculo, las limitaciones internas de aproximaciones de abscisas-mantisa del punto flotante resultaría en unas inexactitudes similares, o peores. ¿Quiere un ejemplo de peor? Mire esto:

0.0 ^ 0.0 = 1.0

En la lógica de números de punto flotante. de cualquier aplicación, de cualquier precisión, ¡cero elevado a la potencia de cero es igual a uno! ¿Cómo puede será esto? Debido a las aproximaciones de abscisas-mantisa, en las que números de punto flotante son raramente o nunca absolutamente precisos. Cero de punto flotante no es realmente cero, sino una aproximación cercana a cero. Bueno, se puede decir que esto es de fiar. Es matemáticamente correcto decir que una aproximación de cero elevada a la potencia de una aproximación de cero será igual a una aproximación de 1 (uno). Sin embargo, ¿se puede encontrar un matemático o ingeniero honesto que aceptaría el cálculo de punto flotante arriba? Este ingeniero digo que eso no es buena lógica.

Mi aplicación de fx utiliza estas dos estructuras en C:

#define IntSize long long

struct frac
    {
    IntSize num;
    IntSize den;
    };

struct fx
    {
    struct frac base;
    struct frac exp;
    } xx;

(IntSize está aquí igual de tipo long long, dándole precisión de 64 bits en la mayoría de las máquinas de destino. Su definición podría ser alterada para lograr una mayor precisión o más portátil, dependiendo de las necesidades del desarrollador.)

Todos los números fx tienen la forma struct fx, es decir, un número base fraccionario (struct frac) con un exponente fraccionario. Los 4 elementos son enteros IntSize. Donde el fx su mismo es un número entero, todos menos el xx.base.num son 1 (uno). Por ejemplo, el número entero 142 tendría la forma

xx.base.num = 142;
xx.base.den = 1;
xx.exp.num = 1;
xx.exp.den = 1;

Que es

(142 / 1) ^ (1 / 1)

La cantidad 142 dividido por 1, elevada a la potencia de la cantidad de 1 divido por 1

Fracciones

Un ejemplo de un valor fraccionario puede ser

(1 / 3) ^ (1 / 1)

No hay decimal repitiendo .3333 y no hay riesgo de errores de redondeo. De veras, no hay intento de realizar la operación de división. La fracción se almacena sencillamente como una fracción de numerador / denominador, todos los elementos todavía son enteros IntSize .

Para números decimales, el número fx empieza a asemejarse al concepto del punto decimal implícito de Cobol. xx.base.den es siempre algúna potencia de diez, dependiendo de cuantos decimales implícitos hay. En una applicación de negocio donde hay campos de dólares y centavos, el denominador sería 100. Por ejemplo, la cantidad de $547.95 sería

(54795 / 100) ^ (1 / 1)

Si IntSize es un número entero de 64 bits, el número fx puede representar un número decimal con 18 dígitos significativos (como en Cobol), antes o despues del punto decimal implícito. Esto se traduce en campos de dólares y centavos capaz para mil millones de millones de dólares sin nunca perder un solo centavo a errores de redondear o inexactitudes de punto flotante. Eso es (¿casi?) suficiente para llevar la cuenta entera de la deuda nacional de EEUU, hasta el último centavo. Si el desarrollador necesita más capacidad, puede definir IntSize con 128 bits, o según la capacidad de la máquina de destino.

Exponentes

La segunda mitad del número fx es el exponente, la potencia a la que la fracción de la base está elevada. Es también un número fraccionario, a fin de que potencias fraccionarias (raíces) pueden ser representadas. Entonces, por ejemplo, trece elevado al cubo sería

xx.base.num = 13;
xx.base.den = 1;
xx.exp.num = 3;
xx.exp.den = 1;

Que es

(13 / 1) ^ (3 / 1)

La raíz cuadrada de .7 sería

(7 / 10) ^ (1 / 2)

Así como las fracciones de la base, no hay intento de realizar la operación de exponenciación. El exponente se almacena sencillamente como dos enteros IntSize.

Números Imaginarios

Una característica extraordinaria de la técnica fx es la habilidad a tratar con números imaginarios, es decir, los números basado en i, la raíz cuadrada de uno negativo.

(-1 / 1) ^ (1 / 2)

El número fx a la izquierda se podría definir con respecto a i  como el número complejo
((5^(1/2))*i)^(7/2)
pero para hacer algo con esto en C requeriría una extensión a la biblioteca matemática convencional de C, y aún así conllevaría las matemáticas de punto flotante con sus inexactitudes inherentes. (Además, yo no sabría escribir el código C.)
Usted lo aprendió en la secundaria. Cualquier número negativo con raíz par es un número imaginario. Este número fx, por ejemplo:

(-5 / 1) ^ (7 / 4)

está imposible de resolver dentro el dominio de números reales. Pero las normas de matemáticas aún aplican. Por ejemplo, se pueden multiplicar dos números imaginarios:

  (-5 / 1) ^ (7 / 4)
* (-5 / 1) ^ (5 / 4)
 -------------------
  (-5 / 1) ^ (12/ 4)

El producto, en esto caso, ya no tiene raíz par (porque se puede simplificar el exponente al entero 3), y por eso se puede resolver como el número real -125.

El punto es, la técnica fx puede hacer con facilitad el cálculo arriba y otros que consisten en números imaginarios. Yo no sabría hacerlo usando números de punto flotante u otro método convencional de programación.

Limitaciones

La primera limitación es que los números trascendentales como pi, logaritmos, y funciones trigonometricas no pueden ser representados por números fx.

Segundamente, como ha notado arriba, la capacidad máxima del número fx está limitada por la definición de su elemento IntSize. Algunos cálculos pueden dar resultas fuera de esta capacidad. Cuando ocurre esto, el cálculo fx fallará claramente en vez de dar respuestas inexactas.

Pues bien, usted puede tener la certeza que su número fx compuesto de 4 partes es exacto. Pero en algún momento usted quisiera que su aplicación resuelva el número compuesto fx a un solo número entero o una fracción sencilla o número con punto decimal fijado. En varios casos, esto será imposible sin revertiendo a matemáticas de punto flotante o de precisón arbitraria, y la posibilidad de errores de redondeo e inexactitudes. El desarrollador astuto minimizará esto por hacer todos los cálculos y almacenar todos los valores interines como números fx, esperando hasta, por ejemplo, imprimir un informe, para resolver el número.

Implementación

Las estructuras, definiciones, y funciones C son incluidos en el archivo 'fx.h', el cual usted puede descargar y incluir en su propio programa C.

Tal vez un día voy a escribir una página para exponer en detalle las varias funciones fx para operaciones de matemáticas, etc. Mientras tanto, mire el código y los comentarios (en inglés) en el antedicho archivo 'fx.h'.

El archivo 'fx.h' fue modificado: 29 Ago 2019 20:13:09 UTC
   rev. 2019.08.13