Como armazenar dados numéricos de precisão arbitrária

O Cloud Spanner tem vários tipos de dados de coluna, mas não tem um tipo de dados para números de precisão arbitrários. Quando você precisa armazenar números de precisão arbitrária no Cloud Spanner, recomendamos armazená-los como strings.

Introdução aos tipos numéricos

Em muitos bancos de dados, os números de precisão arbitrária são expressos como NUMERIC(p,s), onde p é a precisão decimal (número total de dígitos) e s é a escala (número de dígitos após o ponto decimal). Por exemplo, NUMERIC(8,3) pode representar -99999,999 a +99999,999.

O Cloud Spanner não tem um tipo de dados para números de precisão arbitrários. O Cloud Spanner tem dois tipos numéricos: INT64 e FLOAT64. Nenhum desses tipos tem a precisão ideal para cálculos financeiros, científicos ou de engenharia, em que uma precisão de 30 dígitos ou mais é normalmente necessária. FLOAT64 fornece 15 dígitos de precisão, com uma escala de -294 a +294, e INT64 fornece 18 dígitos de precisão, com uma escala de zero. Para ver mais detalhes sobre como a precisão do ponto flutuante é calculada, consulte Formato de ponto flutuante de precisão dupla.

Normalmente, os tipos numéricos de precisão arbitrária armazenam valores, dimensionando-os e armazenando-os como inteiros. Por exemplo, para um tipo numérico com precisão de 5 e escala de 2, o valor 0,3 é dimensionado por um fator de 100 (102) e armazenado como 00030. Armazenar valores dessa maneira evita erros de frações decimais e perda de precisão.

Por outro lado, o Cloud Spanner armazena os valores de FLOAT64 como números binários. A representação binária de ponto flutuante de 64 bits do IEEE que o Cloud Spanner usa não pode representar com precisão as frações decimais. Essa perda de precisão introduz erros de arredondamento para algumas frações decimais.

Por exemplo, quando você armazena o valor decimal 0,2 usando o tipo de dados FLOAT64, a representação binária converte de volta para um valor decimal de 0,20000000000000001 (para 18 dígitos de precisão). Da mesma forma (1,4 * 165) converte de volta para 230,999999999999971 e (0,1 + 0,2) converte de volta para 0,30000000000000004. É por isso que os floats de 64 bits são descritos como tendo apenas 15 dígitos de precisão.

Recomendação: armazene números de precisão arbitrária como strings

Quando você precisa armazenar um número de precisão arbitrária no banco de dados do Cloud Spanner, e precisa de mais precisão do que o FLOAT64 oferece, recomendamos armazenar o valor como sua representação decimal em uma coluna STRING. Por exemplo, o número 123.4 é armazenado como a string "123.4".

Com essa abordagem, seu aplicativo precisa executar uma conversão sem perdas entre a representação interna do aplicativo do número e o valor da coluna STRING para leituras e gravações do banco de dados.

A maioria das bibliotecas de precisão arbitrárias tem métodos internos para executar essa conversão sem perdas. Em Java, por exemplo, use o método BigDecimal.toPlainString() e o construtor BigDecimal(String).

Armazenar o número como uma string tem a vantagem de que o valor é armazenado com precisão exata (até o limite de comprimento da coluna STRING) e o valor permanece legível.

Como executar agregações e cálculos exatos

Para executar agregações e cálculos exatos em representações de strings de números de precisão arbitrária, seu aplicativo precisa executar esses cálculos. Não é possível usar funções agregadas de SQL.

Por exemplo, para executar o equivalente a um SUM(value) SQL em um intervalo de linhas, o aplicativo precisa consultar os valores de sequência das linhas, convertê-los e adicioná-los internamente no aplicativo.

Como executar agregações, classificação e cálculos aproximados

É possível usar consultas SQL para executar cálculos agregados aproximados, lançando os valores para FLOAT64:

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

Da mesma forma, é possível classificar por valor numérico ou valores de limite por intervalo com conversão:

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

Esses cálculos são aproximados aos limites do tipo de dados FLOAT64.

Alternativas

Existem outras maneiras de armazenar números de precisão arbitrária no Cloud Spanner. Se o armazenamento de números de precisão arbitrária como strings não funcionar para seu aplicativo, considere as seguintes alternativas:

Armazenar valores INT64 dimensionados para aplicativos

Para armazenar números de precisão arbitrária, é possível pré-dimensionar os valores antes de gravar, para que os números sejam sempre armazenados como números inteiros e redimensionar os valores após a leitura. Seu aplicativo armazena um fator de escala fixo e a precisão é limitada aos 18 dígitos fornecidos pelo tipo de dados INT64.

Tomemos, por exemplo, um número que precisa ser armazenado com uma precisão de 5 casas decimais. O aplicativo converte o valor em um inteiro multiplicando-o por 100.000 (mudando o ponto decimal 5 lugares para a direita), portanto, o valor 12,54321 é armazenado como 1254321.

Em termos monetários, essa abordagem é como armazenar valores em dólares como múltiplos de milicentésimos, semelhante ao armazenamento de unidades de tempo como milissegundos.

O aplicativo determina o fator de escalonamento fixo. Se você alterar o fator de escalonamento, precisará converter todos os valores escalonados anteriormente em seu banco de dados.

Essa abordagem armazena valores que são legíveis por humanos (supondo que você saiba o fator de escalonamento). Além disso, é possível usar consultas SQL para executar cálculos diretamente em valores armazenados no banco de dados, desde que o resultado seja dimensionado corretamente e não estoure.

Armazene o valor inteiro sem escalonamento e o escalonamento em colunas separadas

Também é possível armazenar números de precisão arbitrários no Cloud Spanner usando dois elementos:

  • O valor inteiro sem escalonamento armazenado em uma matriz de bytes.
  • Um inteiro que especifica o fator de escalonamento.

Primeiro, seu aplicativo converte o decimal de precisão arbitrária em um valor inteiro não escalonado. Por exemplo, o aplicativo converte 12.54321 para 1254321. O escalonamento para este exemplo é 5.

Em seguida, o aplicativo converte o valor inteiro sem escala em uma matriz de bytes usando uma representação binária portátil padrão (por exemplo, o complemento dos dois big-endian).

Em seguida, o banco de dados armazena a matriz de bytes (BYTES) e o escalonamento de números inteiros (INT64) em duas colunas separadas e as converte novamente em leitura.

Em Java, é possível usar BigDecimal e BigInteger para executar esses cálculos:

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

É possível ler de volta para um Java BigDecimal usando o seguinte código:

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

Essa abordagem armazena valores com precisão arbitrária e uma representação portátil, mas os valores não são legíveis pelo usuário no banco de dados e todos os cálculos precisam ser executados pelo aplicativo.

Armazenar representação interna do aplicativo como bytes

Outra opção é serializar os valores decimais de precisão arbitrária para matrizes de bytes usando a representação interna do aplicativo e, em seguida, armazená-los diretamente no banco de dados.

Os valores do banco de dados armazenados não são legíveis e o aplicativo precisa executar todos os cálculos.

Essa abordagem tem problemas de portabilidade. Se você tentar ler os valores com uma linguagem de programação ou biblioteca diferente da que originalmente os gravou, pode não funcionar. A leitura dos valores de volta pode não funcionar porque diferentes bibliotecas de precisão arbitrária podem ter diferentes representações serializadas para matrizes de bytes.

A seguir

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Cloud Spanner