Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Prática recomendada para armazenar pesos em um banco de dados SQL?


Você alega que existem imprecisões inerentes aos números de ponto flutuante. Acho que isso merece ser explorado um pouco primeiro.

Ao decidir sobre um sistema numérico para representar um número (seja em um pedaço de papel, em um circuito de computador ou em outro lugar), existem dois separados questões a considerar:

  1. sua base; e

  2. seu formato .

Escolha uma base, qualquer base…


Limitado por espaço finito, não se pode representar um membro arbitrário de um conjunto infinito . Por exemplo:não importa quanto papel você compre ou quão pequena seja sua caligrafia, sempre será possível encontrar um número inteiro que não couber no espaço fornecido (você pode continuar acrescentando dígitos extras até que o papel acabe). Então, com inteiros , geralmente restringimos nosso espaço finito para representar apenas aqueles que se enquadram em algum intervalo específico - por exemplo. se tivermos espaço para o sinal positivo/negativo e três dígitos, podemos nos restringir ao intervalo [-999,+999] .

Toda não vazio intervalo contém um conjunto infinito de números reais. Em outras palavras, não importa qual intervalo ocupe os números reais —seja [-999,+999] , [0,1] , [0.000001,0.000002] ou qualquer outra coisa - ainda há um conjunto infinito de reais dentro desse intervalo (é preciso apenas continuar acrescentando dígitos fracionários (diferentes de zero))! Portanto, números reais arbitrários devem sempre ser "arredondado" para algo que pode ser representado no espaço finito.

O conjunto de números reais que podem ser representados em espaço finito depende do sistema numérico usado. Em nosso (familiar) posicional base-10 sistema, o espaço finito será suficiente para metade (0.510 ), mas não para um terço (0.33333…10 ); por outro lado, no (menos familiar) posicional base-9 sistema, é o contrário (esses mesmos números são respectivamente 0.44444…9 e 0.39 ). A consequência de tudo isso é que alguns números que podem ser representados usando apenas uma pequena quantidade de espaço na base posicional-10 (e, portanto, aparecem ser muito "redondo" para nós humanos), e. um décimo, exigiria que circuitos binários infinitos fossem armazenados com precisão (e, portanto, não parecem ser muito "redondos" para nossos amigos digitais)! Notavelmente, como 2 é um fator de 10, o mesmo não acontece ao contrário:qualquer número que pode ser representado com binário finito também pode ser representado com decimal finito.

Não podemos fazer melhor para quantidades contínuas. Em última análise, tais quantidades devem usar uma representação finita em algumas sistema numérico:é arbitrário se esse sistema é fácil em circuitos de computador, em dedos humanos, em outra coisa ou em nada - qualquer que seja o sistema usado, o valor deve ser arredondado e, portanto, sempre resulta em "erro de representação".

Em outras palavras, mesmo que se tenha um instrumento de medição perfeitamente preciso (o que é fisicamente impossível), qualquer medição que ele relatar já terá sido arredondada para um número que por acaso caiba em sua exibição (em qualquer base que ele use - normalmente decimal, por razões óbvias). Portanto, "86,2 onças" nunca é realmente "86,2 onças " mas sim uma representação de "algo entre 86,1500000... oz e 86,2499999... oz ". (Na verdade, porque na realidade o instrumento é imperfeito, tudo o que podemos dizer é que temos alguns grau de confiança que o valor real cai dentro desse intervalo - mas isso está definitivamente se afastando do ponto aqui).

Mas podemos fazer melhor para quantidades discretas . Esses valores não são "números reais arbitrários" e, portanto, nenhuma das opções acima se aplica a eles:eles podem ser representados exatamente no sistema numérico em que foram definidos - e, de fato, devem ser (como converter para outro sistema numérico e truncar para um comprimento finito resultaria no arredondamento para um número inexato). Os computadores podem (ineficientemente) lidar com tais situações representando o número como uma string:por exemplo, considere ASCII ou BCD codificação.

Aplicar um formato…


Como é uma propriedade da base (um pouco arbitrária) do sistema de numeração, se um valor parece ou não ser "redondo" não tem relação com sua precisão . Essa é uma observação muito importante , o que contraria a intuição de muitas pessoas (e é a razão pela qual passei tanto tempo explicando a base numérica acima).

A precisão é determinada por quantos números significativos uma representação tem . Precisamos de um formato de armazenamento capaz de registrar nossos valores em pelo menos tantos algarismos significativos quantos consideramos corretos . Tomando como exemplo valores que consideramos corretos quando declarados como 86.2 e 0.0000862 , as duas opções mais comuns são:

  • Ponto fixo , onde o número de algarismos significativos depende da magnitude :por exemplo. na representação fixa de 5 pontos decimais, nossos valores seriam armazenados como 86.20000 e 0.00009 (e, portanto, têm 7 e 1 algarismos significativos de precisão, respectivamente). Neste exemplo, a precisão foi perdida no último valor (e, de fato, não levaria muito mais tempo para sermos totalmente incapazes de representar qualquer coisa de significância); e o valor anterior armazenado falsa precisão , que é um desperdício do nosso espaço finito (e, de fato, não levaria muito mais para que o valor se tornasse tão grande que transbordasse a capacidade de armazenamento).

    Um exemplo comum de quando esse formato pode ser apropriado é para um sistema de contabilidade:as somas monetárias geralmente devem ser rastreadas até o centavo independentemente de sua magnitude (portanto, menos precisão é necessária para valores pequenos e mais precisão é necessária para valores grandes). Acontece que a moeda geralmente também é considerada discreta (os centavos são indivisíveis), portanto, esse também é um bom exemplo de uma situação em que uma base específica (decimal para a maioria das moedas modernas) é desejável para evitar os erros de representação discutidos acima.

  • Ponto flutuante , onde o número de algarismos significativos é constante independente da magnitude :por exemplo. na representação decimal de 5 algarismos significativos, nossos valores seriam armazenados como 86.200 e 0.000086200 (e, por definição, tem 5 algarismos significativos de precisão nas duas vezes). Neste exemplo, ambos os valores foram armazenados sem perda de precisão; e ambos também têm a mesma quantia de falsa precisão, que é menos dispendiosa (e, portanto, podemos usar nosso espaço finito para representar uma faixa muito maior de valores - grandes e pequenos).

    Um exemplo comum de quando esse formato pode ser apropriado é para registrar qualquer medida do mundo real :a precisão dos instrumentos de medição (que todos sofrem de sistemático e aleatório erros) é bastante constante, independentemente da escala, portanto, com algarismos significativos suficientes (normalmente em torno de 3 ou 4 dígitos), absolutamente nenhuma precisão é perdida mesmo que uma mudança de base resulte em arredondamento para um número diferente .

    Mas qual é a precisão dos formatos de armazenamento de ponto flutuante usado por nossos computadores?

    • Um IEEE754 ponto flutuante de precisão simples (binary32) número tem 24 bits, ou log10(2) (mais de 7) dígitos, de significância - ou seja, tem uma tolerância inferior a ±0.000006% . Em outras palavras, é mais preciso do que dizer "86.20000 ".

    • Um ponto flutuante de precisão dupla (binary64) IEEE754 número tem 53 bits, ou log10(2) (quase 16) dígitos, de significância - ou seja, tem uma tolerância de pouco mais de ±0.00000000000001% . Em outras palavras, é mais preciso do que dizer "86.2000000000000 ".

    O mais importante a perceber é que esses formatos são, respectivamente, mais de dez mil e mais de um trilhão vezes mais preciso do que dizer "86.2" - mesmo que as conversões exatas do binário de volta para decimal incluam uma falsa precisão errônea (que devemos ignorar:mais sobre isso em breve)!

Observe também que ambos corrigido e formatos de ponto flutuante resultarão em perda de precisão quando um valor for conhecido com mais precisão do que o formato suportado. Tais erros de arredondamento pode se propagar em operações aritméticas para produzir resultados aparentemente errôneos (o que sem dúvida explica sua referência às "incorreções inerentes" dos números de ponto flutuante):por exemplo, 3 × 3000 em ponto fixo de 5 casas renderia 999.99000 em vez de 1000.00000; e 7 − ⁄50 em 5 algarismos significativos, ponto flutuante resultaria em 0.0028600 em vez de 0.0028571 .

O campo de análise numérica é dedicado a entender esses efeitos, mas é importante perceber que qualquer sistema utilizável (mesmo realizando cálculos em sua cabeça) é vulnerável a tais problemas porque nenhum método de cálculo com garantia de término pode oferecer precisão infinita :considere, por exemplo, como calcular a área de um círculo – necessariamente haverá perda de precisão no valor usado para π, que se propagará no resultado.

Conclusão


  1. Medidas do mundo real devem usar ponto flutuante binário :é rápido, compacto, extremamente preciso e nada pior do que qualquer outra coisa (incluindo a versão decimal a partir da qual você começou). Desde tipos de dados de ponto flutuante do MySQL são IEEE754, isso é exatamente o que eles oferecem.

  2. Aplicativos de moeda devem usar ponto fixo denário :embora seja lento e desperdice memória, garante que os valores não sejam arredondados para quantidades inexatas e que os centavos não sejam perdidos em grandes somas monetárias. Desde tipos de dados de ponto fixo do MySQL são strings codificadas em BCD, é exatamente isso que eles oferecem.

Finalmente, tenha em mente que linguagens de programação geralmente representam valores fracionários usando ponto flutuante binário types:então, se seu banco de dados armazena valores em outro formato, você precisa ter cuidado como eles são trazidos para sua aplicação ou então eles podem ser convertidos (com todos os problemas decorrentes) na interface.

Qual ​​é a melhor opção neste caso?


Espero tê-lo convencido de que seus valores podem com segurança (e devem ) ser armazenado em tipos de ponto flutuante sem se preocupar muito com quaisquer "imprecisões"? Lembre-se, eles são mais precisa do que sua frágil representação decimal de 3 dígitos significativos já foi:você só precisa ignorar a precisão falsa (mas deve-se sempre faça isso de qualquer maneira, mesmo se estiver usando um formato decimal de ponto fixo).

Quanto à sua pergunta:escolha a opção 1 ou 2 sobre a opção 3 - isso facilita as comparações (por exemplo, para encontrar a massa máxima, basta usar MAX(mass) , enquanto fazê-lo com eficiência em duas colunas exigiria algum aninhamento).

Entre esses dois, não importa qual escolha - números de ponto flutuante são armazenados com um número constante de bits significativos independentemente de sua escala .

Além disso, enquanto no caso geral pode acontecer que alguns valores sejam arredondados para números binários mais próximos de sua representação decimal original usando a opção 1, enquanto simultaneamente outros são arredondados para números binários mais próximos de sua representação decimal original usando a opção 2, como veremos em breve tais erros de representação apenas se manifestarem dentro da falsa precisão que deve sempre ser ignorada.

No entanto, neste No caso, porque acontece que há 16 onças para 1 libra (e 16 é uma potência de 2), as diferenças relativas entre os valores decimais originais e os números binários armazenados usando as duas abordagens são idênticas :

  1. 5.387510 (não 5.3367187510 conforme indicado na sua pergunta) seria armazenado em um float binary32 como 101.0110001100110011001102 (que é 5.3874998092651367187510 ):este é 0.0000036% do valor original (mas, como discutido acima, o "valor original" já era uma representação bastante ruim da quantidade física que representa).

    Sabendo que um float binary32 armazena apenas 7 dígitos decimais de precisão, nosso compilador sabe com certeza que tudo a partir do 8º dígito é definitivamente precisão falsa e, portanto, deve ser ignorado em todas caso — portanto, desde que nosso valor de entrada não exigisse mais precisão do que isso (e se o fez, binary32 foi obviamente a escolha errada de formato), isso garantia um retorno para um valor decimal que parece tão redondo quanto aquele de onde começamos:5.38750010 . No entanto, devemos realmente aplicar conhecimento de domínio neste ponto (como deveríamos com qualquer formato de armazenamento) para descartar qualquer precisão falsa adicional que possa existir, como aqueles dois zeros à direita.

  2. 86.210 seria armazenado em um float binary32 como 1010110.001100110011001102 (que é 86.199996948242187510 ):também é 0.0000036% do valor original. Como antes, ignoramos a precisão falsa para retornar à nossa entrada original.

Observe como as representações binárias dos números são idênticas, exceto pelo posicionamento do ponto radix (que é quatro bits de distância):
101.0110 00110011001100110
101 0110.00110011001100110

Isso ocorre porque 5,3875 × 2 =86,2.