I would like to have the following class for multiple types (eg float
, double
, decimal
):
internal class Progressing_Average
{
public Progressing_Average()
{
_Count = 0;
Value = 0;
}
public double Value { get; private set; }
private int _Count { get; set; }
public void AddValue(double input)
{
if (_Count == int.MaxValue)
{
throw new IndexOutOfRangeException("max amount has been reached! use preciseaverage or moving avg instead!");
}
_Count ;
Value = (input - Value) / _Count;
}
}
I tried with internal class Progressing_Average<T>
but the issue is that i cant perform all nessesary operations on T
Operator '-' cannot be applied to operands of type 'T' and 'T'
Is there a way to do this, does it come with performance implications or should I rather use something in the lines of Processing_Average_Double
having one class for each type makes the code quite duplicate and harder to maintain i believe
CodePudding user response:
Using Lambdas and Expressions
Here is the example code you asked for
class Program
{
static void Main(string[] args)
{
var avg_float = new Progressing_Average<float>();
avg_float.AddValue(1f);
avg_float.AddValue(2f);
avg_float.AddValue(3f);
Console.WriteLine($"ave={avg_float.Value:f4}");
var avg_dec = new Progressing_Average<decimal>();
avg_dec.AddValue(1m);
avg_dec.AddValue(2m);
avg_dec.AddValue(3m);
Console.WriteLine($"ave={avg_dec.Value:c}");
}
}
with sample output
ave=2.0000
ave=$2.00
Generic Math
Ever since NET 3.5, you can do generic math using Expressions and Lambda functions. There is a bit of setup that is needed, but the example below is fairly straightforward.
Some notes:
Define generic parameter
T
and restrict it to numeric types. Newer NET implementations have better ways of doing this but for compatibility, with .NET Framework I use the followingwhere
constraint inT
Define static methods (actually static anonymous functions) for the required math operations. Some basic ones are
-
,*
,/
and the conversion fromint
toT
for mixing_Count
.These methods are built using a static constructor for the type and are called once for each different
T
used.Replace the math operators with the equivalent functions. Like
a b
becomesAdd(a,b)
.
Class Definition
public class Progressing_Average<T> where T: struct,
IComparable,
IComparable<T>,
IConvertible,
IEquatable<T>,
IFormattable
{
static Progressing_Average()
{
var a = Expression.Parameter(typeof(T));
var b = Expression.Parameter(typeof(T));
var n = Expression.Parameter(typeof(int));
Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(a, b), a, b).Compile();
Sub = Expression.Lambda<Func<T, T, T>>(Expression.Subtract(a, b), a, b).Compile();
Mul = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(a, b), a, b).Compile();
Div = Expression.Lambda<Func<T, T, T>>(Expression.Divide(a, b), a, b).Compile();
FromInt = Expression.Lambda<Func<int, T>>(Expression.Convert(n, typeof(T)), n).Compile();
}
static Func<T, T, T> Add { get; }
static Func<T, T, T> Sub { get; }
static Func<T, T, T> Mul { get; }
static Func<T, T, T> Div { get; }
static Func<int, T> FromInt { get; }
public Progressing_Average()
{
_Count = 0;
Value = FromInt(0);
}
public T Value { get; private set; }
private int _Count { get; set; }
public void AddValue(T input)
{
if (_Count == int.MaxValue)
{
throw new IndexOutOfRangeException("max amount has been reached! use preciseaverage or moving avg instead!");
}
_Count ;
Value = Add(Value, Div(Sub(input, Value), FromInt(_Count)));
}
}
Support for User Defined Types
If you relax the constraints a bit, you can even use your own types above.
Define the processing class as
public class Progressing_Average<T> where T: struct,
IEquatable<T>,
IFormattable
and a user-defined type, such as
public readonly struct Point :
IEquatable<Point>,
IFormattable
{
public Point(float x, float y) : this()
{
X = x;
Y = y;
}
public float X { get; }
public float Y { get; }
public static Point operator (Point a, Point b)
=> new Point(a.X b.X, a.Y b.Y);
public static Point operator -(Point a, Point b)
=> new Point(a.X - b.X, a.Y - b.Y);
public static Point operator *(Point a, Point b)
=> new Point(a.X * b.X, a.Y * b.Y);
public static Point operator /(Point a, Point b)
=> new Point(a.X / b.X, a.Y / b.Y);
public static implicit operator Point(int x)
=> new Point(x,x);
public static implicit operator Point(float x)
=> new Point(x,x);
public static implicit operator Point(decimal x)
=> new Point((float)x,(float)x);
public static implicit operator Point(double x)
=> new Point((float)x,(float)x);
#region IFormattable Members
public string ToString(string formatting, IFormatProvider provider)
{
return $"({X},{Y})";
}
public string ToString(string formatting)
=> ToString(formatting, null);
public override string ToString()
=> ToString("g");
#endregion
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(Point)</code></returns>
public override bool Equals(object obj)
{
return obj is Point item && Equals(item);
}
/// <summary>
/// Checks for equality among <see cref="Point"/> classes
/// </summary>
/// <returns>True if equal</returns>
public bool Equals(Point other)
{
return X.Equals(other.X)
&& Y.Equals(other.Y);
}
/// <summary>
/// Calculates the hash code for the <see cref="Point"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295) * hc X.GetHashCode();
hc = (-1521134295) * hc Y.GetHashCode();
return hc;
}
}
public static bool operator ==(Point target, Point other) { return target.Equals(other); }
public static bool operator !=(Point target, Point other) { return !target.Equals(other); }
#endregion
}
Now watch as you can use the above struct
to get averages
class Program
{
static void Main(string[] args)
{
var avg_vec = new Progressing_Average<Point>();
avg_vec.AddValue(new Point(1, 2));
avg_vec.AddValue(new Point(2, 3));
avg_vec.AddValue(new Point(3, 4));
Console.WriteLine($"ave={avg_vec.Value}");
}
}
with output
ave=(2,3)
There is some skullduggery going on here, because behind the scenes the expression Expression.Convert()
automatically calls any implicit operator
for conversions, as well as any user defined arithmetic operators such as operator (a,b)
.
CodePudding user response:
In .NET 7, you'll be able to use number generics (generic math). In .NET 6, this is not possible.
https://devblogs.microsoft.com/dotnet/dotnet-7-generic-math/
In your example, you would use it like this:
internal class Progressing_Average<T> where T : INumber<T>
{
public Progressing_Average()
{
_Count = 0;
Value = default;
}
public T Value { get; private set; }
private int _Count { get; set; }
public void AddValue(T input)
{
if (_Count == int.MaxValue)
{
throw new IndexOutOfRangeException("max amount has been reached! use preciseaverage or moving avg instead!");
}
_Count ;
Value = (input - Value) / _Count;
}
}
CodePudding user response:
You can use Convert.ChangeType
.
Here is an idea you may like to do
internal class Progressing_Average
{
public Progressing_Average()
{
_Count = 0;
Value = 0;
}
public double Value { get; private set; }
private int _Count { get; set; }
// now val could be float decimal int etc..
public void AddValue(object val)
{
double input = (double)Convert.ChangeType(val, typeof(double))
if (_Count == int.MaxValue)
{
throw new IndexOutOfRangeException("max amount has been reached! use preciseaverage or moving avg instead!");
}
_Count ;
Value = (input - Value) / _Count;
}
}