Spanner proporciona el tipo NUMERIC
, que puede almacenar números con precisión decimal.
La semántica del tipo NUMERIC
en Spanner varía entre sus dos dialectos SQL (GoogleSQL y PostgreSQL), especialmente en lo que respecta a los 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 pueden ser cualquier número dentro del intervalo 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 numérico de precisión fija (precision=38 y scale=9) y no se puede usar para almacenar datos numéricos de precisión arbitraria. Si necesita almacenar números de precisión arbitraria en bases de datos del dialecto GoogleSQL, le recomendamos que los almacene como cadenas.
Precisión de los tipos numéricos de Spanner
La precisión es el número de dígitos de un número. La escala es el número de dígitos que hay a la derecha de la coma 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 entero con signo de 64 bits llamado
INT64
en el dialecto de GoogleSQL yINT8
en el dialecto de PostgreSQL. - Tipo de punto flotante binario de 64 bits (doble) de precisión IEEE llamado
FLOAT64
en el dialecto GoogleSQL yFLOAT8
en el dialecto PostgreSQL. - Tipo de
NUMERIC
precisión decimal.
Veamos cada una de ellas en términos de precisión y escala.
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 fraccionarios y proporciona
entre 15 y 17 dígitos significativos (número de dígitos de un número con todos los ceros finales
eliminados) de precisión decimal. Decimos que este tipo representa valores numéricos decimales aproximados porque la representación binaria de coma flotante de 64 bits IEEE que usa Spanner no puede representar con precisión fracciones decimales (base 10), ya que solo puede representar fracciones de base 2 con exactitud.
Esta pérdida de precisión introduce errores de redondeo en algunas fracciones decimales.
Por ejemplo, cuando almacenas el valor decimal 0,2 con el tipo de datos FLOAT64
/ FLOAT8
, la representación binaria se vuelve a convertir en el valor decimal 0,20000000000000001 (con 18 dígitos de precisión). Del mismo modo, (1,4 * 165) se convierte en 230,999999999999971 y (0,1 + 0,2) se convierte en 0,30000000000000004. Por eso, se dice que los números de punto flotante de 64 bits solo tienen entre 15 y 17 dígitos significativos de precisión (solo algunos números con más de 15 decimales se pueden representar como números de punto flotante de 64 bits sin redondear). Para obtener más información sobre cómo se calcula la precisión de coma flotante, consulta el formato de coma flotante de doble precisión.
Ni INT64
/ INT8
ni FLOAT64
/ FLOAT8
tienen la precisión ideal para cálculos financieros, científicos o de ingeniería, en los que suele ser necesaria una precisión de 30 dígitos o más.
El tipo de datos NUMERIC
es adecuado para esas aplicaciones, ya que puede representar valores numéricos decimales exactos con una precisión de más de 30 decimales.
El tipo de datos NUMERIC
de GoogleSQL puede representar números con una precisión decimal fija de 38 y una escala fija de 9. El intervalo de NUMERIC
de GoogleSQL es de -99999999999999999999999999999,999999999
a 99999999999999999999999999999,999999999.
El tipo NUMERIC
del dialecto PostgreSQL puede representar números 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 mayores que la precisión y la escala que ofrece NUMERIC
, en las siguientes secciones se describen algunas soluciones recomendadas.
Recomendación: almacena números de precisión arbitraria como cadenas
Si necesitas almacenar un número de precisión arbitraria en una base de datos de Spanner y necesitas más precisión de la que ofrece NUMERIC
, te recomendamos que almacenes el valor como su representación decimal en una columna STRING
o 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 el valor de la columna STRING
/ VARCHAR
para las lecturas y escrituras 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. Por ejemplo, en Java 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 con
una precisión exacta (hasta el límite de longitud de la columna STRING
/ VARCHAR
) y el valor
sigue siendo legible.
Realizar agregaciones y cálculos exactos
Para realizar agregaciones y cálculos exact en representaciones de cadena de números de precisión arbitraria, tu aplicación debe realizar estos cálculos. No puedes usar funciones de agregación de SQL.
Por ejemplo, para realizar el equivalente de un SUM(value)
de SQL en un intervalo de filas, la aplicación debe consultar los valores de cadena de las filas, convertirlos y sumarlos internamente en la aplicación.
Realizar agregaciones, ordenaciones y cálculos aproximados
Puedes usar consultas SQL para realizar cálculos agregados aproximados convirtiendo los valores a FLOAT64
o FLOAT8
.
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 limitar los valores por intervalo con la 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 se aproximan a los límites del tipo de datos FLOAT64
/ FLOAT8
.
Alternativas
Hay otras formas de almacenar números de precisión arbitraria en Spanner. Si almacenar números de precisión arbitraria como cadenas no funciona en tu aplicación, considera las siguientes alternativas:
Almacena valores enteros escalados de la aplicación.
Para almacenar números de precisión arbitraria, puedes ajustar la escala de los valores antes de escribirlos para que los números se almacenen siempre como números enteros y volver a ajustar la escala de los valores después de leerlos. Tu aplicación almacena un factor de escala fijo y la precisión se limita a los 18 dígitos proporcionados por el tipo de datos INT64
/ INT8
.
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 multiplicándolo por 100.000 (desplaza el punto decimal 5 posiciones hacia la derecha), por lo que el valor 12,54321 se almacena como 1254321
.
En términos monetarios, este enfoque es como almacenar valores en dólares como múltiplos de milésimas de centavo, de forma similar a como se almacenan las unidades de tiempo en milisegundos.
La aplicación determina el factor de escala fijo. Si cambia el factor de escala, debe convertir todos los valores escalados anteriormente de su base de datos.
Con este método, se almacenan valores que pueden leer los usuarios (siempre que conozcas el factor de escala). También puedes usar consultas SQL para hacer cálculos directamente con los valores almacenados en la base de datos, siempre que el resultado se ajuste correctamente y no se desborde.
Almacenar el valor entero sin escalar y la escala en columnas independientes
También puedes almacenar números de precisión arbitraria en Spanner mediante dos elementos:
- Valor entero sin escalar almacenado en una matriz de bytes.
- Número entero que especifica el factor de escala.
Primero, tu aplicación convierte el decimal de precisión arbitraria en un valor entero sin escalar. Por ejemplo, la aplicación convierte 12.54321
en 1254321
.
La escala de este ejemplo es 5
.
A continuación, la aplicación convierte el valor entero sin escalar en una matriz de bytes mediante una representación binaria portátil estándar (por ejemplo, complemento a dos big-endian).
A continuación, la base de datos almacena la matriz de bytes (BYTES
/ BYTEA
) y la escala de enteros (INT64
/ INT8
) en dos columnas independientes y los vuelve a convertir al leerlos.
En Java, puedes usar BigDecimal
y BigInteger
para hacer estos cálculos:
byte[] storedUnscaledBytes = bigDecimal.unscaledValue().toByteArray();
int storedScale = bigDecimal.scale();
Puedes leer una retrollamada a Java BigDecimal
con el siguiente código:
BigDecimal bigDecimal = new BigDecimal(
new BigInteger(storedUnscaledBytes),
storedScale);
Este método almacena valores con una precisión arbitraria y una representación portátil, pero los valores no son legibles por humanos 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 arbitraria en matrices de bytes mediante la representación interna de la aplicación y, a continuación, almacenarlos directamente en la base de datos.
Los valores de la base de datos almacenados no son legibles por humanos 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 a la que los escribió originalmente, es posible que no funcione. Es posible que no se puedan leer los valores porque las diferentes bibliotecas de precisión arbitraria pueden tener representaciones serializadas diferentes para las matrices de bytes.
Siguientes pasos
- Consulta otros tipos de datos disponibles en Spanner.
- Consulta cómo configurar correctamente un diseño de esquema y un modelo de datos de Spanner.
- Consulta información sobre cómo optimizar el diseño de tu esquema para Spanner.