Я писал метод, который вычислял бы значение e^x. То, как я реализовал это в python, было следующим.
import math
def exp(x):
return sum([
x**n/math.factorial(n)
for n in range(0, 100)
])
Это очень хорошо вернет значение e^x. Но когда я попытался реализовать тот же метод на С#, он не выдал того же значения, что и на питоне. Далее была реализация на С#.
static double exp(int x)
{
double FinalAnswer = 0;
for (int j = 0; j <= 100; j++)
{
FinalAnswer += (Math.Pow(x, j))/Factorial(j);
}
return FinalAnswer;
}
Вывод для этого кода сначала был символом бесконечности. Чтобы решить эту проблему, я просто уменьшил количество запусков цикла. Вывод кода на С#, где цикл выполнялся только 10 раз, был довольно близок к выводу на питоне, где цикл выполнялся 100 раз. Мой вопрос в том, что происходит между двумя циклами на разных языках программирования. Сначала я подумал, что выражение, которое я использовал в своем методе для вычисления e^x, быстро сходится. Но как цикл, который выполняется 10 раз, дает результат, соответствующий результату цикла, который выполняется 100 раз?
Кроме того, когда я увеличил цикл for в С# до 20 и 30, значения e ^ x для x> 3 были далекими. Может ли кто-нибудь объяснить, что здесь происходит?
Я попробовал оба фрагмента с 3 и 13 и не нашел существенной разницы. Пожалуйста, добавьте примеры (например, пары вход-выход), с которыми вы столкнулись. Также имейте в виду, что чистый python работает с числами с бесконечной точностью, где c# double
— это аппаратный нативный тип с ограничениями точности (вы можете видеть это, когда 3**50
дает int 717897987691852588770249
в python, а в c# (long)Math.Pow(3,50)
дает -9223372036854775808
).
Обратите внимание, что прямое вычисление математической формулы $\sum_{n=0}^k\frac{X^n}{n!}$ в том виде, в каком она написана, является особенно плохим способом ее вычисления практически на любом языке. Вычисление полинома с использованием Схема Хорнера не только использует намного меньше операций умножения и деления, но также позволяет избежать переполнения, которое здесь наблюдается, и, как правило, более снисходительно относится к ранним ошибкам округления.
Скорее всего, вы столкнетесь здесь с целочисленное переполнение с версией C# функции Factorial (по крайней мере, с вашей ее реализацией или откуда она исходит).
В C# int
— это числовой тип, хранящийся в 32 битах памяти, что означает, что он ограничен -2^31 <= n <= 2^31 - 1
, что составляет около +/- 2,1 миллиарда. Вы можете попробовать использовать тип long
, который является 64-битным числовым типом, однако для еще больших верхних границ в вашем цикле for, например, приближения к 100, вы также будете переполнять long
.
Когда вы запускаете функцию Factorial в C#, сначала она запускается нормально, однако, если вы продолжите, вы увидите, что она внезапно переходит к отрицательным числам, и если вы продолжите двигаться дальше, он станет равным 0 и перестанет меняться. Вы видите вывод бесконечности из-за деления на 0, и C# имеет способ обработки этого с помощью удвоений; то есть просто вернуться double.PositiveInfinity
.
Причина, по которой этого не происходит в python, заключается в том, что он использует переменное количество битов для хранения своих числовых значений.
Добавлено примечание: Вы также можете попробовать использовать факториальную функцию, которая работает с типом double
вместо int
или long
, однако при этом вы потеряете точность точного значения, но вы получите больший диапазон в качестве величины. из числа, которое вы можете сохранить, больше
Дополнительное примечание: Как упоминалось в комментариях, в C# есть тип с именем BigInteger
, который предназначен для обработки огромных чисел, таких как значения, которые вы ожидаете от больших входных данных для функции факториала. Вы можете найти ссылку на документы BigInteger здесь
Я знаю, что это очень запоздалое добавление, но вы можете вычислить каждый компонент факториальной функции отдельно с той мощностью, которую вы используете. Вот что я имею в виду:
public decimal Exp(decimal power, int accuracy = 100)
{
decimal runningTotal = 1;
decimal finalValue = 1;
for (int i = 1; i <= accuracy; i++)
{
runningTotal *= power/i;
finalValue += runningTotal;
}
return finalValue;
}
Не могли бы вы уточнить эту часть: «Причина, по которой этого не происходит в python, заключается в том, что он использует переменное количество бит для хранения своих числовых значений».
@YonatanNir Я не особо разбираюсь в тонкостях, но на высоком уровне способ хранения чисел в python использует нефиксированное количество байтов. если число, которое вы пытаетесь представить, требует больше, чем количество байтов, которое в настоящее время содержит переменная, оно просто выделяет больше и использует это. Вы можете найти лучшее сравнение здесь: pythontutorial.net/advanced-python/python-целые числа
В C# также есть класс BigInteger
(в System.Numerics
), который можно использовать для хранения сколь угодно больших чисел за счет более медленных вычислений и несколько более сложного кода. (Вы не сможете использовать Math.Factorial()
или Math.Pow()
, но вы можете достаточно легко написать свои собственные реализации BigInteger.) У BigInteger есть своя собственная функция Pow()
, которая того стоит.
Для справки, последний термин math.factorial(n)
(n=99
) эквивалентен 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000
— в качестве целого числа без знака для этого потребуется 519 бит, намного больше, чем даже long
.
FWIW здесь Целочисленная реализация Python. По сути, он хранит длину (ob_size
, поле общего назначения, которым слегка злоупотребляют для целых чисел) и блок uint32_t
этой длины, биты которого представляют число.
Я бы предложил double f = 1.0; for (int j = 0; j < 100; j++) { FinalAnswer += (Math.Pow(x, j))/f; f = f * (j + 1);}
Или, на самом деле, избавиться от Math.Pow
таким же образом.
Если вам нужно получить действительно большие числа, очень маленькие числа или числа с большим количеством знаков по обе стороны от десятичной точки для C#, проверьте порт заголовков/классов gmplib
Я думаю, вам нужно
j < 100
, ваш диапазон питона останавливается на 99...92, 93, 94, 95, 96, 97, 98, 99]