C# - Tudo certo como dois e dois são cinco


 Neste artigo vou mostrar como a aritmética do computador difere da aritmética da escola, para que você possa evitar os problemas resultantes e não passar vexame.


...

Meu amor
Tudo em volta está deserto tudo certo
Tudo certo como dois e dois são cinco

...

(Caetano Veloso)

 

A forma como os computadores realizam cálculos pode ser surpreendentemente diferente de como todos nós fomos ensinados na escola.

Como resultado, programas que realizam até mesmo simples cálculos aritméticos podem gerar erros inesperados que podem causar embaraços ou mesmo catástrofes, dependendo da sua utilização.
 

Acompanhe o artigo e surpreenda-se com erros que você talvez nem imaginasse que fossem possíveis de ocorrer.


Recursos usados :

Criando o projeto no VS 2013

Abra o VS Community 2013 e clique em New Project;

A seguir selecione Other Project Types -> Visual Studio Solutions

Selecione o template Blank Solution e informe o nome Matematica_Binaria e clique no botão OK;

Vamos agora incluir dois projetos na nossa solução:

  1. Projeto usando a linguagem C# chamado Matematica_CSharp;

  2. Projeto usando a linguagem VB.NET chamado Matematica_VBNET;

No menu File clique em Add -> New Project;

A seguir selecione Visual C# -> Windows Desktop e o template Console Application;

Informe o nome Matematica_CSharp e clique no botão OK:

Repetindo o procedimento...

No menu File clique em Add -> New Project;

A seguir selecione Visual Basic -> Windows Desktop e o template Console Application;

Informe o nome Matematica_VBNET e clique no botão OK:

Teremos assim uma solução com dois projetos que iremos usar para criar os exemplos do artigo.

   Começando com adição de inteiros

Vamos começar com a adição de inteiros.  Qual o resultado seria esperado pela execução do código abaixo em VB .NET e C# ?

 Sub Main()
        Dim x As Integer = 2
        Dim y As Integer = 2
        Dim z As Integer = x + y
        Console.WriteLine("CSharp => " + z)
        Console.ReadKey()
    End Sub

 

 static void Main(string[] args)
 {
            int x = 2;
            int y = 2;
            int z = x + y;

            Console.WriteLine("VB .NET => " + z.ToString());
            Console.ReadKey();
}
VB .NET C#

Se você pensou número 4 como resultado. Acertou !

Em ambos os código o resultado da soma dos inteiros 2 e 2 é igual ao inteiro 4.  Mas seria isso válido para qualquer inteiro ?

Vejamos então o exemplo abaixo. Qual o resultado você espera ?

Ora se 2 mais dois é igual a 4 então 2000000000 mais 2000000000 seria igual a 4000000000. Correto !!!

 Sub Main()
        Dim x As Integer = 2000000000;
        Dim y As Integer = 2000000000;
        Dim z As Integer = x + y
        Console.WriteLine("CSharp => " + z)
        Console.ReadKey()
    End Sub

 

 static void Main(string[] args)
 {
            int x = 2000000000;
            int y = 2000000000;
            int z = x + y;
            
            Console.WriteLine("VB .NET => " + z.ToString());
            Console.ReadKey();
}
VB .NET C#

Na linguagem  VB .NET irá ocorrer uma mensagem de erro do tipo : An unhandled exception of type 'System.OverflowException' occurred in Matematica_VBNET.exe

Na linguagem C# você obterá o seguinte resultado :

Isso mesmo . O valor exibido é -294967296.

Mas de onde vem esse número mágico ??

Ocorre que tanto na linguagem VB .NET como na linguagem C# existe um limite mínimo e máximo para números inteiros.

Podemos obter esses valores usando os métodos MinValue() e MaxValue() para o tipo inteiro, tanto em VB .NET como em C#:

 Sub Main()
        Dim x As Integer = 2000000000
        Dim y As Integer = 2000000000
        Console.WriteLine("Inteiro Minimo => " & Integer.MinValue)
        Console.WriteLine("Inteiro Maximo => " & Integer.MaxValue)
        Console.ReadKey()
        Dim z As Integer = x + y
        Console.WriteLine("VB .NET => " + z.ToString())
        Console.ReadKey()
    End Sub

 

static void Main(string[] args)
{
            int x = 2000000000;
            int y = 2000000000;
            int z = x + y;
            Console.WriteLine("CSharp Inteiro Mínimo => " + int.MinValue);
            Console.WriteLine("CSharp Inteiro Máximo => " + int.MaxValue);
            
            Console.WriteLine("CSharp => " + z);
            
            Console.ReadKey();
}
VB .NET C#

O resultado obtido serão , tanto para VB .NET como para C#, os valores : -2147483648 e 2147483647

Esses são os maiores e os menores números inteiros possíveis no sistema. (Lembre que um número inteiro pode ser representado em 32 bits de memória.)

Mas como explicar o esdrúxulo resultado obtido na linguagem C# :  -294967296.

Na aritmética dos inteiros, usando a linguagem C#, quando o valor máximo é ultrapassado, então você começa de novo a partir do menor número inteiro possível.

Veja esse comportamento no exemplo abaixo onde vamos somar o inteiro 2147483645 com 5. Isso deveria resultar no número 2147483650 mas esse número ultrapassa o valor máximo permitido que é 2147483647.

static void Main(string[] args)
        {
            int x = 2147483645;
                   //2147483647 (o valor máximo) 
            int y = 5;  
            int z = x + y;   //seria 2147483650
            Console.WriteLine("CSharp => " + z);
            Console.ReadKey();
        }

 

C#

O resultado obtido é:

Como estamos adicionando o valor 5 e o valor máximo é ultrapassado começamos novamente do valor mínimo que é -2147483648 e prosseguimos a operação obtendo o valor -2147483646.

Segundo esse comportamento você verá que a diferença entre os quatro bilhões e o maior número inteiro possível é exatamente o mesmo que a diferença entre o menor inteiro possível e nosso resultado de -294967296.

Por padrão, na linguagem C#, uma expressão que contém apenas valores constantes causa um erro de compilação se a expressão produz um valor que está fora do alcance do tipo de destino.

Se a expressão contém um ou mais valores não constantes, o compilador não detecta o estouro.

Para fazer com que a linguagem C# tenha o mesmo comportamento da linguagem VB .NET e lance uma exceção devemos usar a palavra reservada checked.

A palavra-chave checked é usada para ativar explicitamente o estouro da pilha, verificando assim se o valor máximo permitido para o tipo int em operações e conversões aritméticas foi ultrapassado, e lançando uma exceção.

   Divisão e multiplicação de inteiros

Vamos agora a um erro mais sutil mas não incomum.

Examine o código do exemplo abaixo e tente prever o resultado de 70% de 20000.

 Sub Main()

        Dim ValorOriginal As Integer = 20000
        Dim PercentualDesconto As Integer = 70
        Dim resultado As Integer = (PercentualDesconto / 100) * ValorOriginal

        Console.WriteLine(resultado)   //resultado = 14000
        Console.ReadKey()
 End Sub 
 static void Main(string[] args)
 {
            int ValorOriginal = 20000;
            int PercentualDesconto = 70;
            int resultado = (PercentualDesconto / 100) * ValorOriginal;
            Console.WriteLine(resultado); //resultado 0
            Console.ReadKey();
 }
VB .NET C#

O valor obtido para a linguagem VB .NET é 14000 e esta correto. Mas para a linguagem C# o valor obtido é zero.

Por quê ?

O resultado de uma divisão entre inteiros deve ser um inteiro.

Então, quanto é 70/100 ?

É evidente que não pode ser de 0,7, porque isso não é um número inteiro.

Quando a divisão entre inteiros não é um valor inteiro em C#, o resultado é sempre arredondado em direção a zero, não para o número inteiro mais próximo, de modo que o primeiro cálculo de 70/100 resultado em 0, o que é então multiplicado pelo valor original.

Você pode pensar que o código abaixo vai funcionar, pois evita o valor zero obtido na divisão:

int resultado = (PercentualDesconto * ValorOriginal) / 100;

E para este exemplo ele vai funcionar, mas se aumentarmos o valor da variável ValorOriginal continuamos com problemas.

Vejamos então o resultado obtido pelo código quando o valor de ValorOriginal é alterado para 200000000:

static void Main(string[] args)
{
            int ValorOriginal = 200000000;
            int PercentualDesconto = 70;
            //int resultado = (PercentualDesconto / 100) * ValorOriginal;
            int resultado = (PercentualDesconto * ValorOriginal) / 100;
            Console.WriteLine(resultado);
            Console.ReadKey();
 }
C#

O resultado é :

É óbvio que 11150981 não é 70% de 200.000.000.

Mais uma vez estamos tendo problemas com o arredondamento.

Quando multiplicamos 70 por 200.000.000 obtemo o valor 14.000.000.000 que muito maior que o maior número inteiro possível suportado pelo sistema.

A linguagem C# então efetua os ajustes até chegar ao valor 1115098112 que a seguir é dividido por 100.

Como eu disse esse é comportamento padrão da linguagem C# para alterá-lo basta usar a palavra-reservada checked.

Veja como ficaria nosso exemplo:

 static void Main(string[] args)
  {
            try
            {
                int ValorOriginal = 200000000;
                int PercentualDesconto = 70;
                int resultado = checked(PercentualDesconto * ValorOriginal) / 100;
                Console.WriteLine(resultado);
            }
            catch(OverflowException e)
            {
                Console.WriteLine(e.ToString());
            }
            Console.ReadKey();
 }
C#

Agora ao executar o resultado será:

Conclusão !

Usando C# você deve tomar muito cuidado quando realizar operações de divisão e multiplicação e mesmo de adição.

Jesus lhes respondeu, e disse: A minha doutrina não é minha, mas daquele que me enviou.
Se alguém quiser fazer a vontade dele, pela mesma doutrina conhecerá se ela é de Deus, ou se eu falo de mim mesmo.
Quem fala de si mesmo busca a sua própria glória; mas o que busca a glória daquele que o enviou, esse é verdadeiro, e não há nele injustiça.

João 7:16-18

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

Referências:


José Carlos Macoratti