Almacena datos numéricos de precisión arbitraria

Spanner proporciona el tipo NUMERIC que puede almacenar números de precisión decimal exactamente. La semántica del tipo NUMERIC en Spanner varía entre sus dos SQL dialectos (GoogleSQL y PostgreSQL), en especial en torno al Límites de escala y precisión:

  • NUMERIC en el dialecto de PostgreSQL es un tipo numérico de precisión decimal arbitraria (la escala o la precisión puede ser cualquier número dentro del rango admitido) y, por lo tanto, es una opción ideal para almacenar datos numéricos de precisión arbitraria.

  • NUMERIC en GoogleSQL es un tipo de datos numérico con precisión fija (precisión = 38 y escala = 9) y no se puede usar para almacenar datos numéricos con precisión arbitraria. Cuando necesites almacenar números de precisión arbitrarios en bases de datos de dialecto de GoogleSQL, recomendamos que los guardes como cadenas.

Precisión de los tipos numéricos de Spanner

La precisión es el número de dígitos que hay en un número. La escala es la cantidad de dígitos a la derecha del punto decimal de un número. Por ejemplo, el número 123.456 tiene una precisión de 6 y una escala de 3. Spanner tiene tres tipos numéricos:

  • Tipo de número entero firmado de 64 bits llamado INT64 en el dialecto GoogleSQL y INT8 en el dialecto PostgreSQL.
  • Es un tipo de número de punto flotante de precisión binaria de 64 bits (doble) de IEEE, llamado FLOAT64 en el dialecto de GoogleSQL y FLOAT8 en el dialecto de PostgreSQL.
  • Tipo NUMERIC de precisión decimal.

Analicemos cada uno en términos de precisión y escalamiento.

INT64/INT8 representa valores numéricos que no tienen un componente fraccionario. Este tipo de datos proporciona 18 dígitos de precisión, con una escala de cero.

FLOAT64 / FLOAT8 solo puede representar valores numéricos decimales aproximados con componentes fraccionales y proporciona Entre 15 y 17 dígitos significativos (recuento de dígitos en un número con todos ceros finales) de precisión decimal. Decimos que este tipo representa valores numéricos decimales aproximados porque la representación binaria de punto flotante IEEE de 64 bits que usa Spanner no puede representar con precisión las fracciones decimales (base 10) (solo puede representar fracciones de base 2 con exactitud). Esta pérdida de precisión genera errores de redondeo para algunas fracciones decimales.

Por ejemplo, cuando almacenas el valor decimal 0.2 con el tipo de datos FLOAT64/FLOAT8, la representación binaria vuelve a un valor decimal de 0.20000000000000001 (con 18 dígitos de precisión). De manera similar, (1.4 * 165) vuelve a ser 230.999999999999971 y (0.1 + 0.2) vuelve a ser 0.30000000000000004. Por eso, los números de punto flotante de 64 bits se describen como que solo tienen 15 dígitos significativos de precisión (solo algunos números con más de 15 decimales los dígitos se pueden representar como un número de punto flotante de 64 bits sin redondeo). Para obtener más detalles sobre cómo se calcula la precisión de punto flotante, consulta Formato de punto flotante de precisión doble.

Ni INT64 / INT8 ni FLOAT64 / FLOAT8 tienen la precisión ideal para cálculos financieros, científicos o de ingeniería, con una precisión de 30 dígitos o más.

El tipo de datos NUMERIC es adecuado para esas aplicaciones, ya que puede de representar valores numéricos de precisión decimal exacta con una precisión de más con más de 30 dígitos decimales.

Los datos de GoogleSQL NUMERIC el tipo puede representar números con una precisión decimal fija de 38 y una escala fija de 9. El rango de NUMERIC de GoogleSQL es -999999999999999999999999999.999999999 al 9999999999999999999999999999.999999999.

El tipo NUMERIC del dialecto PostgreSQL puede representar números con un con una precisión decimal máxima de 147,455 y una escala máxima de 16,383.

Si necesitas almacenar números que sean más grandes que la precisión y el escalamiento que ofrece NUMERIC, en las siguientes secciones se describen algunas soluciones recomendadas.

Recomendación: almacena números de precisión arbitrarios como strings

Cuando necesitas almacenar un número de precisión arbitrario en un Spanner y necesitas más precisión que la que proporciona NUMERIC, te recomendamos que almacenes el valor como su representación decimal en una STRING / VARCHAR . Por ejemplo, el número 123.4 se almacena como la cadena "123.4".

Con este enfoque, tu aplicación debe realizar una conversión sin pérdidas entre la representación interna de la aplicación del número y STRING / VARCHAR de columna para las operaciones de lectura y escritura de la base de datos.

La mayoría de las bibliotecas de precisión arbitraria tienen métodos integrados para realizar esta conversión sin pérdidas. En Java, por ejemplo, puedes usar el método BigDecimal.toPlainString() y el constructor BigDecimal(String).

Almacenar el número como una cadena tiene la ventaja de que el valor se almacena precisión exacta (hasta el límite de longitud de columna STRING / VARCHAR) y el valor siga siendo legible por humanos.

Realiza agregaciones y cálculos exactos

Para realizar agregaciones y cálculos exactos en representaciones de string de números de precisión arbitrarios, la aplicación debe realizar estos cálculos. No puedes usar las funciones de agregación de SQL.

Por ejemplo, para realizar el equivalente de SUM(value) de SQL sobre un rango de filas, la aplicación debe consultar los valores de string para las filas, y convertirlas y sumarlas internamente en la aplicación.

Realiza agregaciones, ordenaciones y cálculos aproximados

Cuando conviertes valores en FLOAT64/FLOAT8, puedes usar las consultas de SQL para realizar cálculos agregados aproximados:

GoogleSQL

SELECT SUM(CAST(value AS FLOAT64)) FROM my_table

PostgreSQL

SELECT SUM(value::FLOAT8) FROM my_table

Del mismo modo, puedes ordenar por valor numérico o valores límite por rango con conversión:

GoogleSQL

SELECT value FROM my_table ORDER BY CAST(value AS FLOAT64);
SELECT value FROM my_table WHERE CAST(value AS FLOAT64) > 100.0;

PostgreSQL

SELECT value FROM my_table ORDER BY value::FLOAT8;
SELECT value FROM my_table WHERE value::FLOAT8 > 100.0;

Estos cálculos son aproximados a los límites del tipo de datos FLOAT64/FLOAT8.

Alternativas

Existen otras formas de almacenar números de precisión arbitrarios en Spanner. Si el almacenamiento de números de precisión arbitrarios como strings no funciona para tu aplicación, considera las siguientes alternativas:

Almacena valores de número entero escalados por aplicación

Para almacenar números de precisión arbitrarios, puedes escalar previamente los valores antes de escribirlos, de modo que los números siempre se almacenen como números enteros, y volver a escalar los valores después de leerlos. La aplicación almacena un factor de escalado fijo, y la precisión se limita a los 18 dígitos que proporciona el tipo de datos INT64/INT8.

Tomemos, por ejemplo, un número que debe almacenarse con una precisión de 5 decimales. La aplicación convierte el valor en un número entero cuando se multiplica por 100,000 (desplazando el punto decimal 5 lugares hacia la derecha), por lo que el valor 12.54321 se almacena como 1254321.

En términos monetarios, este enfoque es como almacenar los valores en dólares como múltiplos de milésima de centavo, similar a almacenar unidades de tiempo en milésimas de segundo.

La aplicación determina el factor de escalamiento fijo. Si cambia el factor de escalamiento, debes convertir todos los valores previamente escalados en tu base de datos.

Este enfoque almacena valores que son legibles (suponiendo que conoces el factor de escalamiento). Además, puedes usar consultas de SQL para realizar cálculos en valores almacenados en la base de datos, siempre que el resultado se escale correctamente y no se desborde.

Almacena el número entero sin escala y la escala en columnas separadas

También puedes almacenar números de precisión arbitrarios en Spanner con dos elementos:

  • Número entero sin escala almacenado en un arreglo de bytes
  • Número entero que especifica el factor de escalamiento

Primero, la aplicación convierte el decimal de precisión arbitrario en un número entero sin escala. Por ejemplo, la aplicación convierte 12.54321 a 1254321. La escala para este ejemplo es 5.

Luego, la aplicación convierte el número entero sin escala en un arreglo de bytes mediante una representación binaria portátil estándar (por ejemplo, complemento a dos de extremo grande).

Luego, la base de datos almacena el array de bytes (BYTES / BYTEA) y la escala de números enteros (INT64 / INT8) en dos columnas separadas y las vuelve a convertir en lectura.

En Java, puedes usar BigDecimal y BigInteger para realizar estos cálculos:

byte[] storedUnscaledBytes = bigDecimal.unscaledValue().toByteArray();
int storedScale = bigDecimal.scale();

Puedes volver a leer un BigDecimal de Java con el siguiente código:

BigDecimal bigDecimal = new BigDecimal(
    new BigInteger(storedUnscaledBytes),
    storedScale);

Este enfoque almacena valores con precisión arbitraria y una representación portátil, pero los valores no son legibles en la base de datos, y la aplicación debe realizar todos los cálculos.

Almacena la representación interna de la aplicación como bytes

Otra opción es serializar los valores decimales de precisión arbitrarios en arreglos de bytes mediante la representación interna de la aplicación y, luego, almacenarlos directamente en la base de datos.

Los valores de base de datos almacenados no son legibles, y la aplicación debe realizar todos los cálculos.

Este enfoque tiene problemas de portabilidad. Si intentas leer los valores con un lenguaje de programación o una biblioteca diferente de la que se escribió originalmente, es posible que no funcione. Es posible que la lectura de los valores no funcione porque diferentes bibliotecas de precisión arbitraria pueden tener diferentes representaciones serializadas para arreglos de bytes.

¿Qué sigue?