"The operation overflows at compile time in checked mode"

Greetings, all.
I am having an issue in my code where it gives the error “The operation overflows at compile time in checked mode”. This happens with the following code:

BigInteger NitrogenLower = 71232955 * 100000000000000;
BigInteger NitrogenUpper = 74625 * 100000000000000000;
BigInteger NeonUpper = 52725 * 100000000000000000;
BigInteger NeonLower = 50328409 * 100000000000000;
BigInteger ArgonUpper = 58175 * 100000000000000000;
BigInteger ArgonLower = 56046136 * 100000000000000;
BigInteger OxygenUpper = 6455 * 1000000000000000000;
BigInteger OxygenLower = 61615909 * 100000000000000;
BigInteger CarbonDioxideUpper = 175 * 100000000000000000;
BigInteger CarbonDioxideLower = 16704545 * 1000000000000;

I believe it is likely the large numbers causing some sort of issue, or the "(int)"s and the "BigInteger"s in the following code conflicting, although the errors occur at the first number of each of the variables (e.g. 71232955, 74625 etc.). Here is where the variables are used.

NitrogenPerBreath = UnityEngine.Random.Range((int)NitrogenLower, (int)NitrogenUpper);
NeonPerBreath = UnityEngine.Random.Range((int)NeonLower, (int)NeonUpper);
ArgonPerBreath = UnityEngine.Random.Range((int)ArgonLower, (int)ArgonUpper);
OxygenPerBreath = UnityEngine.Random.Range((int)OxygenLower, (int)OxygenUpper);
CarbonDioxidePerBreath = UnityEngine.Random.Range((int)CarbonDioxideLower, (int)CarbonDioxideUpper);

I hope this is helpful to those trying to help me; I would like to know what other, more skilled (I am fairly new to unity and c#) people could advise me to do.
Thanks.

Each arithmetic type has a specific range. This error essentially means that the value you are working with falls outside that range.

For example, when you write:

BigInteger NitrogenLower = 71232955 * 100000000000000;

you are multiplying an int value with a long value. The multiplication is performed using two long, producing another long, and only afterward is the result converted to BigInteger. The issue is that if the intermediate long result exceeds the long range at any point, an overflow error occurs before the value is converted to BigInteger.

The same problem appears in the following code:

(int)NitrogenLower

Here, NitrogenLower is a BigInteger. If the value stored in it is outside the range of int, casting it to int will produce the same error.

Using BigInteger is generally not a good way to represent numerical values in games for several reasons that are beyond the scope of this explanation. However, you have a few alternatives:

  • Use the long type if your numbers fall within its range.
  • Use a different unit of measurement. For example, instead of storing distances in millimeters, you could store them in kilometers when dealing with large values. If you require more precision than a float or a double can provide, you could define a custom structure that stores the number in parts. For instance, a struct could contain an int for kilometers, another for meters, another for millimeters, and so on. You would also need to override the operators you require, such as + for addition or equality operators to define how comparisons between these structs work. Think of it like using two 10-sided dice to simulate a single 100-sided die.

Since your first code example contains numbers with many zeros, the first solution (using different units) should be sufficient. However, in your second example you attempt to generate a random number within a very large range. In that case you have two options:

  • Implement your own numeric type as described above, or
  • Use multiple random numbers. For example, one random number could generate the most significant digits (e.g., the first three digits), the next random number the following three digits, and so on. You would then combine them into your custom numeric type.

If you need both large range and high precision, beyond what long can represent, more precision than the 6-7 reliable digits of a float or the 15-16 of a double, or exact accuracy for large numbers where changing units is not possible, you will need to implement your own numeric type. The correct approach depends on the range and precision required for what you are trying to achieve.

There are other, more advanced solutions such as fixed-point integers, custom integer types, or fixed-point math libraries, but in most cases these are unnecessary. The simpler approaches are the ones described above, especially if you intend to perform arithmetic operations with those numbers: change the units you use and ensure that the values generated in your random operations stay within the range supported by those numeric types, or implement a simple struct that stores the information you need, as described.

You should store those values normalized to a 0-1 range as a double, for instance:

double NitrogenLower = 0.71232955;
double NitrogenUpper = 0.00074625;

Mathematically speaking, you can completely discard the 1000000000000000 since that’s just “padded zeroes” and still compute the correct results. All large number calculations on a computer commonly are done within the 0-1 range using float or double types for this and several other reasons.

If eventually you need to display that value it just becomes a matter of string formatting, ie appending enough zeros at the end respectively displaying it in scientific notation like 7.4625e+13.

If, and only if, you have a value so small that a double can’t capture enough precision then use decimal as the type.

Since Unity’s random only accepts float or int values, and float value precision in the 0-1 range has at most 7 “reliable” decimal digits (6 if you want to be conservative) you may have to entirely avoid any such methods and instead, find and use a double-based equivalent such as System.Random. Otherwise you lose too much, if not all precision in those values.

This page compares float, double, and decimal and provides their range of accuracy.