AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 456794
Accepted
Yimin Rong
Yimin Rong
Asked: 2018-07-18 08:26:33 +0800 CST2018-07-18 08:26:33 +0800 CST 2018-07-18 08:26:33 +0800 CST

Perdendo precisão com dc

  • 772

Eu quero usar dcpara lidar com alguns números de base 16 com pontos hexadecimais, mas estou tendo problemas de precisão. Por exemplo, abaixo estou multiplicando F423F.FDpor 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 dcera 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.

dc
  • 3 3 respostas
  • 1424 Views

3 respostas

  • Voted
  1. Stephen Kitt
    2018-07-18T08:51:24+08:002018-07-18T08:51:24+08:00

    Expresso como decimal (usando dcpara 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 dccomportamento 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, use k(e lembre-se de que a precisão é sempre expressa em dígitos decimais, independentemente da base de entrada ou saída):

    10 k
    16 d i o
    F423FFD 100 / p
    F423F.FD0000000
    100 * p
    F423FFD.000000000
    

    (A precisão de 8 dígitos seria suficiente, pois é isso que você precisa para representar 1 ÷ 256 em decimal.)

    • 8
  2. Best Answer
    meuh
    2018-07-18T09:03:30+08:002018-07-18T09:03:30+08:00

    Observe que apenas imprimir o número original mostra que ele é arredondado:

    $ dc <<<'16 d i o F423F.FD p'
    F423F.FA
    

    Você pode contornar isso adicionando muitos zeros à direita para obter mais precisão:

    $ dc <<<'16 d i o F423F.FD000000 100 * p'
    F423FFD.0000000
    
    • 6
  3. user232326
    2018-07-20T11:18:52+08:002018-07-20T11:18:52+08:00

    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

    $ dc <<<"20k 16 d i o 0.3 1 / p"
    .199999999999999999999999999
    

    Na verdade, a constante simples 0.3também é alterada:

    $ dc <<<"20 k 16 d i o     0.3     p"
    .1
    

    Parece que é de uma forma estranha, mas não é (mais adiante).
    Adicionar mais zeros faz com que a resposta se aproxime do valor correto:

    $ dc <<<"20 k 16 d i o     0.30     p"
    .2E
    
    $ dc <<<"20 k 16 d i o     0.300     p"
    .2FD
    
    $ dc <<<"20 k 16 d i o     0.3000     p"
    .3000
    

    O último valor é exato e permanecerá exato, não importa o quanto mais zeros sejam adicionados.

    $ dc <<<"20 k 16 d i o     0.30000000     p"
    .3000000
    

    O problema também está presente em bc:

    $ bc <<< "scale=20; obase=16; ibase=16;    0.3 / 1"
    .19999999999999999
    
    $ bc <<< "scale=20; obase=16; ibase=16;    0.30 / 1"
    .2E147AE147AE147AE
    
    $ bc <<< "scale=20; obase=16; ibase=16;    0.300 / 1"
    .2FDF3B645A1CAC083
    
    $ bc <<< "scale=20; obase=16; ibase=16;    0.3000 / 1"
    .30000000000000000
    

    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)

    $ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
    .625000000000000000000000000000
    .110351562500000000000000000000
    .1234567890
    

    Além disso, para um número de ponto flutuante como 2^(-10), que em binário tem apenas um bit (definido):

    $ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
    .0000000001000000000000000000000000000000000000000000000000000000000
    .00097656250000000000
    

    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:

    $ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
    2
    .FA
    FA.E1
    

    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.FDexigirá 8 dígitos decimais para ser representado corretamente:

    $ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
    8
    .98828125
    253.00000000
    

    Adicionar zeros

    A matemática é direta (para números hexadecimais):

    • Conte o número de dígitos hexadecimais ( h) após o ponto.
    • Multiplique hpor 4.
    • Adicione h×4 - h = h × (4-1) = h × 3 = 3×hzeros.

    No código shell (para sh):

    a=F423F.FD
    h=${a##*.}
    h=${#h}
    a=$a$(printf '%0*d' $((3*h)) 0)
    echo "$a"
    
    echo "obase=16;ibase=16;$a*100" | bc
    
    echo "20 k 16 d i o $a 100 * p" | dc
    

    Que imprimirá (corretamente em dc e bc):

    $  sh ./script
    F423F.FD000000
    F423FFD.0000000
    F423FFD.0000000
    

    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):

    Os cálculos internos devem ser conduzidos como se fossem decimais, independentemente das bases de entrada e saída, para o número especificado de dígitos decimais.

    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 <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
    1.FA
    

    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.FDantes de expandi-la para 50 dígitos):

    $ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
    .FAE147AE147AE147AE147AE147AE147AE147AE147A
    

    No entanto, isso é exato:

    $ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
    .FD0000000000000000000000000000000000000000
    

    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.00110101101 = 
    = 0. 0     0      1     1      0      1     0      1      1     0       1
    

    = 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.

    • 1

relate perguntas

  • Como é que a impressão dc divide o número longo

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    ssh Não é possível negociar: "nenhuma cifra correspondente encontrada", está rejeitando o cbc

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    Como descarregar o módulo do kernel 'nvidia-drm'?

    • 13 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Wong Jia Hau ssh-add retorna com: "Erro ao conectar ao agente: nenhum arquivo ou diretório" 2018-08-24 23:28:13 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Bagas Sanjaya Por que o Linux usa LF como caractere de nova linha? 2017-12-20 05:48:21 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve