Возвращение результатов из потока с помощью глобальных переменных

Рейтинг: 1Ответов: 2Опубликовано: 30.10.2014

Почему следующий код возвращает 0:

using System;
using System.Threading;

 class Program 
 {

   static long Factorial(long n) 
   { 
     long res = 1; 
     do 
     { 
       res = res * n; 
     } while(--n > 0); 
     return res; 
   }

   static void Main() 
   {   
     long res1 = 0, res2 = 0;

     long n1 = 5000, n2 = 10000;

     Thread t1 = new Thread(() => 
       { 
         res1 = Factorial(n1); 
       });

     Thread t2 = new Thread(() => { res2=Factorial(n2); });

     // Запускаем потоки 
     t1.Start();  t2.Start();

     // Ожидаем завершения потоков 
     t1.Join();  t2.Join();

     Console.WriteLine("Factorial of {0} equals {1}", n1, res1); 
     Console.WriteLine("Factorial of {0} equals {1}", n2, res2); 
   }

 }

Вывод:

Factorial of 5000 equals 0
Factorial of 10000 equals 0

Ответы

▲ 4Принят

Факториал от 5000 - это оооооооочень большое число. Скорее всего, во вселенной даже нет столько атомов. long хоть и позволяет представлять достаточно большие числа, но все же тут вы явно перегнули палку. В один прекрасный момент возникает переполнение, вследствие чего переменная res становится то отрицательной, то положительной, В итоге, когда n становится равной 4938 возникает ситуация, когда произведение res*n становится равным нулю (опять же из-за некорретного результата вследстиве переполнения. В действительности же это произведение будет очень большим по модулю отрицательным числом).

Как известно, в C# из соображений производительности отключена проверка переполнения результатов математических операций (таких операций в программе бывает огромное количество, и выполнять такую проверку с выбросом искючения очень накладно, это при том, что само переполнение возникает крайне редко). Включить такую проверку можно, пометив необходимый участок кода блоком checked { }. Например так:

static long Factorial(long n)
{
    long res = 1;                
    do
    {
        checked 
        {
            res = res*n;
        }
    } while (--n > 0);        
    return res;
}

В этом случае вы при переполнении будете получать исключение OverflowException. А вообще при работе с подобными числами стоит использовать специальный структуру BigInteger из сборки в System.Numerics.dll. Операции с ее экземплярами выполняются медленнее, чем с обычными int или long, но зато они позволяют корректно представить необходимое число.

Вот ваш метод с использованием BigInteger:

static BigInteger Factorial(long n)
{
    BigInteger res = 1;
    do
    {
        res = res*n;
    } while (--n > 0);
    return res;

}

а вот результат при n == 5000. Выглядит внушительно:

alt text

▲ 1

В дополнение к правильному ответу @DreamChild: не стоит пользоваться устаревшими потоками, и тем более передачей результатов через глобальные переменные. Пользуйтесь уже ставшим стандартным async/await:

class Program
{
    static Task<BigInteger> ComputeFactorial(long n)
    {
        return Task.Run(() =>
            {
                BigInteger result = 1;
                for (long i = 2; i < n; i++)
                    result *= i;
                return result;
            });
    }

    static async Task Run()
    {
        var t1 = ComputeFactorial(5000);
        var t2 = ComputeFactorial(10000);

        Console.WriteLine(await t1);
        Console.WriteLine(await t2);
    }

    static void Main(string[] args)
    {
        Run().Wait();
    }
}