Reféns COBOL e Matemática. Parte 1

Vamos ser sinceros: ninguém gosta de números fracionários - nem mesmo de computadores.

Quando se trata da linguagem de programação COBOL, a primeira pergunta que surge na cabeça de todos sempre se parece com: "Por que a humanidade ainda usa essa linguagem em muitas áreas vitais?" Os bancos ainda estão usando COBOL. Cerca de 7% do PIB dos EUA depende de COBOL no processamento de pagamentos do CMS . O Internal Revenue Service (IRS) dos Estados Unidos, como todos sabem, ainda usa COBOL. Essa linguagem também é usada na aviação (a partir daqui , aprendi uma coisa interessante sobre esse tópico: o número da reserva de passagens aéreas costumava ser um indicador usual). Pode-se dizer que muitas organizações muito sérias, seja um setor público ou privado, ainda estão usando o COBOL.



A segunda parte

O autor do material, a primeira parte da tradução que publicamos hoje, encontrará uma resposta para a pergunta de por que o COBOL, o idioma que surgiu em 1959, ainda é tão difundido.

Por que COBOL ainda está vivo?


A resposta tradicional a essa pergunta é profundamente cínica. As organizações são preguiça, incompetência e estupidez. Eles estão buscando preços baixos e não estão dispostos a investir na reescrita de seus sistemas de software em algo moderno. Em geral, pode-se supor que o motivo pelo qual o trabalho de um número tão significativo de organizações dependa do COBOL seja uma combinação de inércia e miopia. E nisso, é claro, há alguma verdade. Reescrever grandes quantidades de código confuso é uma tarefa enorme. É caro Isso é complicado. E se o software existente parecer funcionar bem, a organização não terá uma motivação particularmente forte para investir em um projeto para atualizar esse software.

Tudo isso é verdade. Mas quando eu trabalhei no IRS, os veteranos do COBOL falaram sobre como eles tentaram reescrever o código em Java e verificou-se que o Java não pôde executar os cálculos corretamente.

Pareceu-me extremamente estranho. É tão estranho que imediatamente entendi o pensamento alarmista: "Senhor, isso significa que o IRS vem arredondando os pagamentos de impostos a todos há 50 anos !!!" Eu simplesmente não podia acreditar que o COBOL é capaz de contornar o Java em termos dos cálculos matemáticos necessários ao IRS. No final, eles não lançaram as pessoas para o espaço.

Um dos efeitos colaterais interessantes de aprender COBOL no verão é que comecei a entender o seguinte. A questão não é que Java não possa executar corretamente cálculos matemáticos. O ponto é exatamente como o Java faz os cálculos corretos. E quando você entende como os cálculos são executados em Java e como a mesma coisa é feita no COBOL, você começa a entender por que muitas organizações acham tão difícil se livrar do legado de seus computadores.

O que "i" deve ser pontilhado?


Vou me afastar um pouco da história do COBOL e falar sobre como os computadores armazenavam informações antes que a representação binária de dados se tornasse o padrão de fato (mas o material sobre como usar a interface do z / OS; isso é algo especial). Penso que, ao considerar nossa questão, será útil desviar-nos do tema principal nessa direção. No material acima, falei sobre várias maneiras de usar comutadores binários para armazenar números em sistemas binários, ternários e decimais, para armazenar números negativos - e assim por diante. A única coisa em que não prestei atenção suficiente foi como os números decimais são armazenados.

Se você projetou seu próprio computador binário, poderá começar decidindo que usará o sistema de números binários. Os bits à esquerda do ponto representam números inteiros - 1, 2, 4, 8. E os bits à direita - números fracionários - 1/2, 1/4, 1/8 ...


2,75 na representação binária

O problema aqui é entender como armazenar o próprio ponto decimal (de fato - devo dizer “ponto binário” - porque, afinal, estamos falando de números binários). Isso não é algum tipo de "alquimia do computador", então você pode adivinhar o que estou falando sobre números de ponto flutuante e de ponto fixo. Nos números de ponto flutuante, um ponto binário pode ser colocado em qualquer lugar (ou seja, pode "flutuar"). A posição do ponto é armazenada como um expoente. A capacidade de mover um ponto torna possível armazenar um número maior de números do que o disponível na ausência dessa oportunidade. O ponto decimal pode ser movido para a parte traseira do número e selecionar todos os bits para armazenar valores inteiros, representando números muito grandes. O ponto pode ser deslocado para a frente do número e expressar valores muito pequenos. Mas essa liberdade tem um preço de precisão. Vamos dar uma outra olhada na representação binária de 2.75 do exemplo anterior. Uma transição de quatro para oito é muito mais do que uma transição de um quarto para um oitavo. Pode ser mais fácil imaginar isso se reescrevermos o exemplo, como mostrado abaixo.


Escolhi a distância entre os números a olho nu - apenas para demonstrar minha ideia

A diferença entre os números é fácil de calcular por conta própria. Por exemplo, a distância entre 1/16 e 1/32 é 0,03125, mas a distância entre 1/2 e 1/4 já é 0,25.

Por que isso é importante? No caso de uma representação binária de números inteiros, isso não importa - a distância entre números adjacentes de um registro binário pode ser facilmente compensada preenchendo-os com as combinações apropriadas de bits e sem perder a precisão. Mas no caso da representação de números fracionários, não é tão simples. Se você tentar "preencher" os "buracos" entre números adjacentes - algo pode "cair" (e realmente cair) nesses buracos. Isso leva ao fato de que, no formato binário, não é possível obter representações exatas de números fracionários.

Isso é ilustrado pelo exemplo clássico do número 0,1 (um décimo). Como representar esse número em formato binário? 2 -1 é 1/2 ou 0,5. Isso é demais. 1/16 é 0,0635. Isso é muito pouco. 1/16 + 1/32 já está mais próximo (0,09375), mas 1/16 + 1/32 + 1/64 já é mais do que precisamos (0,109375).

Se você acredita que esse raciocínio pode ser continuado indefinidamente - então você está certo - do jeito que é .

Aqui você pode dizer para si mesmo: “Por que não salvamos 0,1 da mesma maneira que armazenamos o número 1? Podemos salvar o número 1 sem problemas - então, basta remover o ponto decimal e armazenar qualquer número da mesma maneira que armazenamos números inteiros. "

Essa é uma excelente solução para esse problema, exceto pelo fato de exigir a correção do ponto binário / decimal em algum local predeterminado. Caso contrário, os números 10.00001 e 100000.1 serão exatamente iguais. Mas se o ponto for fixo, de modo que, digamos, dois dígitos sejam alocados para a parte fracionária do número, podemos arredondar 10.00001 para 10.00 e 100000.1 girará 100000.10.

Acabamos de "inventar" os números de ponto fixo.

Com a representação de diferentes valores usando números de pontos fixos, acabamos de descobrir. É fácil de fazer. É possível, usando números de ponto fixo, facilitar a solução de outros problemas? Lembremos aqui sobre nossos bons amigos - sobre números decimais binários (decimal codificado em binário, BCD). A propósito, para que você saiba, esses números são usados ​​na maioria das calculadoras científicas e gráficas. A partir desses dispositivos, o que é bastante claro, eles esperam os resultados corretos dos cálculos.


Calculadora TI-84 Plus

Taxa de recorrência de Muller e Python


Os números de ponto fixo são considerados mais precisos devido ao fato de que os "orifícios" entre os números são constantes e porque o arredondamento ocorre apenas quando você precisa imaginar um número para o qual simplesmente não há espaço suficiente. Porém, ao usar números de ponto flutuante, podemos representar números muito grandes e muito pequenos usando a mesma quantidade de memória. É verdade que, com a ajuda deles, é impossível representar todos os números na faixa acessível com precisão e somos forçados a recorrer ao arredondamento para preencher os “buracos”.

COBOL foi criado como um idioma no qual, por padrão, números de ponto fixo são usados. Mas isso significa que o COBOL é melhor do que as linguagens modernas para realizar cálculos matemáticos? Se pegarmos um problema como o resultado do cálculo do valor 0.1 + 0.2, pode parecer que a pergunta anterior deve ser respondida “sim”. Mas será chato. Então vamos seguir em frente.

Vamos experimentar o COBOL usando o chamado relacionamento de Recorrência de Muller. Jean-Michel Muller é um cientista francês que pode ter feito uma grande descoberta científica no campo da tecnologia da informação. Ele encontrou uma maneira de interromper o funcionamento correto dos computadores usando a matemática. Tenho certeza de que ele diria que estuda os problemas de confiabilidade e precisão, mas nunca mais: cria problemas matemáticos que “quebram” os computadores. Uma dessas tarefas é sua fórmula de recorrência. É assim:


Este exemplo é retirado daqui.

A fórmula não parece assustadora. Certo? Esta tarefa é adequada para nossos propósitos, pelos seguintes motivos:

  • Apenas regras simples de matemática são usadas aqui - sem fórmulas complicadas ou idéias profundas.
  • Começamos com um número que tem dois dígitos após o ponto decimal. Como resultado, é fácil imaginar que estamos trabalhando com valores que representam certas quantias de dinheiro.
  • O erro resultante dos cálculos não é um pequeno erro de arredondamento. Este é um desvio do resultado correto por ordens inteiras de magnitude.

Aqui está um pequeno script Python que calcula os resultados da relação de recorrência de Mueller usando números de ponto flutuante e ponto fixo:

from decimal import Decimal def rec(y, z):  return 108 - ((815-1500/z)/y)  def floatpt(N):  x = [4, 4.25]  for i in range(2, N+1):   x.append(rec(x[i-1], x[i-2]))  return x  def fixedpt(N):  x = [Decimal(4), Decimal(17)/Decimal(4)]  for i in range(2, N+1):   x.append(rec(x[i-1], x[i-2]))  return x N = 20 flt = floatpt(N) fxd = fixedpt(N) for i in range(N):  print str(i) + ' | '+str(flt[i])+' | '+str(fxd[i]) 

Aqui está o resultado desse script:

 i | floating pt  | fixed pt -- | -------------- | --------------------------- 0 | 4       | 4 1 | 4.25      | 4.25 2 | 4.47058823529 | 4.4705882352941176470588235 3 | 4.64473684211 | 4.6447368421052631578947362 4 | 4.77053824363 | 4.7705382436260623229461618 5 | 4.85570071257 | 4.8557007125890736342039857 6 | 4.91084749866 | 4.9108474990827932004342938 7 | 4.94553739553 | 4.9455374041239167246519529 8 | 4.96696240804 | 4.9669625817627005962571288 9 | 4.98004220429 | 4.9800457013556311118526582 10 | 4.9879092328  | 4.9879794484783912679439415 11 | 4.99136264131 | 4.9927702880620482067468253 12 | 4.96745509555 | 4.9956558915062356478184985 13 | 4.42969049831 | 4.9973912683733697540253088 14 | -7.81723657846 | 4.9984339437852482376781601 15 | 168.939167671 | 4.9990600687785413938424188 16 | 102.039963152 | 4.9994358732880376990501184 17 | 100.099947516 | 4.9996602467866575821700634 18 | 100.004992041 | 4.9997713526716167817979714 19 | 100.000249579 | 4.9993671517118171375788238 

Até a iteração 12, o erro de arredondamento parece mais ou menos insignificante, mas então o verdadeiro inferno começa. Os cálculos de ponto flutuante convergem para um número vinte vezes maior do que o resultado dos cálculos de ponto fixo.

Talvez você pense que é improvável que alguém realize cálculos recursivos em larga escala. Mas foi exatamente isso que causou o desastre de 1991, que levou à morte de 28 pessoas, quando o sistema de controle de mísseis Patriot calculou incorretamente o tempo. Acontece que os cálculos de ponto flutuante causaram muitos danos acidentalmente. Aqui estão algumas ótimas coisas que talvez a computação de alto desempenho seja apenas uma maneira mais rápida de obter respostas erradas. Leia este trabalho se desejar obter mais informações sobre o problema discutido aqui e ver mais exemplos.

O problema é que a quantidade de RAM que os computadores possuem não é infinita. Portanto, é impossível armazenar um número infinito de posições decimais (ou binárias). Os cálculos de ponto fixo podem ser mais precisos que os de ponto flutuante se houver confiança de que é menos provável que sejam necessários mais números após o ponto do que o formato usado. Se o número não couber neste formato, será arredondado. Deve-se notar que nem os cálculos de ponto fixo nem os de ponto flutuante são protegidos do problema demonstrado pela relação de recorrência de Mueller. Isso e outros como resultado dão resultados incorretos. A questão é quando isso acontece. Se você aumentar o número de iterações em um script Python, por exemplo, de 20 para 22, o número final obtido nos cálculos com um ponto fixo será 0,728107. 23 iterações? -501.7081261. 24? 105.8598187.

Em diferentes idiomas, esse problema se manifesta de maneiras diferentes. Alguns, como COBOL, permitem que você trabalhe com números cujos parâmetros estão bem definidos. E no Python, por exemplo, existem valores padrão que podem ser configurados se o computador tiver memória suficiente. Se adicionarmos a linha getcontext().prec = 60 ao nosso programa, informando ao módulo decimal do Python que usaria 60 posições após o período, e não 28, como é feito por padrão, o programa poderá executar 40 iterações da relação de recorrência sem erros Mueller.

Para continuar ...

Caros leitores! Você encontrou problemas sérios decorrentes da natureza dos cálculos de ponto flutuante?

Source: https://habr.com/ru/post/pt467251/


All Articles