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
ПРИМЕЧАНИЕ: Вы должны добавить к юнит-тесты с большим количеством результатов вы проверяемые вручную, а также проверить любого из моих работающих здесь в интерпретации алгебры как код, чтобы убедиться, что это правильно.
Хорошо, спасибо за ответ. Можете ли вы сказать мне, правильно ли учтен мой код и этот результат? Например, работают ли мои функции Series и Factorial? Я проверил их, а также получаю тот же результат, что и вы, но все же я думал, что мой результат неверен ... :( – Eru
Я обновил свой ответ, чтобы показать, как вы, как и это, а также внесли некоторые исправления в ваш программный код. Вы должны убедиться, что мои исправления верны, но с дальнейшей проверкой и тестированием. – softwariness
Я тестировал это в символической математической программе, и я получаю 0.0067 для P (0). что помогает с двойной проверкой. –