Is there a way to convert an integer to a approviate float and back? Some function that gets and returns ints need float during the computation.
Look at this:
void Main()
{
Interpolation.LinearBetween((0, 5), (10, 3), 7).DumpIt();
Interpolation.LinearBetween((0.0f, 5), (10, 3), 7).RoundToInt().DumpIt();
}
public static class Interpolation
{
public static T LinearBetween<T>((T x1, T y1) a, (T x2, T y2) b, T x) where T : INumber<T>
{
//f(x) = m*x + n
//one oft operands needs to be floating point type
var m = (b.y2 - a.y1) / (b.x2 - a.x1);
var n = b.y2 - m * b.x2;
return (x * m + n);
}
public static int RoundToInt(this float value)
{
return (int)Math.Round(value);
}
public static void DumpIt(this object toDump)
{
Console.WriteLine(toDump);
}
}
Doing the whole LinearBetween is not correct. Edit: Rewitten the question.
What you'd like to do here is, in the context of C#'s generic math, when performing a complex calculation on values of some arbitrary numeric type T
, when T
also happens to be a generic binary integer, to perform the calculation using floating point precision and only round off at the very end.
This is possible, but awkward. Here's one possible way:
public static class Interpolation
{
public static T LinearBetween<T>((T x1, T y1) a, (T x2, T y2) b, T x) where T : INumber<T> =>
LinearBetween<T, double>(a, b, x); // TODO: decide whether float is precise enough
public static T LinearBetween<T, TFloat>((T x1, T y1) a, (T x2, T y2) b, T x)
where T : INumber<T>, INumberBase<T>
where TFloat : IFloatingPoint<TFloat>
{
if (NumericTypeInformation<T>.IsBinaryInteger)
{
// An integer type. Convert to the preferred float format, perform the calculation, then round at the very end.
var floatResult = LinearBetweenUnconverted(
(TFloat.CreateChecked(a.x1), TFloat.CreateChecked(a.y1)),
(TFloat.CreateChecked(b.x2), TFloat.CreateChecked(b.y2)),
TFloat.CreateChecked(x));
var rounded = TFloat.Round(floatResult, MidpointRounding.ToEven); // TODO: choose your preferred MidpointRounding
return T.CreateChecked(rounded);
}
else
{
// Not an integer type. Perform the calculation directly.
return LinearBetweenUnconverted(a, b, x);
}
}
static T LinearBetweenUnconverted<T>((T x1, T y1) a, (T x2, T y2) b, T x) where T : INumber<T>
{
var m = (b.y2 - a.y1) / (b.x2 - a.x1);
var n = b.y2 - m * b.x2;
return (x * m + n);
}
}
// Generic class to static cache type information about a given numeric type.
public static class NumericTypeInformation<TNumber> where TNumber : INumberBase<TNumber>
{
readonly static Lazy<bool> isBinaryInteger = new Lazy<bool>(
() => typeof(TNumber)
.GetInterfaces()
.Where(i => i.IsGenericType)
.Select(i => i.GetGenericTypeDefinition())
.Any(i => i == typeof(IBinaryInteger<>)));
public static bool IsBinaryInteger => isBinaryInteger.Value;
}
Demo fiddle here.
Key points:
You can use INumberBase<TSelf>.CreateChecked<TOther>(TOther)
to create one type of number from another, e.g. to create a float
or double
from an int
or short
and vice versa. This particular method throws in the event of an overflow. If you don't want that, you could use CreateTruncating()
or CreateSaturating()
Once you have converted your arbitrary numbers to some floating type, you can do the calculation in that type, then round off using IFloatingPoint<TSelf>.Round(TSelf, MidpointRounding)
, then finally convert back using CreateChecked()
in reverse.
I chose to do the intermediate calculations in double
rather than float
purely due to personal preference.
Checking to see whether whether an arbitrary INumber<T>
type implements IBinaryInteger<T>
is bizarrely hard! I thought it would be possible to do something like
static bool IsBinaryInteger<T>() where T : INumber<T> =>
typeof(T).IsAssignableTo(typeof(IBinaryInteger<T>);
But this results in a compilation error
The type '
T
' cannot be used as type parameter 'TSelf
' in the generic type or method 'IBinaryInteger<TSelf>
'. There is no boxing conversion or type parameter conversion from 'T
' to 'System.Numerics.IBinaryInteger<T>
'.
I think (but am not sure) that the problem is that IBinaryInteger<TSelf>
has a constraint where TSelf : IBinaryInteger<TSelf>
-- so it may be impossible to check it a type TSelf
implements IBinaryInteger<TSelf>
unless it is already constrained to do so!
As an alternative, I had to loop though all the interfaces implemented by T
and check whether any are generic interfaces whose open generic type is IBinaryInteger<>
. Since this is likely slow, I cached the result statically.