I recently created a new data type for Above the Stars. It was an interesting excursion into many things I’ve never used before so I thought I’d do a write-up regarding it.

The MegaInt is capable of holding very large numbers all in a single integer, as well displaying them in a readable format.

A basic description can be found below as well as the full script (Creative Commons 4.0).

Description

The MegaInt can hold very large numbers by splitting the 32 bits of an integer. It uses the last 24 bits for accuracy (7 significant figures) and the remaining 8 bits for power (10256).

I tried to be as descriptive as possible in the code (usually my comments are lacking) so I hope it’s at least somewhat educational, or at the very least to anyone making similar (incremental) games.

The concept itself is quite straightforward. It takes the input (converted to String) and reduces it to the 7 digit length, incrementing the power for each excess digit dropped.

I wont go into the various conversions or mathematical methods. (the latter works as normal but accounts for the powers). There may be some odd behaviours at very low numbers (always rounding up) but for the most part it works quite well.

Conclusion

Overall I’m very happy with the result. It is lightweight and accurate enough for my purposes, although there is much that could be improved.

If I were to do it again I would definitely build it to work with below-one numbers (I’d do it now but it would require a large rewrite). Some of the methods should probably be rewritten to look/run cleaner, as well. I might also focus less on saving memory and more on performance, but I’m not too sure.

Anyway, I hope you find this useful.

Code

IsDigitsOnly //ArmanDoesStuff 2017 using System.Collections; using System.Collections.Generic; using UnityEngine; public static class ArmanLibrary { //Check if string only contains digits public static bool IsDigitsOnly(this string str) { foreach (char c in str) { if (c < '0' || c > '9') { return false; } } return true; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //ArmanDoesStuff 2017 using System . Collections ; using System . Collections . Generic ; using UnityEngine ; public static class ArmanLibrary { //Check if string only contains digits public static bool IsDigitsOnly ( this string str ) { foreach ( char c in str ) { if ( c < '0' || c > '9' ) { return false ; } } return true ; } }

Above is a snippet from my custom library of common functions. IsDigitsOnly extends the string class and is pretty self-explanatory. It’s used in the MegaInt script (below) when taking a string input from an outside source

MegaInt 1.0 //ArmanDoesStuff 2017 //MegaInt 1.0 using System; public class MegaInt { #region variables string[] PowerNames = new string[] { "", "Thousand", "Million", "Billion", "Trillion", "Quadrillion", "Quintillion", "Sextillion", "Septillion", "Octillion", "Nonillion", "Decillion", "Undecillion", "Duodecillion", "Tredecillion", "Quattuordecillion", "Quindecillion", "Sexdecillion", "Septendecillion", "Octodecillion", "Novemdecillion", "Vigintillion" }; uint storedValue; //actual data, holds Value and Power #endregion #region accessors //First 8 bits are Power (up to 10^255) byte Power { get { //return as byte (pushed right to override Val) return (byte)(storedValue >> 24); } set { //set as left bits, add original Val storedValue = (uint)(value << 24) + (storedValue & 16777215); } } //Last 24 are Value(16777216 - accuracy to 7 - 9999999) uint Val { get { //return with Power masked out return (storedValue & 16777215); //16777215 = 0000 0000 1111 1111 1111 1111 1111 1111 } set { //set unmasked since the Power bits should never be set anyway. Add original Power to left bits storedValue = (uint)((value /* & 16777215 */) + (Power << 24)); } } #endregion #region functions //divide by power of 10 public static MegaInt Pow10Div(MegaInt a, byte p) { //get power difference int outPower = a.Power - p; if (outPower < 0) { string outValString = a.Val.ToString(); //take off last digets to the amount of power or until only one remains return ulong.Parse(outValString.Substring(0, outValString.Length - Math.Min(Math.Abs(outPower), outValString.Length - 1))); } else { //if there is power remaining after divide, just output val with new power return new MegaInt(a.Val, (byte)outPower); } } public MegaInt Pow10Div(byte p) { return Pow10Div(this, p); } //divide by mult of 10 public static MegaInt Pow10Mult(MegaInt a, byte p) { //get power difference return new MegaInt(a.Val, (byte)(a.Power + p)); } public MegaInt Pow10Mult(byte p) { return Pow10Mult(this, p); } //divide by power of 10, round up public static uint OverPowerDiff(MegaInt a, byte highPower) { return (uint)((a.Val - 1) / (Math.Pow(10, highPower - a.Power))) + 1; } uint[] ConvertToMega(string a) { return ConvertToMega(a, 0); } //Power Function public static MegaInt PowerMult(MegaInt init, MegaInt mult, int pow) { for (int i = 0; i < pow; i++) { init *= mult; } return init; } uint[] ConvertToMega(string a, byte p) { //normalise to 7 digits if there is power while (a.Length < 7 && p > 0) { a += "0"; p--; } //accuracy to 7 digits since 24-bits go up to 16777216 - 8 characters while (a.Length > 7) { //remove end number and increase power of 10 by one a = a.Remove(a.Length - 1); p++; } //return as values suitable for variables return new uint[2] { uint.Parse(a), p }; } #region constuctors public MegaInt(ulong a) { uint[] i = ConvertToMega(a.ToString()); Val = i[0]; Power = (byte)i[1]; } public MegaInt(uint a) { uint[] i = ConvertToMega(a.ToString()); Val = i[0]; Power = (byte)i[1]; } public MegaInt(ulong a, byte p) { uint[] i = ConvertToMega(a.ToString(), p); Val = i[0]; Power = (byte)i[1]; } public MegaInt(string a) { #if UNITY_EDITOR //Deactivate sanitized input check because I'm not using direct user input to MegaInt. a = a.TrimStart('0'); if (!a.IsDigitsOnly() || a == "") { Val = 0; Power = 0; return; } #endif uint[] i = ConvertToMega(a); Val = i[0]; Power = (byte)i[1]; } #endregion #region implicits public static implicit operator MegaInt(string a) { return new MegaInt(a); } public static implicit operator MegaInt(uint a) { return new MegaInt(a); } public static implicit operator MegaInt(ulong a) { return new MegaInt(a); } public static implicit operator string(MegaInt a) { return a.ToString(); } #endregion #region overrides public override string ToString() { if (Val < 1000) //only act on Values above 1 thousand { return Val.ToString(); } //the higher the power the further the point moves until it resets at 10^3 to x.xxx int digits = Power + (Val.ToString().Length); //number of digits if (digits > 66) //if above Vigintillion then just show power { return Val.ToString().Substring(0, 4).Insert(1, ".") + " 10^" + digits; } int decimalPlace = digits % 3 == 0 ? 3 : digits % 3; // + (Value < 0 ? 1 : 0); if doing negetives //get first 4 digits as significant figures //Add decimal point at correct place (as described above) //add power name (power, plus extra power for length minus the first 3) over 3 for because increments are 10^3 return Val.ToString().Substring(0, 4).Insert(decimalPlace, ".") + " " + PowerNames[(digits - 1) / 3]; } //I have no idea what I'm doing public override bool Equals(object obj) { var item = obj as MegaInt; if (item == null) { return false; } return this.storedValue.Equals(item.storedValue); } //I have no idea what I'm part II public override int GetHashCode() { return this.storedValue.GetHashCode(); } #region equalities public static bool operator <(MegaInt leftSide, MegaInt rightSide) { //check powers, then check values (if powers are equal) if (leftSide.Power < rightSide.Power || (leftSide.Power == rightSide.Power && leftSide.Val < rightSide.Val)) { return true; } return false; } public static bool operator >(MegaInt leftSide, MegaInt rightSide) { //same as above (with signs switched) if (leftSide.Power > rightSide.Power || (leftSide.Power == rightSide.Power && leftSide.Val > rightSide.Val)) { return true; } return false; } public static bool operator ==(MegaInt leftSide, MegaInt rightSide) { //powers AND values must match if (leftSide.storedValue == rightSide.storedValue) { return true; } return false; } public static bool operator !=(MegaInt leftSide, MegaInt rightSide) { //defined above then inverted if (leftSide == rightSide) { return false; } return true; } #endregion #region enumeration //get higher power of both values and sum based on that public static MegaInt operator +(MegaInt leftSide, MegaInt rightSide) { byte highPower = (byte)Math.Max(leftSide.Power, rightSide.Power); ulong totalVal = OverPowerDiff(leftSide, highPower) + OverPowerDiff(rightSide, highPower); return new MegaInt(totalVal, highPower); } //minus based on the power difference public static MegaInt operator -(MegaInt leftSide, MegaInt rightSide) { if (rightSide > leftSide) { return 0; } if (leftSide.Power - rightSide.Power > 7) { return leftSide; } ulong totalVal = leftSide.Val - (OverPowerDiff(rightSide, leftSide.Power)); return new MegaInt(totalVal, leftSide.Power); } //Simple multiplication with powers public static MegaInt operator *(MegaInt leftSide, MegaInt rightSide) { return new MegaInt((ulong)leftSide.Val * (ulong)rightSide.Val, (byte)(leftSide.Power + rightSide.Power)); } //Simple division with powers public static MegaInt operator /(MegaInt leftSide, MegaInt rightSide) { return new MegaInt(((ulong)(leftSide.Val - 1) / (ulong)rightSide.Val) + 1, (byte)(leftSide.Power - rightSide.Power)); } #endregion #endregion #endregion } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 //ArmanDoesStuff 2017 //MegaInt 1.0 using System ; public class MegaInt { #region variables string [ ] PowerNames = new string [ ] { "" , "Thousand" , "Million" , "Billion" , "Trillion" , "Quadrillion" , "Quintillion" , "Sextillion" , "Septillion" , "Octillion" , "Nonillion" , "Decillion" , "Undecillion" , "Duodecillion" , "Tredecillion" , "Quattuordecillion" , "Quindecillion" , "Sexdecillion" , "Septendecillion" , "Octodecillion" , "Novemdecillion" , "Vigintillion" } ; uint storedValue ; //actual data, holds Value and Power #endregion #region accessors //First 8 bits are Power (up to 10^255) byte Power { get { //return as byte (pushed right to override Val) return ( byte ) ( storedValue >> 24 ) ; } set { //set as left bits, add original Val storedValue = ( uint ) ( value << 24 ) + ( storedValue & 16777215 ) ; } } //Last 24 are Value(16777216 - accuracy to 7 - 9999999) uint Val { get { //return with Power masked out return ( storedValue & 16777215 ) ; //16777215 = 0000 0000 1111 1111 1111 1111 1111 1111 } set { //set unmasked since the Power bits should never be set anyway. Add original Power to left bits storedValue = ( uint ) ( ( value /* & 16777215 */ ) + ( Power << 24 ) ) ; } } #endregion #region functions //divide by power of 10 public static MegaInt Pow10Div ( MegaInt a , byte p ) { //get power difference int outPower = a . Power - p ; if ( outPower < 0 ) { string outValString = a . Val . ToString ( ) ; //take off last digets to the amount of power or until only one remains return ulong . Parse ( outValString . Substring ( 0 , outValString . Length - Math . Min ( Math . Abs ( outPower ) , outValString . Length - 1 ) ) ) ; } else { //if there is power remaining after divide, just output val with new power return new MegaInt ( a . Val , ( byte ) outPower ) ; } } public MegaInt Pow10Div ( byte p ) { return Pow10Div ( this , p ) ; } //divide by mult of 10 public static MegaInt Pow10Mult ( MegaInt a , byte p ) { //get power difference return new MegaInt ( a . Val , ( byte ) ( a . Power + p ) ) ; } public MegaInt Pow10Mult ( byte p ) { return Pow10Mult ( this , p ) ; } //divide by power of 10, round up public static uint OverPowerDiff ( MegaInt a , byte highPower ) { return ( uint ) ( ( a . Val - 1 ) / ( Math . Pow ( 10 , highPower - a . Power ) ) ) + 1 ; } uint [ ] ConvertToMega ( string a ) { return ConvertToMega ( a , 0 ) ; } //Power Function public static MegaInt PowerMult ( MegaInt init , MegaInt mult , int pow ) { for ( int i = 0 ; i < pow ; i ++ ) { init *= mult ; } return init ; } uint [ ] ConvertToMega ( string a , byte p ) { //normalise to 7 digits if there is power while ( a . Length < 7 && p > 0 ) { a += "0" ; p -- ; } //accuracy to 7 digits since 24-bits go up to 16777216 - 8 characters while ( a . Length > 7 ) { //remove end number and increase power of 10 by one a = a . Remove ( a . Length - 1 ) ; p ++ ; } //return as values suitable for variables return new uint [ 2 ] { uint . Parse ( a ) , p } ; } #region constuctors public MegaInt ( ulong a ) { uint [ ] i = ConvertToMega ( a . ToString ( ) ) ; Val = i [ 0 ] ; Power = ( byte ) i [ 1 ] ; } public MegaInt ( uint a ) { uint [ ] i = ConvertToMega ( a . ToString ( ) ) ; Val = i [ 0 ] ; Power = ( byte ) i [ 1 ] ; } public MegaInt ( ulong a , byte p ) { uint [ ] i = ConvertToMega ( a . ToString ( ) , p ) ; Val = i [ 0 ] ; Power = ( byte ) i [ 1 ] ; } public MegaInt ( string a ) { #if UNITY_EDITOR //Deactivate sanitized input check because I'm not using direct user input to MegaInt. a = a . TrimStart ( '0' ) ; if ( ! a . IsDigitsOnly ( ) || a == "" ) { Val = 0 ; Power = 0 ; return ; } #endif uint [ ] i = ConvertToMega ( a ) ; Val = i [ 0 ] ; Power = ( byte ) i [ 1 ] ; } #endregion #region implicits public static implicit operator MegaInt ( string a ) { return new MegaInt ( a ) ; } public static implicit operator MegaInt ( uint a ) { return new MegaInt ( a ) ; } public static implicit operator MegaInt ( ulong a ) { return new MegaInt ( a ) ; } public static implicit operator string ( MegaInt a ) { return a . ToString ( ) ; } #endregion #region overrides public override string ToString ( ) { if ( Val < 1000 ) //only act on Values above 1 thousand { return Val . ToString ( ) ; } //the higher the power the further the point moves until it resets at 10^3 to x.xxx int digits = Power + ( Val . ToString ( ) . Length ) ; //number of digits if ( digits > 66 ) //if above Vigintillion then just show power { return Val . ToString ( ) . Substring ( 0 , 4 ) . Insert ( 1 , "." ) + " 10^" + digits ; } int decimalPlace = digits % 3 == 0 ? 3 : digits % 3 ; // + (Value < 0 ? 1 : 0); if doing negetives //get first 4 digits as significant figures //Add decimal point at correct place (as described above) //add power name (power, plus extra power for length minus the first 3) over 3 for because increments are 10^3 return Val . ToString ( ) . Substring ( 0 , 4 ) . Insert ( decimalPlace , "." ) + " " + PowerNames [ ( digits - 1 ) / 3 ] ; } //I have no idea what I'm doing public override bool Equals ( object obj ) { var item = obj as MegaInt ; if ( item == null ) { return false ; } return this . storedValue . Equals ( item . storedValue ) ; } //I have no idea what I'm part II public override int GetHashCode ( ) { return this . storedValue . GetHashCode ( ) ; } #region equalities public static bool operator < ( MegaInt leftSide , MegaInt rightSide ) { //check powers, then check values (if powers are equal) if ( leftSide . Power < rightSide . Power || ( leftSide . Power == rightSide . Power && leftSide . Val < rightSide . Val ) ) { return true ; } return false ; } public static bool operator > ( MegaInt leftSide , MegaInt rightSide ) { //same as above (with signs switched) if ( leftSide . Power > rightSide . Power || ( leftSide . Power == rightSide . Power && leftSide . Val > rightSide . Val ) ) { return true ; } return false ; } public static bool operator == ( MegaInt leftSide , MegaInt rightSide ) { //powers AND values must match if ( leftSide . storedValue == rightSide . storedValue ) { return true ; } return false ; } public static bool operator != ( MegaInt leftSide , MegaInt rightSide ) { //defined above then inverted if ( leftSide == rightSide ) { return false ; } return true ; } #endregion #region enumeration //get higher power of both values and sum based on that public static MegaInt operator + ( MegaInt leftSide , MegaInt rightSide ) { byte highPower = ( byte ) Math . Max ( leftSide . Power , rightSide . Power ) ; ulong totalVal = OverPowerDiff ( leftSide , highPower ) + OverPowerDiff ( rightSide , highPower ) ; return new MegaInt ( totalVal , highPower ) ; } //minus based on the power difference public static MegaInt operator - ( MegaInt leftSide , MegaInt rightSide ) { if ( rightSide > leftSide ) { return 0 ; } if ( leftSide . Power - rightSide . Power > 7 ) { return leftSide ; } ulong totalVal = leftSide . Val - ( OverPowerDiff ( rightSide , leftSide . Power ) ) ; return new MegaInt ( totalVal , leftSide . Power ) ; } //Simple multiplication with powers public static MegaInt operator * ( MegaInt leftSide , MegaInt rightSide ) { return new MegaInt ( ( ulong ) leftSide . Val * ( ulong ) rightSide . Val , ( byte ) ( leftSide . Power + rightSide . Power ) ) ; } //Simple division with powers public static MegaInt operator / ( MegaInt leftSide , MegaInt rightSide ) { return new MegaInt ( ( ( ulong ) ( leftSide . Val - 1 ) / ( ulong ) rightSide . Val ) + 1 , ( byte ) ( leftSide . Power - rightSide . Power ) ) ; } #endregion #endregion #endregion }

Thanks for reading and have a great day!