Working with Big Numbers in C#

I recently set out to do some universal gravitation calculations in code, but I soon found that a lot of the existing or built-in data-types (Int64, Decimal, etc.) for doing so in .NET were rather inadequate (they either did not allow a high degree of precision, or they did not allow numbers large enough to suit my needs), so I searched for something that I could use to model very large numbers to an acceptably large degree of accuracy. That’s when I came across the BigRational class on CodePlex. This class decomposes large fractions into two BigInteger values (one to represent the numerator and another to represent the denominator), since any fractional number can be decomposed as such. I kept on royally butchering my by-hand calculations (since there are so many big numbers at play, along with unit conversions, etc.), and I got fed up to the point that I set out to create a code-based model in order to calculate surface gravities for the Sun and all of the planets in our Solar System using Newton’s law of universal gravitation:

using Numerics;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;

namespace UniversalPhysics
{

    public static class BigRationals
    {
        public static BigRational WithExponent(BigRational value, BigInteger exponent)
        {
            return BigRational.Multiply(value, BigRational.Pow(new BigRational(10, 1), exponent));
        }

        public static BigRational FromInteger(BigInteger value)
        {
            return new BigRational(value);
        }

        public static BigRational FromDouble(double value)
        {
            if (value == 0 || Double.IsNaN(value))
            {
                return new BigRational(0, 1);
            }
            if (Double.IsPositiveInfinity(value))
            {
                value = Double.MaxValue;
            }
            if (Double.IsNegativeInfinity(value))
            {
                value = Double.MinValue;
            }
            double fraction = value % 1;
            double numerator = value - fraction;
            double denominator = 1;
            // Notes:
            // 
            // - You are at the mercy of the precision of the double data type.
            // - If you debug this, you will see that the fraction variable changes in precision.
            // - The double data type does this to accomodate similar levels of precision.
            // - This may result in a slight skew of a very large value variable or a very small value variable.
            // - This effect is negligible when the value's precision is much less than Double.MaxValue's precision.
            while (!Double.IsNaN(fraction))
            {
                double decimalPlace = fraction * 10 * denominator - (fraction * 10 * denominator) % 1;
                if (Double.IsInfinity(numerator * 10 + decimalPlace))
                {
                    break;
                }
                if (Double.IsInfinity(denominator * 10))
                {
                    break;
                }
                denominator *= 10;
                numerator = numerator * 10 + decimalPlace;
                fraction = fraction - decimalPlace / denominator;
            }
            BigInteger numeratorAsBigInteger = new BigInteger(numerator);
            BigInteger denominatorAsBigInteger = new BigInteger(denominator);
            return new BigRational(numeratorAsBigInteger, denominatorAsBigInteger);
        }

        public static BigRational FromDoubleWithExponent(double value, BigInteger exponent)
        {
            return WithExponent(FromDouble(value), exponent);
        }
    }

    class Program
    {
        
        static SolarSystem System = new SolarSystem();

        static void Main(string[] args)
        {
            Console.WriteLine("Sun's Surface Gravity = " + (double)System.Sun.SurfaceGravity);
            Console.WriteLine("Mercury's Surface Gravity = " + (double)System.Mercury.SurfaceGravity);
            Console.WriteLine("Venus' Surface Gravity = " + (double)System.Venus.SurfaceGravity);
            Console.WriteLine("Earth's Surface Gravity = " + (double)System.Earth.SurfaceGravity);
            Console.WriteLine("Mars' Surface Gravity = " + (double)System.Mars.SurfaceGravity);
            Console.WriteLine("Jupiter's Surface Gravity = " + (double)System.Jupiter.SurfaceGravity);
            Console.WriteLine("Saturn's Surface Gravity = " + (double)System.Saturn.SurfaceGravity);
            Console.WriteLine("Uranus' Surface Gravity = " + (double)System.Uranus.SurfaceGravity);
            Console.WriteLine("Neptune's Surface Gravity = " + (double)System.Neptune.SurfaceGravity);
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }

    public class SolarSystem
    {

        public Planet Sun;
        public Planet Mercury;
        public Planet Venus;
        public Planet Earth;
        public Planet Mars;
        public Planet Jupiter;
        public Planet Saturn;
        public Planet Uranus;
        public Planet Neptune;

        public SolarSystem()
        {
            // Notes:
            //
            // - When viewed from above the Sun's north pole, the sun spins counter-clockwise on its axis.
            // - Prograde motion, when viewed from above the Sun's north pole, means counter-clockwise motion.
            // - Retrograde motion, when viewed from above the Sun's north pole, means clockwise motion.
            //
            // Putting this all together, when viewed from above the Sun's north pole:
            //
            // - The sun spins counter-clockwise.
            // - Venus and Uranus are the only two planets which have clockwise orbits.
            //      - All other planets have counter-clockwise orbits.
            // - Venus is the only planet that spins clockwise on its axis.
            //      - All other planets spin counter-clockwise on their axes.
            //      - Uranus kind-of also spins counter-clockwise, but the angle defined by its axial tilt makes it quite an oddball.
            //          - It doesn't spin like the other planets.
            //          - If you were the Sun, and a car in front of you was facing you with its front, and that car started to drive towards you, the way the wheels would spin on that car would be similar to the direction Uranus spins.
            //
            // What does this all mean?
            //
            // - Well, a planet's sidereal rotation period is really defined by axial tilt.
            // - Axial tilt (or obliquity), is the angle between an object's rotational axis and its orbital axis.
            //      - Equivalently, its the angle between a planet's equatorial plane and orbital plane.
            //      - It differs from orbital inclination.
            // - Don't take what I say for granted until you apply axial tilt for yourself, because I've made approximations with terms like clockwise and counter-clockwise.
            Sun = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(1.98855, 30),
                MeanRadius = BigRationals.FromInteger(696342000),
                SiderealRotationPeriod = TimeSpan.FromDays(25.05)
            };
            Mercury = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(3.3022, 23),
                MeanRadius = BigRationals.FromInteger(2439700),
                SiderealRotationPeriod = TimeSpan.FromHours(1407.5)
            };
            Venus = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(4.8676, 24),
                MeanRadius = BigRationals.FromInteger(6051800),
                SiderealRotationPeriod = TimeSpan.FromDays(243.0185) // Retrograde (this planet orbits the Sun in the opposite spin direction of the Sun).
            };
            Earth = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(5.97219, 24),
                MeanRadius = BigRationals.FromInteger(6371000),
                SiderealRotationPeriod = new TimeSpan(0, 23, 56, 4, 100)
            };
            Mars = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(6.4185, 23),
                MeanRadius = BigRationals.FromInteger(3389500),
                SiderealRotationPeriod = new TimeSpan(24, 37, 22)
            };
            Jupiter = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(1.8986, 27),
                MeanRadius = BigRationals.FromInteger(69911000),
                SiderealRotationPeriod = new TimeSpan(9, 55, 30)
            };
            Saturn = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(5.6846, 26),
                MeanRadius = BigRationals.FromInteger(58232000),
                SiderealRotationPeriod = new TimeSpan(10, 34, 0)
            };
            Uranus = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(8.681, 25),
                MeanRadius = BigRationals.FromInteger(25362000),
                SiderealRotationPeriod = new TimeSpan(17, 14, 24) // Retrograde (this planet orbits the Sun in the opposite spin direction of the Sun).
            };
            Neptune = new Planet
            {
                Mass = BigRationals.FromDoubleWithExponent(1.0243, 26),
                MeanRadius = BigRationals.FromInteger(24622000),
                SiderealRotationPeriod = new TimeSpan(16, 6, 36)
            };
        }
    }

    public class Planet
    {
        public BigRational Mass;
        public BigRational MeanRadius;
        public TimeSpan SiderealRotationPeriod;
        public BigRational AngularVelocity
        {
            get
            {
                return Laws.GetAngularVelocity(SiderealRotationPeriod);
            }
        }
        public BigRational SurfaceGravity
        {
            get
            {
                return Laws.GetNewtonianSurfaceGravity(Mass, MeanRadius);
            }
        }
    }

    public static class Laws
    {
        public static BigRational GetAngularVelocity(TimeSpan siderealRotationPeriod)
        {
            return BigRational.Divide(BigRational.Multiply(new BigRational(2.0), Constants.PI), BigRationals.FromDouble(siderealRotationPeriod.TotalSeconds));
        }

        public static BigRational GetNewtonianSurfaceGravity(BigRational mass, BigRational meanRadius)
        {
            return BigRational.Divide(BigRational.Multiply(Constants.GravitationalConstant, mass), BigRational.Pow(meanRadius, 2));
        }
    }

    public static class Constants
    {
        public static BigRational PI = BigRationals.FromDouble(3.141592654);
        public static BigRational GravitationalConstant = BigRationals.FromDoubleWithExponent(6.67384, -11);
        public static BigRational SpeedOfLight = BigRationals.FromInteger(299792458);
    }
}

Newton’s law of universal gravitation has been superseded by Einstein’s theory of general relativity, but it is still used as a good approximation of gravitational force. Relativity is for extreme precision, or for cases around the boundaries of Newton’s law of universal gravitation (very big masses, or very close massive objects). Alas, the output to all of that code above is as follows (the values obtained from Newton’s formulae are fairly close to the values from NASA’s Planetary Fact Sheet):

Sun's Surface Gravity = 273.695164677966
Mercury's Surface Gravity = 3.70259586050561
Venus' Surface Gravity = 8.86995750090755
Earth's Surface Gravity = 9.81960902526831
Mars' Surface Gravity = 3.72853358713613
Jupiter's Surface Gravity = 25.9249685707583
Saturn's Surface Gravity = 11.1879956428603
Uranus' Surface Gravity = 9.00696656052567
Neptune's Surface Gravity = 11.2760322511266
Press any key to exit.

Alexandru

"To avoid criticism, say nothing, do nothing, be nothing." - Aristotle

"It is wise to direct your anger towards problems - not people; to focus your energies on answers - not excuses." - William Arthur Ward

"Science does not know its debt to imagination." - Ralph Waldo Emerson

"Money was never a big motivation for me, except as a way to keep score. The real excitement is playing the game." - Donald Trump

"All our dreams can come true, if we have the courage to pursue them." - Walt Disney

"Mitch flashes back to a basketball game held in the Brandeis University gymnasium in 1979. The team is doing well and chants, 'We're number one!' Morrie stands and shouts, 'What's wrong with being number two?' The students fall silent." - Tuesdays with Morrie

I'm not entirely sure what makes me successful in general programming or development, but to any newcomers to this blood-sport, my best guess would be that success in programming comes from some strange combination of interest, persistence, patience, instincts (for example, someone might tell you that something can't be done, or that it can't be done a certain way, but you just know that can't be true, or you look at a piece of code and know something doesn't seem right with it at first glance, but you can't quite put your finger on it until you think it through some more), fearlessness of tinkering, and an ability to take advice because you should be humble. Its okay to be wrong or to have a bad approach, realize it, and try to find a better one, and even better to be wrong and find a better approach to solve something than to have had a bad approach to begin with. I hope that whatever fragments of information I sprinkle across here help those who hit the same roadblocks.

Leave a Reply

Your email address will not be published. Required fields are marked *