For loop in c# vs For loop in python
Asked Answered
B

1

14

I was writing a method that would calculate the value of e^x. The way I implemented this in python was as follows.

import math

def exp(x):
    return sum([
        x**n/math.factorial(n)
        for n in range(0, 100)
    ])

This would return the value of e^x very well. But when I tried to implement the same method in c#, it didn't output the same value as it did in python. The following was the implementation in c#.

static double exp(int x)
{
    double FinalAnswer = 0;
    for (int j = 0; j <= 100; j++)
    {
        FinalAnswer += (Math.Pow(x, j))/Factorial(j);
    }
    return FinalAnswer;
}

The output for this code was an infinity symbol at first. To resolve this I just reduced the number of times the loop ran. The output of the code in c# where the loop only ran 10 times was pretty close to the output in python where the loop ran 100 times. My question is that what is going on between the two loops in different programming languages. At first I thought that the expression that I was using in my method to calculate e^x was converging quickly. But how does a loop that runs 10 times produce an output that matches the output of a loop that runs 100 times.

Also, When I increased the for loop in c# to 20 and 30, the values of e^x for x > 3 were way off. Could someone explain what is going on here?

Barto answered 28/6, 2022 at 7:10 Comment(3)
I think you need j < 100 , your python range stops at 99 ...92, 93, 94, 95, 96, 97, 98, 99]Lynn
I tried both snippets with 3 and 13 and can't find a significant difference. Please add examples (i.e. inout-output-pairs) that you encountered. Also keep in mind that pure python works with infinity precises numbers where c#'s double is a hardware native type with precision limits (you can see this with 3**50 yielding the int 717897987691852588770249 in python and in c# (long)Math.Pow(3,50) yields -9223372036854775808).Fawkes
Note that directly evaluating the mathematical formula $\sum_{n=0}^k\frac{X^n}{n!}$ as it is written is a particularly bad way to compute it, in almost any language. Evaluating the polynomial using Horner's scheme not only uses a lot fewer multiplications and divisions, but also avoids the kind of overflow experienced here, and tends to be more forgiving about early rounding errors.Spiraea
C
20

What you're likely running into here is integer overflow with the C# version of the Factorial function (at least your implementation of it, or wherever its coming from).

In C#, an int is a numerical type stored in 32 bits of memory, which means it's bounded by -2^31 <= n <= 2^31 - 1 which is around +/- 2.1 billion. You could try using a long type, which is a 64 bit numerical type, however for even larger upper bounds in your for loop, like getting close to 100, you're going to overflow long as well.

When you run the Factorial function in C#, it starts off normally for the first little while, however if you keep going, you'll see that it all of a sudden jumps into negative numbers, and if you keep going even further than that, it'll get to 0 and stop changing. You're seeing the output of infinity due to division by 0, and C# has a way of handling that with doubles; that being to just return double.PositiveInfinity.

The reason why this doesn't happen in python is that it uses a variable number of bits to store its numerical values.

Added note: What you might also want to try is using a Factorial function that works with the double type instead of int or long, however by doing this, you'll lose precision on what the exact value is, but you get more range as the magnitude of the number you can store is larger

Further Note: As mentioned in the comments, C# has a type called BigInteger which is designed to handle huge numbers like the values you would expect from large inputs to a Factorial function. You can find a reference to the BigInteger docs here


What you can do is calculate each component of the factorial function separately with the power you're using. Here's what I mean:

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;
}
Cawthon answered 28/6, 2022 at 7:34 Comment(7)
Can you please elaborate on that part: "The reason why this doesn't happen in python is that it uses a variable number of bits to store its numerical values."Alp
@YonatanNir i'm not extremely familiar with the intricacies, but on a high level, the way python stores numbers uses an unfixed number of bytes. if the number you're trying to represent requires more than the number of bytes that the variable currently contains, it just allocates more and uses that. You can find a better comparison here: pythontutorial.net/advanced-python/python-integersCawthon
C# also has a BigInteger class (in System.Numerics), which can be used to store arbitrarily large numbers, at the expense of slower calculations and somewhat more complicated code. (You won't be able to use Math.Factorial() or Math.Pow(), but you can write your own BigInteger implementations of those easily enough.) BigInteger has its own Pow() function for what it's worth.Colorable
For reference, the final term math.factorial(n) (n=99) is equivalent to 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000 – as an unsigned integer this would need 519 bits, way more than even long.Repair
FWIW here is Python's integer implementation. It essentially stores a length (ob_size, a general-purpose field that's being slightly abused for integers) and a block of uint32_t of that length, whose bits represent the number.Roebuck
I’d suggest double f = 1.0; for (int j = 0; j < 100; j++) { FinalAnswer += (Math.Pow(x, j))/f; f = f * (j + 1);} Or actually, get rid of Math.Pow in the same way.Hypolimnion
If you need to get to really big numbers, really small numbers, or numbers with lots of places on either side of the decimal for C# check the port of the gmplib headers/classesPrefabricate

© 2022 - 2024 — McMap. All rights reserved.