Namespace

All Generic Arithmetic classes are in the GenericArithmetic namespace. Add this at the top of your scripts to access them:

using GenericArithmetic;

How It Works

Generic Arithmetic lets you write generic classes and methods that perform arithmetic and comparison operations on any numeric or operator-enabled type, without knowing the concrete type at compile time.

The key concept is the computator: a small class that knows how to perform every operation (+, -, *, /, comparisons, etc.) for a specific type combination. Computators are discovered automatically at startup; you never call them directly.

Built-in computators cover all common Unity numeric and vector types: int, float, double, long, byte, sbyte, short, uint, ulong, ushort, char, decimal, Vector2, and Vector3. For any other type, you add support in one of three ways covered below.

There are three fallback strategies for types without a registered computator, configured in the Configuration inspector:

  • Throw - throws an exception immediately; safest for catching missing computators early.
  • Dynamic - uses C# dynamic; reliable and handled by the .NET runtime, but slower. Does not work on IL2CPP.
  • Expression - compiles a delegate on first use; near-native speed after the first call, but does not work on IL2CPP.

Computable<T>

Computable<T> is a lightweight struct wrapper around any value. It exposes all arithmetic, bitwise, comparison, and boolean operators directly on the wrapped value, so generic code can use standard C# operator syntax.

Operations

Wrap a value with an implicit cast or with Computable<T>.From(), then use operators naturally:

class MyGenericClass<T>
{
    void Example(T a, T b)
    {
        Computable<T> ca = a;
        Computable<T> cb = b;

        T sum  = ca + cb;
        T diff = ca - cb;
        T prod = ca * cb;
        bool gt = ca > cb;
    }
}

The underlying value is always accessible through the value field or by implicitly casting back to T.

To cast between types, use From<TOther>() and To<TOther>():

Computable<float> cf = Computable<float>.From<int>(42); // casts int to float
int i = cf.To<int>();                                       // casts float to int

Same-Type Limitation

Operators on Computable<T> only work when both operands share the same T. Cross-type operations like int + float are not supported through this struct; use Calculate<TResult> instead.

// OK: same type on both sides
Computable<int> a = 3;
Computable<int> b = 5;
Computable<int> result = a + b;

// Cross-type: use Calculate instead
float result2 = Calculate<float>.Add<int, float>(3, 1.5f);

Calculate<TResult> & Evaluate

These static classes give you explicit control over operations and their result type. They are the best choice when operands have different types, when you need the result in a specific type, or when you want to keep the code as a plain method call rather than introducing wrapper structs.

Binary Operations

Calculate<TResult> takes two type parameters for the operands and returns TResult:

// Multiply a float scale factor by a Vector3
Vector3 scaled = Calculate<Vector3>.Multiply<float, Vector3>(scale, direction);

// Add two ints, produce a long
long total = Calculate<long>.Add<int, int>(a, b);

Available binary operations: Add, Subtract, Multiply, Divide, Modulo, And, Or, Xor.

Unary Operations

Unary methods take one type parameter for the operand:

float negated   = Calculate<float>.Negate<float>(value);
int   shifted   = Calculate<int>.LeftShift<int>(value, 2);
long  casted    = Calculate<long>.Cast<int>(value);

Available unary operations: Plus, Negate, Invert, Complement, Increment, Decrement, Cast, LeftShift, RightShift.

Comparisons

Evaluate handles comparisons and boolean tests; all methods return bool:

bool isGreater = Evaluate.GreaterThan<float, float>(a, b);
bool isTrue    = Evaluate.True<MyType>(myValue);

Available comparisons: Equal, NotEqual, GreaterThan, LessThan, GreaterThanOrEqual, LessThanOrEqual, True, False.

Try Methods

Both classes expose a nested Try class with non-throwing versions of every method. They return true and set the out parameter on success, or return false (without throwing) when the operation is not supported for the given types:

if (Calculate<float>.Try.Add<int, float>(a, b, out float result))
    Debug.Log(result);

if (Evaluate.Try.GreaterThan<MyType, MyType>(x, y, out bool gt))
    Debug.Log(gt);

Math<T>

Math<T> is a static utility class that provides generic equivalents of UnityEngine.Mathf and System.Math, built entirely on Calculate<T> and Evaluate. It works with any numeric type T that supports the necessary operators.

Typed constants are exposed as static readonly fields, computed once per closed type (e.g. Math<float>) and cached: Zero, One, PI, E, Deg2Rad, Rad2Deg.

float clamped = Math<float>.Clamp(value, 0f, 1f);
float angle   = Math<float>.DeltaAngle(current, target);
float s       = Math<float>.Sin(Math<float>.PI / 6f);

A nested Math<T>.Try class mirrors every method with a non-throwing version, following the same pattern as Calculate<T>.Try:

if (Math<float>.Try.Sqrt(value, out float root))
    Debug.Log(root);
Methods that require transcendental math (rounding, trig, powers, logarithms) cast through double internally. All other methods (Abs, Sign, Min, Max, Clamp, Lerp, Repeat, etc.) stay entirely within generic arithmetic. See the Public API for the full method list.

Source Generator

The [GenerateComputators] assembly-level attribute tells the included Roslyn source generator to produce all four computator classes at compile time, with no .cs files written to Assets. Generated computators are fully AOT-safe, making this the recommended approach for IL2CPP builds.

Place the attribute in any script in your project (typically in a dedicated Computators.cs file):

// Single-type form: generates BinaryCalculator, UnaryCalculator,
// BinaryEvaluator, and UnaryEvaluator all at once
[assembly: GenerateComputators(typeof(MyValue))]

// Three-type form: generates BinaryCalculator and BinaryEvaluator
// for cross-type operations (e.g. float * MyValue -> MyValue)
[assembly: GenerateComputators(typeof(MyValue), typeof(float), typeof(MyValue))]

The generator inspects the type's operators at compile time and only emits code for operations that are actually defined; unsupported ones throw InvalidOperationException.

The Generic Arithmetic source generator DLL must be present in your project (included in the package at Assets/Generic Arithmetic/Source Generator/). Priority order: Manual > Source Generated > Default computator.

Manual Computators

Manual computators are regular C# classes you write yourself. They run at the same speed as source-generated ones, but give you full control over how each operation is implemented, making them the right choice when you need custom behavior (e.g. clamping on add, saturating arithmetic, or any logic that goes beyond a direct operator call).

Derive from one of the four abstract base classes and implement its methods. The package discovers subclasses automatically at startup with no registration step, and they take priority over source-generated and default computators.

You only need to create the computator classes that correspond to the operations you actually use. For example, if you only use Calculate<T> binary operations, only a BinaryCalculator is needed.

Using the Creator Tool

The easiest way to create a manual computator is through the editor tool at Tools > Generic Arithmetic > Create Manual Computator Script.

Create Manual Computator Script window
Create Manual Computator Script window

Select the output folder in the Project window, enter the type name (or three types for a cross-type computator), and click Create. The generated script contains all required method stubs with correct implementations pre-filled where possible. Only valid operations for the type are inlined; unsupported ones throw InvalidOperationException.

Writing Manually

Derive from the relevant base class and implement all abstract methods:

// Binary arithmetic for a custom type
class ManualBinaryCalculatorMyValue : BinaryCalculator<MyValue, MyValue, MyValue>
{
    public override MyValue Add(MyValue a, MyValue b)      => a + b;
    public override MyValue Subtract(MyValue a, MyValue b) => a - b;
    public override MyValue Multiply(MyValue a, MyValue b) => a * b;
    public override MyValue Divide(MyValue a, MyValue b)   => a / b;
    public override MyValue Modulo(MyValue a, MyValue b)   => throw new InvalidOperationException();
    public override MyValue And(MyValue a, MyValue b)      => throw new InvalidOperationException();
    public override MyValue Or(MyValue a, MyValue b)       => throw new InvalidOperationException();
    public override MyValue Xor(MyValue a, MyValue b)      => throw new InvalidOperationException();
}

// Cross-type: float * MyValue -> MyValue
class ManualBinaryCalculatorMyValueCross : BinaryCalculator<MyValue, float, MyValue>
{
    public override MyValue Multiply(float a, MyValue b) => b * a;
    // ... other methods
}

The four base classes and their purposes:

  • BinaryCalculator<TResult, TParamA, TParamB> - arithmetic and bitwise operations (+, -, *, /, %, &, |, ^).
  • UnaryCalculator<TResult, TParam> - unary operations (+, -, !, ~, ++, --, <<, >>) and explicit casting.
  • BinaryEvaluator<TParamA, TParamB> - comparison operations (==, !=, >, <, >=, <=).
  • UnaryEvaluator<TParam> - boolean evaluation (operator true / operator false).

Configuration

All editor tools are under Tools > Generic Arithmetic:

Tools > Generic Arithmetic menu
Tools > Generic Arithmetic menu

Open the configuration asset via Tools > Generic Arithmetic > Configuration to control how the package behaves at runtime.

Generic Arithmetic Configuration inspector
Generic Arithmetic Configuration inspector

Default Computator

Controls what happens when an operation is called for a type that has no registered computator.

  • Throw - throws an exception; safest for catching missing computators early.
  • Dynamic - uses C# dynamic; reliable and handled by the .NET runtime, but slower. Does not work on IL2CPP.
  • Expression - compiles a delegate on first use; near-native performance after the first call, but does not work on IL2CPP.

Built-in Computators

A read-only list of all computators shipped with the package (all primitive and vector types). Each entry shows whether it is source-generated or written manually.

User Computators

A list of all computators found in your project assemblies. Click any entry to ping its source script in the Project window. This section is useful for verifying that your computators were picked up correctly.

The configuration asset is created automatically at Assets/Resources/Generic Arithmetic Configuration.asset the first time it is accessed. You can safely commit it to version control.

Tests

Unit Tests

The package ships with NUnit tests located in Assets/Generic Arithmetic/Tests/. Run them from the Window > General > Test Runner panel in Unity. They cover:

  • Computable<T> - implicit conversions, From/To casting, Equals override (against another Computable<T>, a bare T, null, and an unrelated type), ToString/GetHashCode delegation, and operator wiring.
  • Calculate<T>.Try - returns true with the correct result for valid operations, returns false without throwing for invalid ones, and correctly returns true with PositiveInfinity for float division by zero.
  • Evaluate.Try - same contract as above for comparison operations.
  • Math<T> - constants, basic operations (Abs, Sign, Min, Max, Clamp, Approximately), interpolation (Lerp, MoveTowards, SmoothStep, etc.), periodic (Repeat, PingPong, DeltaAngle), rounding, powers, logarithms, and trigonometry including hyperbolic functions. Each method is tested against known float values.
  • Math<T>.Try - returns true and the correct result for valid operations, returns false without throwing for unsupported types, and correctly uses out bool result for Approximately.

Bulk Tests

Open Tools > Generic Arithmetic > Bulk Tests to run all registered computators against a broad set of predefined values and review the results visually. This is useful for spotting edge cases across many type combinations at once.

Bulk Tests window
Bulk Tests window

Results are color-coded: green for success, yellow for a wrong value, red for an unexpected exception, and orange when an expected exception was not thrown. Use the Result Type and Operations filter dropdowns to narrow down the view to the cases you care about.

Demo

The package ships with a benchmark scene that compares four approaches to generic arithmetic: non-generic reference, naive generic, Computable<T>, and Calculate<T>. Run it to get a sense of the relative performance cost of each approach on your target platform.

Delete the Demo folder once you are done to remove unused assets from your project.

Author & Contact

Created by Juste Tools.

If you enjoy Generic Arithmetic, please consider leaving a review on the Asset Store. Your feedback helps improve the tool!