14. Funções Intrínsecas

Em 1989 a ANS publicou um documento sugerindo a implementação de funções intrínsecas no COBOL. Essa iniciativa tinha por objetivo oferecer de forma nativa algumas das funcionalidades que, antes, precisavam ser construídas como subrotinas. Muitos compiladores COBOL/85 incorporaram essas funções, mas elas só foram consideradas obrigatórias a partir da revisão que aconteceu em 2002.

O padrão 2002 estabeleceu 42 funções obrigatórias. Muitos compiladores que aderiram a esse padrão oferecem mais do que isso, mas é possível que você encontre compiladores anteriores que não ofereçam nenhuma.

O GNU-COBOL, por exemplo, possui 89 funções intrínsecas. O IBM Enterprise COBOL for Z/OS oferece 53. O ACUCOBOL, que não aderiu ao padrão 2002, não tem nenhuma.

Existem funções para tratamento de data e hora, operações com strings e  variáveis de sistema, funções matemáticas, estatísticas, financeiras e trigonométricas. Neste capítulo veremos algumas funções que estão presentes na maioria dos compiladores e que são úteis para boa parte dos sistemas comerciais.

O uso de funções intrínsecas é relativamente recente em COBOL, principalmente quando comparamos com a idade da linguagem e dos grandes sistemas que foram escritos com ela. Por esse motivo não é tão comum encontrar programas em sistemas legados que utilizem esse recurso. Conhecer algumas dessas funções, no entanto, pode facilitar significativamente o desenvolvimento de novos programas ou a manutenção de programas existentes.

O uso de funções intrínsecas

As funções no COBOL podem ser usadas em qualquer lugar onde você usaria uma variável ou um literal, principalmente em comandos de atribuição de valores, como MOVE e COMPUTE. A chamada a uma função segue o seguinte formato geral:

FUNCTION nome-da-funcao (argumento1 argumento2 ... argumentoN)

Os comandos abaixo são exemplos do uso de funções intrínsecas do COBOL:

MOVE FUNCTION LOWERCASE(“cobol”) TO WT-NM-LINGUAGEM
COMPUTE WT-RAIZ = FUNCTION SQRT(WT-QUADRADO)
COMPUTE WT-VALOR-PRESENTE = FUNCTION PRESENT-VALUE(0.03 WV-PRESTACAO(3))
DISPLAY FUNCTION MAX(WV-SAL(01), WV-SAL(02), WV-SAL(03))

Você pode separar os argumentos com vírgulas, mas isso não é obrigatório; deve haver pelo menos um espaço entre um argumento e outro.

A quantidade de argumentos varia de função para função. Existem funções que não precisam de nenhum, outras que esperam uma quantidade fixa de argumentos e outras ainda que podem receber uma quantidade variável de argumentos.

Um argumento pode ser um literal, uma variável, uma ocorrência de uma tabela interna, uma tabela interna inteira (todas as ocorrências), uma expressão aritmética, um literal numérico ou alfanumérico, constantes figurativas, variáveis de sistema e até mesmo o resultado de outra função.

Nem todos os compiladores, porém, trabalham com todos os tipos de argumentos. O Enterprise COBOL for Z/OS, por exemplo, aceita todos eles. O GNU COBOL não.

O tipo de argumento que você pode fornecer para uma função depende da própria função. Algumas só aceitam argumentos numéricos, outras só alfanuméricos. Além disso, algumas funções exigem um range específico de valores para seus argumentos. Por exemplo, algumas funções que tratam datas só aceitam argumentos com valores entre 01/01/1601 e 31/12/9999.

Obtendo o caracter correspondente a um inteiro

A função CHAR retorna o caracter correspondente à posição ordinal informada via argumento:

FUNCTION CHAR(inteiro)

Por exemplo, se o programa está rodando num ambiente que usa a tabela ASCII como referência e você quer recuperar a 66a posição dessa tabela você deve informar

FUNCTION CHAR(66)

Mas há um detalhe importante: a tabela ASCII começa com o inteiro zero, logo a 66a posição da tabela corresponde ao caracter ASCII 65, que equivale à letra “A”. Em outras palavras, se você quiser o caracter correspondente ao ASCII n, deve informar n + 1 como argumento da função CHAR.

Se você informar como argumento um número menor que zero ou maior que 255, o COBOL retornará um caracter nulo (x’00’).

Maiúsculas e minúsculas

No capítulo sobre manipulação de strings vimos que o comando…

INSPECT WT-VARIAVEL
     CONVERTING “abcdefghijklmnopqrstuvwxyz”
             TO “ABCDEFGHIJKLMNOPQRSTUVWXYZ”

…converte de minúsculo para maiúsculo todos os caracteres do conteúdo da variável WT-VARIAVEL. Uma outra forma de fazer isso seria através da função UPPER-CASE. O comando abaixo teria o mesmo efeito:

MOVE FUNCTION UPPER-CASE(WT-VARIAVEL) TO WT-VARIAVEL

No sentido contrário, é possível também converter uma string de maiúsculas para minúsculas usando a função LOWER-CASE:

MOVE FUNCTION LOWER-CASE(WT-VARIAVEL) TO WT-VARIAVEL

Invertendo o conteúdo de strings

A função REVERSE gera uma string com o mesmo tamanho do argumento mas com os caracteres em ordem invertida. O comando…

MOVE FUNCTION REVERSE(“COBOL”) TO WT-REVERSE

…armazenaria “LOBOC” na variável WT-REVERSE.

Cobol: Para que serve a função que inverte strings

Eliminando espaços em strings

A função TRIM está disponível em alguns compiladores, como o GNU COBOL, mas não em todos. Essa função remove espaços à esquerda de uma string armazenada numa variável alfanumérica:

MOVE FUNCTION TRIM(WT-TEXTO) TO WT-TEXTO

Outra forma de obter data e hora correntes

Em alguns exemplos dos capítulos anteriores, sempre que precisamos obter a data e a hora corrente do sistema nós usamos os comandos ACCEPT … FROM DATE e ACCEPT … FROM TIME.

Uma outra forma de se obter essas informações é através da função CURRENT-DATE. Esta função não precisa de argumentos:

FUNCTION CURRENT-DATE

Na prática, você usa essa função num comando MOVE para preencher o conteúdo de um item de grupo:

MOVE FUNCTION CURRENT-DATE TO item-de-grupo

O item de grupo de destino possui a seguinte estrutura:

01 item-de-grupo.
    03 ano                  PIC 9(004).
    03 mes                  PIC 9(002).
    03 dia                  PIC 9(002).
    03 hora                 PIC 9(002).
    03 minuto               PIC 9(002).
    03 segundo              PIC 9(002).
    03 centésimo-segundo    PIC 9(002).
    03 diferenca-greenwich  PIC X(005).

Além de mostrar data/hora num só campo, essa função também retorna mais informações, tais como o ano com quatro dígitos, os centésimos de segundos da hora corrente e a diferença para a hora padrão de Greenwich, que virá no formato –HHMM ou +HHMM.

Muitas vezes queremos apenas uma parte da data/hora padrão, e o uso de funções intrínsecas nos permite usar modificadores de referencia para isso. Por exemplo, se precisamos apenas da data corrente podemos criar um item de grupo para a data e mover parte da CURRENT-DATE para ela:

01 WT-DATA.
    03 WT-ANO         PIC 9(004).
    03 WT-MES          PIC 9(002).
    03 WT-DIA          PIC 9(002).

...

MOVE FUNCTION CURRENT-DATE(1:8) TO WT-DATA

O exemplo acima preenche a variável WT-DATA com as oito primeiras posições do CURRENT-DATE, que correspondem exatamente a ano (com quatro dígitos), mês e dia.

Também poderíamos fazer:

01 WT-HORA.
    03 WT-HOR         PIC 9(002).
    03 WT-MIN          PIC 9(002).
    03 WT-SEG          PIC 9(002).

...

MOVE FUNCTION CURRENT-DATE(9:6) TO WT-HORA

Nesse caso, estamos movendo apenas a substring que começa na posição 9, tem 6 bytes de extensão, e correspondem exatamente a hora, minuto e segundo.

Somando dias a datas

Toda empresa tem uma subrotina, normalmente escrita em COBOL, para somar (ou subtrair) dias a uma determinada data. As datas em COBOL, como já vimos, normalmente são variáveis numéricas de 6 ou 8 dígitos, logo construir uma rotina para operações com datas exigia um monte de transformações de dados e operações aritméticas.

Com as funções intrínsecas tudo isso se reduziu a dois comandos:

COMPUTE inteiro = FUNCTION INTEGER-OF-DATE(data) + dias
MOVE FUNCTION DATE-OF-INTEGER(inteiro) TO data-nova

O primeiro comando usa a função INTEGER-OF-DATE, que transforma uma data gregoriana em um número inteiro que representa a quantidade de dias transcorridos desde 31/12/1600. O valor máximo permitido para o argumento é 31/12/9999, logo o maior valor retornado por essa função é 3.067.671, um número de 7 dígitos. O argumento data pode ser um literal numérico ou uma variável numérica de 8 dígitos que represente uma data no formato AAAAMMDD.

Depois de somar (ou subtrair) uma quantidade de dias a esse inteiro podemos transformá-lo novamente numa data gregoriana com a função DATE-OF-INTEGER. Essa função retornará um valor numérico que representa uma data também no formato AAAAMMDD. Qualquer argumento menor que 1 ou maior que 3.067.671 fará com que a função retorne zero.

Calculando a diferença entre datas

A função INTEGER-OF-DATE ajuda também quando precisamos calcular a quantidade de dias entre duas datas.

COMPUTE NR-DIAS = FUNCTION INTEGER-OF-DATE(DT-FINAL)
                - FUNCTION INTEGER-OF-DATE(DT-INICIAL)

Ainda sobre o bug do milênio

Nos anos 1950, 1960 e 1970, memória e espaço de armazenamento eram escassos e caros, e toda oportunidade para economizar esses recursos era aproveitada.

Por esse motivo, datas normalmente eram armazenadas em campos com 6 dígitos, no formato AAMMDD. À medida que a virada do século foi se aproximando, lá pelos anos 1990, as consequências disso começaram a ficar evidentes: qualquer operação ou comparação entre datas deixaria de funcionar, pois 1o de Janeiro de 2000 (000101) se tornaria uma data anterior a 31 de dezembro de 1999 (991231).

Nos grandes sistemas, o custo para expandir milhares de datas em arquivos permanentes ou transitórios e todas as variáveis de trabalho correspondentes em dezenas de milhares de programas e milhões de linhas de código era proibitivo. Uma das soluções mais comuns nessa época ficou conhecida como janelamento de datas.

Tecnicamente o janelamento era uma solução muito simples: bastava criar uma subrotina que recebesse uma data com seis dígitos, comparasse o ano dessa data com um ano pivot (digamos, 50), decidir se a data pertencia ao século XX ou ao século XXI e retornar uma data com oito dígitos com um 19 ou um 20 na frente do ano. O ano pivot estabelecia o “corte” do século: 19/08/03 seria tratado como 19/08/2003, enquanto 03/09/67 seria tratado como 03/09/1967.

A natureza da data é que determinava o ano pivot ideal. Uma data de nascimento, por exemplo, era transformada com um ano pivot diferente de uma data de emissão de fatura ou data de pagamento.

Um das funções intrínsecas do COBOL/2002 faz justamente isso, dispensando a necessidade de se construir uma subrotina para essa finalidade.

A função DATE-TO-YYYYMMDD recebe uma data numérica de 6 dígitos no formato AAMMDD e, opcionalmente, um ano pivot, que pode ser uma variável ou um literal numérico. Assim, por exemplo…

MOVE FUNCTION DATE-TO-YYYYMMDD(150819) TO WT-DATA

…preencheria a variável WT-DATA com 20150817, pois 15 é menor que 50, o ano pivot assumido por default. Já…

MOVE FUNCTION DATE-TO-YYYYMMDD(970819, 70) TO WT-DATA

…preencheria WT-DATA com 19970817, pois 97 é maior que o ano pivot informado (70).

Existe outra função similar, YEAR-TO-YYYY que faz a mesma operação transformando um ano com dois dígitos num ano com quatro dígitos, a partir de um ano pivot informado. O comando abaixo converte a variável numérica de dois dígitos WT-AA num ano de quatro dígitos que será armazenado na variável WT-AAAA usando 70 como ano pivot:

MOVE FUNCTION YEAR-TO-YYYY(WT-AA, 70) TO WT-AAAA

Os sistemas que passaram pela correção do bug milênio estão rodando até hoje e continuarão rodando por muito tempo ainda. Fatalmente você precisará processar uma dessas datas de seis dígitos e aplicar uma técnica de janelamento. A empresa em que você estiver trabalhando certamente terá uma subrotina para isso, mas talvez você possa aplicar essas soluções, que são bem mais simples.

Obtendo informações sobre o programa

Algumas funções bem interessantes fornecem informações sobre o programa em tempo de execução.  Você pode, por exemplo, recuperar o nome do programa que está sendo executado para padronizar cabeçalhos em telas e relatórios:

MOVE FUNCTION MODULE-ID TO WR-CAB-NM-PROGRAMA

Você também pode obter a data e hora em que o programa foi compilado. Isso pode ser útil na análise de erros reportados pelos usuários:

DISPLAY “VERSAO GERADA EM “ FUNCTION MODULE-FORMATTED-DATE

Ou a localização do programa que está sendo executado:

DISPLAY “PROGRAMA EXECUTADO NO DIRETORIO “ FUNCTION MODULE-PATH

Ou ainda o nome do programa fonte que deu origem ao programa que está sendo executado:

DISPLAY “FONTE “ FUNCTION MODULE-SOURCE

E até mesmo o nome do programa que chamou o subprograma que está sendo executado no momento. Essa função retornará um string nulo se o programa que está sendo executado for um programa principal:

DISPLAY “CHAMADO POR “ FUNCTION MODULE-CALLER-ID

Vale destacar que essas funções não fazem parte das 42 funções obrigatórias da revisão COBOL/2002. Mas elas estão disponíveis em alguns compiladores, como o GNU COBOL.

Inteiros de números decimais

A função INTEGER retornar o maior valor inteiro que é menor ou igual ao argumento informado. Por exemplo, FUNCTION INTEGER(2.5) retornaria 2, enquanto FUNTION INTEGER(-3.5) retornaria -4.

Isso, obviamente, não é a mesma coisa que “truncar” um número decimal. Para isso existe outra função, chamada INTEGER-PART. FUNCTION INTEGER-PART(2.5) retornaria 2, mas FUNCTION INTEGER-PART(-3.5) retornaria -3.

Vale a pena destacar que…

    MOVE FUNCTION INTEGER-PART(NUMERO-DECIMAL) TO NUMERO-INTEIRO

…é a mesma coisa que…

    MOVE NUMERO-DECIMAL TO NUMERO-INTEIRO

…desde que NUMERO-DECIMAL seja uma variável numérica com casas decimais e NUMERO-INTEIRO seja uma variável numérica sem casas decimais.

Calculando fatoriais

Na computação o cálculo de fatoriais é muito usado para demonstrar a aplicação de funções recursivas, como no clássico exemplo abaixo, escrito em C:

int fatorial (int numero) {
    return numero == 0 ? 1 : numero * fatorial(numero - 1);
}

No entanto, existem muitos sistemas escritos em COBOL (sistemas atuariais para previdência ou sistemas de sinistralidade para seguradoras, por exemplo) que precisam calcular probabilidades e permutas, onde o fatorial de números inteiros é essencial.

Uma das funções intrínsecas do COBOL oferece uma solução para esse tipo de cálculo. O exemplo abaixo calcula o fatorial do número armazenado na variável NUMERO e move o resultado para a variável FATORIAL.

MOVE FUNCTION FACTORIAL(NUMERO) TO FATORIAL

Essa função respeita o axioma matemático sobre o fatorial de zero. Logo, FUNCTION FACTORIAL(0) retornaria 1.

Resto e módulo de divisão

Quando precisamos obter o resto de uma divisão normalmente usamos a opção REMAINDER do comando DIVIDE:

DIVIDE DIVIDENDO
    BY DIVISOR
    GIVING QUOCIENTE
    REMAINDER RESTO

A função REM fornece o mesmo resultado e é a solução ideal quando não precisamos do quociente da divisão:

MOVE FUNCTION REM(DIVIDENDO DIVISOR) TO RESTO

Ela também tem a vantagem de dispensar uma operação desnecessária quando queremos apenas o resto da divisão, como no exemplo abaixo:

IF FUNCTION REM(ANO 4) = ZEROS
   comandos
END-IF

Muita gente usa a função módulo para obter o resto de uma divisão, que fornece o mesmo resultado que a função REM quando estão envolvidos apenas números positivos:

MOVE FUNCTION MOD(DIVIDENDO DIVISOR) TO RESTO

Mas os resultados são diferentes quando o dividendo ou o divisor são negativos, e essa diferença é provocada pela própria definição das funções:

FUNCTION MOD(DIVIDENDO DIVISOR) =
         DIVIDENDO – (DIVISOR * FUNCTION INTEGER(DIVIDENDO / DIVISOR)


FUNCTION REM(DIVIDENDO DIVISOR) =
         DIVIDENDO – (DIVISOR * FUNCTION INTEGER-PART(DIVIDENDO / DIVISOR)

Como já vimos neste mesmo capítulo, a função INTEGER retorna o maior inteiro que é menor ou igual ao argumento: FUNCTION INTEGER(-3.5) é igual a -4. Já a função INTEGER-PART simplesmente trunca a parte decimal: FUNCTION INTEGER-PART(-3.5) é igual a -3.

Raiz quadrada

A função SQRT retorna a raiz quadrada de um número positivo, como no exemplo abaixo:

MOVE FUNCTION SQRT(NUMERO) TO RAIZ

Mas vale destacar que você pode calcular qualquer raiz em COBOL usando o comando COMPUTE e uma propriedade da radiciação:

Logo, o comando abaixo traria o mesmo resultado:

COMPUTE RAIZ = NUMERO ** (1 / 2)

E esse formato pode ser extrapolado para qualquer índice do radical:

COMPUTE RAIZ = NUMERO ** (1 / INDICE)

Somatórios

A função SUM retorna a soma dos argumentos fornecidos. Se todos os argumentos forem inteiros, a função retornará um valor inteiro. Se um ou mais argumentos tiverem casas decimais, o retorno terá decimais.

FUNCTION SUM(argumento1 argumento2 ... argumentoN)

Essa função é especialmente prática quando queremos somar todas as ocorrências de uma tabela interna. Normalmente faríamos um loop para obter o somatório. Por exemplo:

01 WV-VALORES.
    03 WV-VALOR        PIC 9(006)V9(002) OCCURS 100.
    03 WT-SOMATORIO    PIC 9(006)V9(002) VALUE ZEROS.

...

MOVE ZEROS TO WT-SOMATORIO
PERFORM VARYING WV-IND FROM 1 BY 1 UNTIL WV-IND > 100
   ADD WV-VALOR(WV-IND) TO WT-SOMATORIO
END-PERFORM

Com a função SUM a construção é mais simples:

MOVE FUNCTION SUM(WV-VALOR(ALL)) TO WT-SOMATORIO

Essa notação, com a palavra reservada ALL, não está disponível em todos os compiladores.

Logaritmos

O uso de logaritmos em sistemas comerciais pode ser encontrado, principalmente, em aplicações do sistema financeiro. Logaritmos são essenciais para responder a perguntas do tipo quanto tempo (T) levará para que uma aplicação inicial X a Y% de juros chegue ao valor Z?

Para responder a essa pergunta a matemática financeira nos ensina que precisamos calcular:

T = LOG (Z / X) / LOG (1 + Y)

O COBOL possui a função LOG para calcular logaritmo neperiano e LOG10 para calcular logaritmo na base 10. Assim, poderíamos responder à pergunta acima codificando alguma coisa parecida com:

COMPUTE VALOR-FUTURO = VALOR-FUTURO / VALOR-PRESENTE
ADD 1 TO TAXA-JUROS
COMPUTE TEMPO = FUNCTION LOG(VALOR-FUTURO) / FUNCTION LOG(TAXA-JUROS)

…onde as variáveis VALOR-FUTURO e VALOR-PRESENTE devem estar com valores maiores que zero e tanto elas quanto as variáveis TAXA-JUROS e TEMPO devem ser numéricas.

Valores máximos e mínimos

As funções MAX e MIN retornam o conteúdo do argumento que contém, respectivamente, o maior e o menor valor. Seus formatos são muito simples:

FUNCTION MAX(argumento1 argumento2 ... argumentoN)
FUNCTION MIN(argumento1 argumento2 ... argumentoN)

É possível passar uma tabela interna inteira como argumento. O exemplo abaixo, move para a variável WT-MAIOR-VALOR o maior conteúdo existente numa das ocorrências da tabela interna WV-VALOR:

MOVE FUNCTION MAX(WV-VALOR(ALL)) TO WT-MAIOR-VALOR

Existem compiladores que não oferecem esse recurso, mas ele pode ser facilmente implementado através de um loop simples. No exemplo abaixo, WV-IX é o indexador da tabela interna WV-VALOR e NNN o número de ocorrências dessa tabela. O loop compara cada ocorrência da tabela ao maior valor apurado até o momento. O campo WT-MAIOR-VALOR começa com LOW-VALUES (o menor valor possível, que corresponde ao hexadecimal x’00’) para que qualquer ocorrência da tabela interna seja maior que esse valor inicial.

MOVE LOW-VALUES TO WT-MAIOR-VALOR
PERFORM VARYING WV-IX FROM 1 BY 1 UNTIL WV-IX > NNN
   MOVE FUNCTION MAX(WT-MAIOR-VALOR WV-VALOR(WV-IX)) TO WT-MAIOR-VALOR
END-PERFORM

Para obter o menor valor, podemos fazer como no exemplo abaixo. A variável WT-MENOR-VALOR começa com HIGH-VALUES (o maior valor possível, que corresponde ao hexadecimal x’FF’) para que qualquer ocorrência da tabela seja menor que esse valor inicial:

MOVE HIGH-VALUES TO WT-MENOR-VALOR
PERFORM VARYING WV-IX FROM 1 BY 1 UNTIL WV-IX > NNN
   MOVE FUNCTION MIN(WT-MENOR-VALOR WV-VALOR(WV-IX)) TO WT-MENOR-VALOR
END-PERFORM

Quando precisamos saber a posição ordinal do argumento de maior ou menor valor (e não seu conteúdo) usamos duas outras funções: ORD-MAX e ORD-MIN. Por exemplo:

           MOVE 07 TO WV-VALOR(01)
           MOVE 69 TO WV-VALOR(02)
           MOVE 13 TO WV-VALOR(03)
           MOVE 25 TO WV-VALOR(04)
           MOVE 33 TO WV-VALOR(05)

           DISPLAY "ORD-MAX: " FUNCTION ORD-MAX(WV-VALOR(1)
                                                WV-VALOR(2)
                                                WV-VALOR(3)
                                                WV-VALOR(4)
                                                WV-VALOR(5))

…mostraria…

ORD-MAX: 2

…pois o maior valor está no segundo argumento da lista fornecida.

Medidas de posição e dispersão

Existem algumas funções específicas que facilitam bastante o cálculo de medidas de posição (média, mediana, moda…) e dispersão (amplitude, variância, desvio padrão…) em aplicações que trabalham com estatística.

É possível, por exemplo, calcular a média simples de um conjunto de valores armazenados numa tabela interna:

COMPUTE WT-MEDIA = FUNCTION MEAN(WT-VALORES(ALL))

Essa função pode ser usada também para calcular a média de uma quantidade menor de argumentos, mas seu benefício em termo de simplicidade de código não é tão evidente pois poderia ser facilmente substituída por um comando COMPUTE. Os dois comandos abaixo dariam o mesmo resultado:

COMPUTE WT-MEDIA = FUNCTION MEAN(WT-VALOR1 WT-VALOR2 WT-VALOR3)

COMPUTE WT-MEDIA = (WT-VALOR1 + WT-VALOR2 + WT-VALOR3) / 3

Para calcular a mediana precisaríamos construir um algoritmo para ordenar os valores e encontrar o valor na posição central, como pede a Estatística. Com a função MEDIAN esse algoritmo não é necessário:

COMPUTE WT-MEDIANA = FUNCTION MEDIAN(WT-VALORES(ALL))

Para amplitude e amplitude média temos as funções RANGE e MIDRANGE:

MOVE FUNCTION RANGE(WT-VALORES(ALL)) TO WT-AMPLITUDE

MOVE FUNCTION MIDRANGE(WT-VALORES(ALL) TO WT-AMPLITUDE-MEDIA

O desvio padrão é calculado pela função abaixo, que segue o mesmo formato das funções anteriores:

COMPUTE WT-DESVIO = FUNCTION STANDARD-DEVIATION(WT-VALORES(ALL))

Ou…

COMPUTE WT-DESVIO = FUNCTION STANDARD-DEVIATION (WT-VALOR1
                                                 WT-VALOR2
                                                 WT-VALOR3
                                                 WT-VALOR4
                                                 WT-VALOR5)

O cálculo da variância também é facilitado com uma função intrínseca:

MOVE FUNCTION VARIANCE(WT-VALORES(ALL)) TO WT-VARIANCIA

Gerando números aleatórios

A função RANDOM retorna um valor numérico aleatório entre 0 e 1, como por exemplo 0,125759302. Podemos combinar essa função com operações aritméticas para gerar números inteiros. O exemplo abaixo gera um número aleatório inteiro de 5 dígitos:

COMPUTE WT-ALEATORIO = FUNCTION RANDOM * 10000

Números aleatórios podem ser importantes tanto para a geração de massas de testes quanto para o cálculo de medidas de incerteza, na Estatística. Mas, a rigor, não existem números aleatórios em computação; um algoritmo executado várias vezes com os mesmos dados de entrada produzirá sempre os mesmos resultados. Por esse motivo muitas vezes nos referimos a esses números randômicos como “números pseudoaleatórios”.

Para ilustrar esse conceito, imagine a seguinte sequência de comandos:

COMPUTE NUMERO(1) = FUNCTION RANDOM * 10000
COMPUTE NUMERO(2) = FUNCTION RANDOM * 10000
COMPUTE NUMERO(3) = FUNCTION RANDOM * 10000

Ao executar esse código pela primeira vez você terá três números pseudoaleatórios, digamos 08401, 03943 e 07830. Ao executá-lo uma segunda vez os três números serão os mesmos, 08401, 03943 e 07830. E serão sempre três números toda vez que esse algoritmo for executado.

Um meio muito usado para tornar esses números “mais aleatórios” é alimentar o algoritmo com informações diferentes a cada execução. Na função RANDOM isso é feito com um argumento, opcional, chamado seed ou semente. Normalmente usamos o próprio relógio do sistema para nos fornecer sementes diferentes para a função.

Melhorando o exemplo anterior, podemos fazer…

ACCEPT WT-SEED FROM TIME
COMPUTE NUMERO(1) = FUNCTION RANDOM(SEED) * 10000
COMPUTE NUMERO(2) = FUNCTION RANDOM * 10000
COMPUTE NUMERO(3) = FUNCTION RANDOM * 10000

A primeira chamada à função RANDOM forneceu uma semente gerada com a hora corrente do sistema, composta por hora, minuto, segundo e décimo de segundo (WT-SEED foi criada como uma variável 9(008)). Com isso, cada execução do programa irá gerar uma sequência diferente de três números, a menos que você execute esse programa no dia seguinte na mesma hora, minuto, segundo e décimo de segundo.

Repare que você só precisa fornecer a seed na primeira chamada à função RANDOM pois é nessa chamada que a sequência de números pseudoaleatórios é gerada. As demais, sem argumentos, apenas recuperam o próximo número da sequência. Se você fornecer uma mesma seed a cada chamada da função, os três números serão iguais.

A semente pode ser qualquer número inteiro maior ou igual a zero e menor ou igual a 2.147.483.645.

Calculando o valor presente

Calcular o valor presente de uma série de pagamentos futuros é uma funcionalidade comum em sistemas financeiros. O valor presente é dado pela a seguinte fórmula:

Onde VP é o valor presente, VF é o fator futuro, i é a taxa de juros a ser aplicada num período e n a quantidade de períodos (dias, meses, anos…) decorridos até o valor futuro. Em COBOL escreveríamos assim:

COMPUTE VP = VF / ((1 + I) ** N)

Muitas vezes o que queremos é o valor presente total de uma série de pagamentos futuros. Para calcular, por exemplo, o valor presente de uma série de cinco pagamentos mensais de R$ 1.000, a uma taxa de 10% ao mês, devemos aplicar a fórmula acima em cada uma das parcelas e somar os resultados:

Cobol: Cálculo do valor presente
Figura 67. Cálculo do valor presente de uma série de cinco pagamentos futuros

Em COBOL, definiríamos uma tabela interna para armazenar os valores futuros e escreveríamos um loop como no exemplo abaixo:

01 VALOR-PRESENTE   PIC 9(006)V9(002) VALUE ZEROS.
01 TAXA-JUROS       PIC 9(001)V9(003) VALUE 0,10.
01 VALORES-FUTUROS.
   03 VALOR-FUTURO PIC 9(006)V9(002) OCCURS 5 INDEXED BY PERIODO.

...

MOVE ZEROS TO VALOR-PRESENTE
PERFORM VARYING PERIODO FROM 1 BY 1 UNTIL PERIODO > 5
   COMPUTE VALOR-PRESENTE = VALOR-PRESENTE +
       (VALOR-FUTURO(PERIODO) / ((1 + TAXA-JUROS) ** PERIODO))
END-PERFORM

A função PRESENT-VALUE simplifica esse processo:

MOVE FUNCTION PRESENT-VALUE(TAXA-JUROS VALOR-FUTURO(ALL))
     TO VALOR-PRESENTE

Calculando o valor de parcelas fixas

A função ANNUITY é importante quando precisamos responder à seguinte pergunta: qual deve ser o valor da parcela que deve ser paga a cada período para remunerar um empréstimo a determinada taxa de juros?

Ela recebe dois argumentos: a taxa de juros cobrada por período e a quantidade de períodos:

FUNCTION ANNUITY(juros períodos)

Por exemplo, suponha que você precise calcular um valor de parcela fixo para um empréstimo de R$ 10.000,00 que serão pagos em 36 parcelas fixas mensais. A taxa esperada de juros é de 2% ao mês. Para calcular o valor da parcela você faria:

COMPUTE PARCELA = 10000 * FUNCTION ANNUITY(0.02 36)

Neste exemplo, o valor da parcela seria R$ 392,32. Se você trouxer cada uma das 36 parcelas de R$ 393,32 ao valor presente, verá que a soma desses valores será de R$ 10.000.

Naturalmente, no comando acima todos os literais numéricos poderiam ter sido substituídos por variáveis, e isso vale para todas as funções que vimos nesse capítulo.


Anterior Conteúdo Próxima