Eu quero usar dc
para lidar com alguns números de base 16 com pontos hexadecimais, mas estou tendo problemas de precisão. Por exemplo, abaixo estou multiplicando F423F.FD
por 100
, ambos hexadecimais. A resposta esperada é F423FFD
, em vez disso, está dando F423FFA.E1
, próximo, mas não preciso o suficiente, mesmo após o arredondamento.
$ dc
16 d i o F423F.FD 100 * p
F423FFA.E1
Eu li que dc
era uma calculadora de precisão ilimitada, e este não é um número grande de forma alguma. Há algo que estou fazendo de errado?
Obrigado por suas respostas. Dado os problemas com dc
, eu mordi a bala e escrevi meu próprio analisador para números reais em outras bases. Se alguém se interessar pelo código, posso postar aqui.
Expresso como decimal (usando
dc
para converter), isso corresponde a 999999,98 (arredondado para baixo) × 256, ou seja , 255999994,88, que é F423FFA.E1 em hexadecimal.Portanto, a diferença vem do
dc
comportamento de arredondamento de : em vez de calcular 256 × (999999 + 253 ÷ 256), que daria 255999997, ele arredonda 253 ÷ 256 para baixo e multiplica o resultado.dc
é uma calculadora de precisão arbitrária , o que significa que pode calcular com qualquer precisão que você queira, mas você tem que dizer qual é. Por padrão, sua precisão é 0, o que significa que a divisão produz apenas valores inteiros e a multiplicação usa o número de dígitos na entrada. Para definir a precisão, usek
(e lembre-se de que a precisão é sempre expressa em dígitos decimais, independentemente da base de entrada ou saída):(A precisão de 8 dígitos seria suficiente, pois é isso que você precisa para representar 1 ÷ 256 em decimal.)
Observe que apenas imprimir o número original mostra que ele é arredondado:
Você pode contornar isso adicionando muitos zeros à direita para obter mais precisão:
O problema
O problema é a maneira pela qual dc (e bc) entendem as constantes numéricas.
Por exemplo, o valor (em hexadecimal)
0.3
(dividido por 1) é transformado em um valor próximo a0.2
Na verdade, a constante simples
0.3
também é alterada:Parece que é de uma forma estranha, mas não é (mais adiante).
Adicionar mais zeros faz com que a resposta se aproxime do valor correto:
O último valor é exato e permanecerá exato, não importa o quanto mais zeros sejam adicionados.
O problema também está presente em bc:
Um dígito por bit?
O fato muito não intuitivo para números de ponto flutuante é que o número de dígitos necessários (após o ponto) é igual ao número de bits binários (também após o ponto). Um número binário 0,101 é exatamente igual a 0,625 em decimal. O número binário 0,0001110001 é (exatamente) igual a
0.1103515625
(dez dígitos decimais)Além disso, para um número de ponto flutuante como 2^(-10), que em binário tem apenas um bit (definido):
Tem o mesmo número de dígitos binários
.0000000001
(10) como dígitos decimais.0009765625
(10). Esse pode não ser o caso em outras bases, mas a base 10 é a representação interna de números em dc e bc e, portanto, é a única base com a qual realmente precisamos nos preocupar.A prova matemática está no final desta resposta.
escala bc
O número de dígitos após o ponto pode ser contado com a função
scale()
interna bc:Como mostrado, 2 dígitos são insuficientes para representar a constante
0.FD
.E, também, apenas contar o número de caracteres usados após o ponto é uma maneira muito incorreta de relatar (e usar) a escala do número. A escala de um número (em qualquer base) deve calcular o número de bits necessários.
Dígitos binários em um float hexadecimal.
Como se sabe, cada dígito hexadecimal usa 4 bits. Portanto, cada dígito hexadecimal após o ponto decimal requer 4 dígitos binários, que devido ao fato (ímpar?) acima também requerem 4 dígitos decimais.
Portanto, um número como
0.FD
exigirá 8 dígitos decimais para ser representado corretamente:Adicionar zeros
A matemática é direta (para números hexadecimais):
h
) após o ponto.h
por 4.h×4 - h = h × (4-1) = h × 3 = 3×h
zeros.No código shell (para sh):
Que imprimirá (corretamente em dc e bc):
Internamente, bc (ou dc) pode fazer com que o número de dígitos necessários corresponda ao número calculado acima (
3*h
) para converter hexadecimais em representação decimal interna. Ou alguma outra função para outras bases (assumindo que o número de dígitos é finito em relação à base 10 (interna de bc e dc) nessa outra base). Como 2i ( 2,4,8,16 ,...) e 5,10.posix
A especificação posix afirma que (para bc, em que dc é baseado):
Mas "… o número especificado de dígitos decimais." pode ser entendido como "... o número necessário de dígitos decimais para representar a constante numérica" (como descrito acima) sem afetar os "computadores internos decimais"
Porque:
bc não está realmente usando 50 ("o número especificado de dígitos decimais") conforme definido acima.
Somente se dividido é convertido (ainda incorretamente, pois usa uma escala de 2 para ler a constante
0.FD
antes de expandi-la para 50 dígitos):No entanto, isso é exato:
Novamente, a leitura de strings numéricas (constantes) deve usar o número correto de bits.
Prova matemática
Em duas etapas:
Uma fração binária pode ser escrita como a/2 n
Uma fração binária é uma soma finita de potências negativas de dois.
Por exemplo:
= 0 + 0×2 -1 + 0×2 -2 + 1×2 -3 + 1×2 -4 + 0×2 -5 + 1×2 -6 + 0×2 -7 + 1×2 -8 + 1×2 -9 + 0×2 -10 + 1×2 -11
= 2 -3 + 2 -4 + 2 -6 + 2 -8 + 2 -9 + 2 -11 = (com zeros removidos)
Em uma fração binária de n bits, o último bit tem um valor de 2 -n , ou 1/2 n . Neste exemplo: 2 -11 ou 1/2 11 .
= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 = (com inverso)
Em geral, o denominador pode se tornar 2 n com um expoente numerador positivo de dois. Todos os termos podem então ser combinados em um único valor a/2 n . Para este exemplo:
= 2 8 /2 11 + 2 7 /2 11 + 2 5 /2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 = (expresso com 2 11 )
= (2 8 + 2 7 + 2 5 + 2 3 + 2 2 + 1 ) / 2 11 = (extraindo o fator comum)
= (256 + 128 + 32 + 8 + 4 + 1) / 2 11 = (convertido em valor)
= 429 / 2 11
Toda fração binária pode ser expressa como b/10 n
Multiplique a/2 n por 5 n /5 n , obtendo (a×5 n )/(2 n ×5 n ) = (a×5 n )/10 n = b/10 n , onde b = a×5 n . Tem n dígitos.
Para o exemplo, temos:
(429·5 11 )/10 11 = 20947265625 / 10 11 = 0,20947265625
Foi demonstrado que toda fração binária é uma fração decimal com o mesmo número de dígitos.