2015-03-28 4 views
2

Может быть, я не понял, как использовать эти два типа: BigInteger/BigRational, но, вообще говоря, я хочу, чтобы реализовать эти уравнения: enter image description hereBigInteger/BigRational проблемы с преобразования в два раза и обратно

Это мои данные: n = 235, K = 40, и этот маленький p (который на самом деле называется rho) равен 5. В начале проблема была связана с функцией Power: результаты были очень очень большими - поэтому я использовал библиотеку BigInteger. Но потом я понимаю, что будет сделано разделение, и результат будет несколько типа double - поэтому я перешел на библиотеку BigRational.

Вот мой код:

static void Main(string[] args) 
    { 
     var k = 40; 
     var n = 235; 
     var p = 5; 

     // the P(n) equation 
     BigRational pnNumerator = BigRational.Pow(p, n); 
     BigRational pnDenominator = BigRational.Pow(k, (n - k)) * Factorial(k); 


     // the P(0) equation 

     //---the right side of "+" sign in Denominator 
     BigRational pk = BigRational.Pow(p, k); 
     BigRational factorialK = Factorial(k); 
     BigRational lastPart = (BigRational.Subtract(1, (double)BigRational.Divide(p, k))); 
     BigRational factorialAndLastPart = BigRational.Multiply(factorialK, lastPart); 
     BigRational fullRightSide = BigRational.Divide(pk, factorialAndLastPart); 
     //---the left side of "+" sign in Denominator 
     BigRational series = Series(k, p, n); 


     BigRational p0Denominator = series + fullRightSide; 
     BigRational p0Result = BigRational.Divide(1, p0Denominator); 

     BigRational pNResult = BigRational.Divide((pnNumerator * p0Result), pnDenominator); 
     Console.WriteLine(pNResult); 
     Console.ReadKey(); 
    } 

    static BigRational Series(int k, int p, int n) 
    { 
     BigRational series = new BigRational(0.0); 
     var fin = k - 1; 
     for (int i = 0; i < fin; i++) 
     { 
      var power = BigRational.Pow(p, i); 
      var factorialN = Factorial(n); 
      var sum = BigRational.Divide(power, factorialN); 
      series += sum; 
     } 
     return series; 
    } 

    static BigRational Factorial(int k) 
    { 
     if (k <= 1) 
      return 1; 
     else return BigRational.Multiply(k, Factorial(k - 1)); 
    } 

И главная проблема в том, что она не возвращает «нормальное» значение, как, например, 0,3 или 0,03. Результат, который печатается на консоли, очень длинный номер (например, 1200 цифр).

Может кто-нибудь, пожалуйста, взгляните на мой код и помогите мне исправить проблему и решить эти уравнения код. Спасибо

ответ

5

Console.WriteLine(pNResult); звонки BigRational.ToString() под капотом, который печатает номер в форме numerator/denominator.

Легко пропустить / на выходе, учитывая, насколько велики числитель и знаменатель в этом случае.

BigRational поддерживает преобразования до decimal и до double. В этом случае результат слишком мал, чтобы поместиться в decimal. Преобразование в double, дает результат 7.89682541396914E-177.

Если вам нужна более высокая точность, вам понадобится пользовательское преобразование в строку десятичного формата, например, в this Stackoverflow answer.

Используя эту процедуру пользовательских преобразований, чтобы получить результат 1000 знаков после запятой -

Console.WriteLine(pNResult.ToDecimalString(1000)); 

- дает результат в виде:

0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007896825413969130677012889713745949282897117034938033674093526965153968465052503367600313459328336130553067511247052840821917702504425411646279856145044231829004662624845172304039777 0263675109107145461310779641705093156106311143727608208629473359566457461384474633112850335950017209558136575135801388668687571284492241030561019606955986265585636660304889792027894460104216176719717671500843399685686146432982358441225578366059001576682388503227237202077881334695352338638383337717103303153521108812750644260562351186866587629456292506971252525125976755540274041651740194108430555751648707933592643410475214924394223640168857340953563111097979394441303100701008120008166339365089771585037880235325673143152814510586536335380671360865230428857049658368242543653234599817430185879648427434216378356518036776477170130227628307039


Чтобы проверить, что код расчета работает правильно, вы можете добавить блок-тест s для различных функций (Factorial, Series и вычисления самого P).

Подход, который является практическим здесь, заключается в том, чтобы вычислить результаты вручную для некоторых небольших значений k, n и p и проверить, что ваши функции вычисляют те же результаты.

Если вы используете Visual Studio, вы можете использовать this MSDN page в качестве отправной точки для создания проекта единичного тестирования. Обратите внимание, что тестируемые функции должны быть видимыми для проекта unit-test, и вашему проекту для тестирования модулей необходимо будет добавить ссылку на ваш существующий проект, где вы выполняете вычисления, как описано в ссылке.

Начиная с Factorial, который является самым простым, чтобы проверить, можно добавить тест, как это:

[TestClass] 
public class UnitTestComputation 
{ 
    [TestMethod] 
    public void TestFactorial() 
    { 
     Assert.AreEqual(1, Program.Factorial(0)); 
     Assert.AreEqual(1, Program.Factorial(1)); 
     Assert.AreEqual(2, Program.Factorial(2)); 
     Assert.AreEqual(6, Program.Factorial(3)); 
     Assert.AreEqual(24, Program.Factorial(4)); 
    } 
} 

код в ваш вопрос проходит этот тест.

Вы можете добавить метод тестирования для Series функции:

[TestMethod] 
public void TestSeries() 
{ 
    int k = 1; 
    int p = 1; 
    BigRational expected = 1; 
    Assert.AreEqual(expected, Program.Series(k, p)); 

    k = 2; 
    p = 1; 
    expected += 1; 
    Assert.AreEqual(expected, Program.Series(k, p)); 

    k = 3; 
    p = 1; 
    expected += (BigRational)1/(BigRational)2; 
    Assert.AreEqual(expected, Program.Series(k, p)); 

    k = 1; 
    p = 2; 
    expected = 1; 
    Assert.AreEqual(expected, Program.Series(k, p)); 

    k = 2; 
    p = 2; 
    expected += 2; 
    Assert.AreEqual(expected, Program.Series(k, p)); 
} 

Это показало некоторые проблемы в вашем коде:

  • n не должно быть на самом деле параметр этой функции, потому что в этом контексте n не является параметром для функции P, но на самом деле просто «индекс-суммирования». Локальное значение n этой функции представляет собой переменную i.
  • Это то означает, что ваш Factorial(n) вызова необходимо изменить, чтобы Factorial(i)
  • Петли также вне по одному, так как обозначение Sigma для суммирования с учетом числа в верхней части Sigma, так что вы должны есть <= fin (или вы могли бы написать это просто как < k).

Это обновленная Series функция:

// CHANGED: Removed n as parameter (n just the index of summation here) 
public static BigRational Series(int k, int p) 
{ 
    BigRational series = new BigRational(0.0); 
    var fin = k - 1; 
    // CHANGED: Should be <= fin (i.e. <= k-1, or < k) because it's inclusive counting 
    for (int i = 0; i <= fin; i++) 
    { 
     var power = BigRational.Pow(p, i); 
     // CHANGED: was Factorial(n), should be factorial of n value in this part of the sequence being summed. 
     var factorialN = Factorial(i); 
     var sum = BigRational.Divide(power, factorialN); 
     series += sum; 
    } 
    return series; 
} 

Чтобы проверить расчет P(n) вы можете двигаться, что из в свою собственную функцию для тестирования (я назвал его ComputeP здесь):

[TestMethod] 
public void TestP() 
{ 
    int n = 1; 
    int k = 2; 
    int p = 1; 
    // P(0) = 1/(2 + 1/(2*(1 - 1/2))) = 1/3 
    // P(1) = (1/(1/2 * 2)) * P(0) = P(0) = 1/3 
    BigRational expected = 1; 
    expected /= 3; 
    Assert.AreEqual(expected, Program.ComputeP(k, n, p)); 

    n = 2; 
    k = 2; 
    p = 1; 
    // P(2) = (1/(1*2)) * P(0) = 1/6 
    expected = 1; 
    expected /= 6; 
    Assert.AreEqual(expected, Program.ComputeP(k, n, p)); 
} 

У этой проблемы возникли проблемы с вычислением P(n) - у вас был листинг до double, который не должен был присутствовать (в результате получается неточно - вам нужно сохранить все промежуточные результаты в BigRational). Нет необходимости в актерском составе, поэтому просто его устранение устраняет эту проблему.

Вот обновленный ComputeP функция:

public static BigRational ComputeP(int k, int n, int p) 
{ 
    // the P(n) equation 
    BigRational pnNumerator = BigRational.Pow(p, n); 
    BigRational pnDenominator = BigRational.Pow(k, (n - k)) * Factorial(k); 


    // the P(0) equation 

    //---the right side of "+" sign in Denominator 
    BigRational pk = BigRational.Pow(p, k); 
    BigRational factorialK = Factorial(k); 
    // CHANGED: Don't cast to double here (loses precision) 
    BigRational lastPart = (BigRational.Subtract(1, BigRational.Divide(p, k))); 
    BigRational factorialAndLastPart = BigRational.Multiply(factorialK, lastPart); 
    BigRational fullRightSide = BigRational.Divide(pk, factorialAndLastPart); 
    //---the left side of "+" sign in Denominator 
    BigRational series = Series(k, p); 


    BigRational p0Denominator = series + fullRightSide; 
    BigRational p0Result = BigRational.Divide(1, p0Denominator); 

    BigRational pNResult = BigRational.Divide((pnNumerator * p0Result), pnDenominator); 
    return pNResult; 
} 

Для избежания путаницы, здесь есть вся обновленная программа расчета:

using System; 
using System.Numerics; 
using System.Text; 
using Numerics; 

public class Program 
{ 
    public static BigRational ComputeP(int k, int n, int p) 
    { 
     // the P(n) equation 
     BigRational pnNumerator = BigRational.Pow(p, n); 
     BigRational pnDenominator = BigRational.Pow(k, (n - k)) * Factorial(k); 


     // the P(0) equation 

     //---the right side of "+" sign in Denominator 
     BigRational pk = BigRational.Pow(p, k); 
     BigRational factorialK = Factorial(k); 
     // CHANGED: Don't cast to double here (loses precision) 
     BigRational lastPart = (BigRational.Subtract(1, BigRational.Divide(p, k))); 
     BigRational factorialAndLastPart = BigRational.Multiply(factorialK, lastPart); 
     BigRational fullRightSide = BigRational.Divide(pk, factorialAndLastPart); 
     //---the left side of "+" sign in Denominator 
     BigRational series = Series(k, p); 


     BigRational p0Denominator = series + fullRightSide; 
     BigRational p0Result = BigRational.Divide(1, p0Denominator); 

     BigRational pNResult = BigRational.Divide((pnNumerator * p0Result), pnDenominator); 
     return pNResult; 
    } 

    // CHANGED: Removed n as parameter (n just the index of summation here) 
    public static BigRational Series(int k, int p) 
    { 
     BigRational series = new BigRational(0.0); 
     var fin = k - 1; 
     // CHANGED: Should be <= fin (i.e. <= k-1, or < k) because it's inclusive counting 
     for (int i = 0; i <= fin; i++) 
     { 
      var power = BigRational.Pow(p, i); 
      // CHANGED: was Factorial(n), should be factorial of n value in this part of the sequence being summed. 
      var factorialN = Factorial(i); 
      var sum = BigRational.Divide(power, factorialN); 
      series += sum; 
     } 
     return series; 
    } 

    public static BigRational Factorial(int k) 
    { 
     if (k <= 1) 
      return 1; 
     else return BigRational.Multiply(k, Factorial(k - 1)); 
    } 

    static void Main(string[] args) 
    { 
     var k = 40; 
     var n = 235; 
     var p = 5; 
     var result = ComputeP(k, n, p); 
     Console.WriteLine(result.ToDecimalString(1000)); 
     Console.ReadKey(); 
    } 
} 

// From https://stackoverflow.com/a/10359412/4486839 
public static class BigRationalExtensions 
{ 
    public static string ToDecimalString(this BigRational r, int precision) 
    { 
     var fraction = r.GetFractionPart(); 

     // Case where the rational number is a whole number 
     if (fraction.Numerator == 0 && fraction.Denominator == 1) 
     { 
      return r.GetWholePart() + ".0"; 
     } 

     var adjustedNumerator = (fraction.Numerator 
              * BigInteger.Pow(10, precision)); 
     var decimalPlaces = adjustedNumerator/fraction.Denominator; 

     // Case where precision wasn't large enough. 
     if (decimalPlaces == 0) 
     { 
      return "0.0"; 
     } 

     // Give it the capacity for around what we should need for 
     // the whole part and total precision 
     // (this is kinda sloppy, but does the trick) 
     var sb = new StringBuilder(precision + r.ToString().Length); 

     bool noMoreTrailingZeros = false; 
     for (int i = precision; i > 0; i--) 
     { 
      if (!noMoreTrailingZeros) 
      { 
       if ((decimalPlaces % 10) == 0) 
       { 
        decimalPlaces = decimalPlaces/10; 
        continue; 
       } 

       noMoreTrailingZeros = true; 
      } 

      // Add the right most decimal to the string 
      sb.Insert(0, decimalPlaces % 10); 
      decimalPlaces = decimalPlaces/10; 
     } 

     // Insert the whole part and decimal 
     sb.Insert(0, "."); 
     sb.Insert(0, r.GetWholePart()); 

     return sb.ToString(); 
    } 
} 

И вот вся программа модульного тестирования :

using System; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using Numerics; 


[TestClass] 
public class UnitTestComputation 
{ 
    [TestMethod] 
    public void TestFactorial() 
    { 
     Assert.AreEqual(1, Program.Factorial(0)); 
     Assert.AreEqual(1, Program.Factorial(1)); 
     Assert.AreEqual(2, Program.Factorial(2)); 
     Assert.AreEqual(6, Program.Factorial(3)); 
     Assert.AreEqual(24, Program.Factorial(4)); 
    } 

    [TestMethod] 
    public void TestSeries() 
    { 
     int k = 1; 
     int p = 1; 
     BigRational expected = 1; 
     Assert.AreEqual(expected, Program.Series(k, p)); 

     k = 2; 
     p = 1; 
     expected += 1; 
     Assert.AreEqual(expected, Program.Series(k, p)); 

     k = 3; 
     p = 1; 
     expected += (BigRational)1/(BigRational)2; 
     Assert.AreEqual(expected, Program.Series(k, p)); 

     k = 1; 
     p = 2; 
     expected = 1; 
     Assert.AreEqual(expected, Program.Series(k, p)); 

     k = 2; 
     p = 2; 
     expected += 2; 
     Assert.AreEqual(expected, Program.Series(k, p)); 
    } 

    [TestMethod] 
    public void TestP() 
    { 
     int n = 1; 
     int k = 2; 
     int p = 1; 
     // P(0) = 1/(2 + 1/(2*(1 - 1/2))) = 1/3 
     // P(1) = (1/(1/2 * 2)) * P(0) = P(0) = 1/3 
     BigRational expected = 1; 
     expected /= 3; 
     Assert.AreEqual(expected, Program.ComputeP(k, n, p)); 

     n = 2; 
     k = 2; 
     p = 1; 
     // P(2) = (1/(1*2)) * P(0) = 1/6 
     expected = 1; 
     expected /= 6; 
     Assert.AreEqual(expected, Program.ComputeP(k, n, p)); 
    } 
} 

Кстати, P(n) результат с обновленной программой для ваших входных значений для n, p и k теперь:

0,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000593109980769066916025972569398424267669807629726200017375290861590898269902277869938365969961320969473356001666906480007119114830921839913623591124192047955091318951831902550404167336054683697071654765071519020060437129398945035521954738463786221029427589397688847246112810 536958194364039693387170592425527136243952416704526069736811587380688876091926255908361275575249492845970903676492429684929779402600032481018886875698972533534890841796034626337674846620462046294537488580901129338625628349474358946962065227890599744775562637784553656488649841148591533557896418988044457914999854241038974478576578909626765823565817758792682480009619613438867365912697996527957775248350987801430141776875171808382272960426476953742528769626555642957093028553993908356226007570404005591174451216846471710162760343


ПРИМЕЧАНИЕ: Вы должны добавить к юнит-тесты с большим количеством результатов вы проверяемые вручную, а также проверить любого из моих работающих здесь в интерпретации алгебры как код, чтобы убедиться, что это правильно.

+0

Хорошо, спасибо за ответ. Можете ли вы сказать мне, правильно ли учтен мой код и этот результат? Например, работают ли мои функции Series и Factorial? Я проверил их, а также получаю тот же результат, что и вы, но все же я думал, что мой результат неверен ... :( – Eru

+0

Я обновил свой ответ, чтобы показать, как вы, как и это, а также внесли некоторые исправления в ваш программный код. Вы должны убедиться, что мои исправления верны, но с дальнейшей проверкой и тестированием. – softwariness

+1

Я тестировал это в символической математической программе, и я получаю 0.0067 для P (0). что помогает с двойной проверкой. –

Смежные вопросы