9

The following code is very repetitive:

 public static double Interpolate(double x1, double y1, double x2, double y2, double x)
    {
        return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
    }
    public static decimal Interpolate(decimal x1, decimal y1, decimal x2, decimal y2, decimal x)
    {
        return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
    }

However, my attempt to use generics does not compile:

 public static T Interpolate<T>(T x1, T y1, T x2, T y2, T x)
    {
        return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
    }

The error message is as follows:

Error 2 Operator '-' cannot be applied to operands of type 'T' and 'T' C:\Git...\LinearInterpolator.cs

How should I reuse my code?

Edit: fast runtime is important for this modules.

Arne Lund
  • 2,366
  • 3
  • 26
  • 39
  • 3
    Pretty sure you're more or less stuck with what you have there... – Servy Apr 24 '12 at 16:01
  • 1
    As `System.Double` and `System.Decimal` do not share a common ancestor defining the operators you need, I'm afraid you can't. – Nicolas Repiquet Apr 24 '12 at 16:04
  • C# does not have "generics" in common understanding of the word. It's not possible to use anything from T, unless it's type is already known (constrained). – Agent_L Apr 24 '12 at 16:11
  • 2
    @Agent_L I think the word you're looking for is 'Templates'. C# generics aren't 'Templates' as you see them in, say, c++. – Servy Apr 24 '12 at 16:11
  • 1
    Yes @Agent_L - I'm a former C++ programmer and C# doesn't have *templates* but it most definitely does have *generics* in what I consider to be the 'common understanding of the word' :) – Andras Zoltan Apr 24 '12 at 16:14
  • Well, it's no longer generic when you have to declare the type. That's the understanding I meant. Probably I am the one who misunderstood. – Agent_L Apr 24 '12 at 16:27
  • possible duplicate of [C# generic constraint for only integers](http://stackoverflow.com/questions/32664/c-sharp-generic-constraint-for-only-integers) – nawfal Apr 03 '13 at 00:07

4 Answers4

4

Your current code is fine as it is - possibly the best you can achieve without casting doubles to decimals.

A generic solution is possible, but would involve quite a lot of infrastructure - so much so that it would make things way too complex.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
4

Whenever I need to do this, I simply resort to dynamic. This will work just fine, but may be somewhat slower than the overloads you currently have.

 public static T Interpolate<T>(T x1, T y1, T x2, T y2, T x)
 {         
     return y1 + ((dynamic)x - x1) * ((dynamic)y2 - y1) / ((dynamic)x2 - x1);
 }
Gabe
  • 84,912
  • 12
  • 139
  • 238
3

You can't use operators in generics without specifying a base class constraint on the T.

You might do something like this: Creating a Math library using Generics in C# but personally, unless you really have lots of different formulae then I don't see much point.

There's also the possibility of dynamically compiling expression trees for T, rewriting the types between double/decimal from a 'model' tree generated by the C# compiler (keeping the arithmetic precedence etc)... if you're really interested in that I can post the code for such a solution (I'll need an hour or so though!). Runtime performance will be slower.

Community
  • 1
  • 1
Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • Thanks for disappointing. I am not interested in slow approaches, I'd rather live with my duplication. – Arne Lund Apr 24 '12 at 16:28
  • I don't know that a dynamically compiled version would be "slow", just slightly slower than the current overloads. I'd expect it to be comparable to my `dynamic` version. – Gabe Apr 24 '12 at 16:41
  • @Gabe you're probably right (and I personally hold a lot of stock in dynamically compiled and `dynamic` stuff, I often find performace exceeds my expectations, after the initial warm-up at least) - I might benchmark it out against your dynamic solution if I get a free slice of time. Would be really interesting to see :) – Andras Zoltan Apr 24 '12 at 16:46
2

You can use IConvertible. But the performance difference can be considerable depending on what you need. Compared with a method that use Single, the difference may reach almost 50% more. Doing 100000 iterations, with single takes 109ms and 156ms using Generic.

See this code (.Net 2):

using System;
using System.Text;
using NUnit.Framework;

namespace ProofOfConcept.GenericInterpolation
{
    /// <summary>
    /// Proof of concept for a generic Interpolate.
    /// </summary>
    [TestFixture]
    public class GenericInterpolationTest
    {
        /// <summary>
        /// Interpolate test.
        /// </summary>
        [Test]
        public void InterpolateTest()
        {
            Int16 interpolInt16 = Interpolate<Int16>(2, 4, 5, 6, 7);
            Int32 interpolInt32 = Interpolate<Int32>(2, 4, 5, 6, 7);

            Double interpolDouble = Interpolate<Double>(2, 4, 5, 6, 7);
            Decimal interpolDecimal = Interpolate<Decimal>(2, 4, 5, 6, 7);

            Assert.AreEqual((Int16)interpolInt32, (Int16)interpolInt16);
            Assert.AreEqual((Double)interpolDouble, (Double)interpolDecimal);

            //performance test
            int qtd = 100000;
            DateTime beginDt = DateTime.Now;
            TimeSpan totalTimeTS = TimeSpan.Zero;
            for (int i = 0; i < qtd; i++)
            {
                interpolDouble = Interpolate(2, 4, 5, 6, 7);
            }
            totalTimeTS = DateTime.Now.Subtract(beginDt);
            Console.WriteLine(
                "Non Generic Single, Total time (ms): " + 
                totalTimeTS.TotalMilliseconds);

            beginDt = DateTime.Now;
            for (int i = 0; i < qtd; i++)
            {
                interpolDouble = Interpolate<Double>(2, 4, 5, 6, 7);
            }
            totalTimeTS = DateTime.Now.Subtract(beginDt);
            Console.WriteLine(
                "Generic, Total time (ms): " + 
                totalTimeTS.TotalMilliseconds);
        }

        /// <summary>
        /// Interpolates the specified x1.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="x1">The x1.</param>
        /// <param name="y1">The y1.</param>
        /// <param name="x2">The x2.</param>
        /// <param name="y2">The y2.</param>
        /// <param name="x">The x.</param>
        /// <returns></returns>
        public static T Interpolate<T>(T x1, T y1, T x2, T y2, T x) where T : IConvertible
        {
            IConvertible x1C = x1 as IConvertible;
            IConvertible y1C = y1 as IConvertible;
            IConvertible x2C = x2 as IConvertible;
            IConvertible y2C = y2 as IConvertible;
            IConvertible xC = x as IConvertible;
            Decimal retDec = y1C.ToDecimal(null) + 
               (xC.ToDecimal(null) - x1C.ToDecimal(null)) * 
               (y2C.ToDecimal(null) - y1C.ToDecimal(null)) / 
               (x2C.ToDecimal(null) - x1C.ToDecimal(null));

            return (T)((IConvertible)retDec).ToType(typeof(T), null);
        }

        /// <summary>
        /// Interpolates the specified x1.
        /// </summary>
        /// <param name="x1">The x1.</param>
        /// <param name="y1">The y1.</param>
        /// <param name="x2">The x2.</param>
        /// <param name="y2">The y2.</param>
        /// <param name="x">The x.</param>
        /// <returns></returns>
        public static Single Interpolate(Single x1, Single y1, Single x2, Single y2, Single x)
        {
            Single retSing = y1 + (x - x1) * (y2 - y1) / (x2 - x1);

            return retSing;
        }
    }
}
Hailton
  • 1,192
  • 9
  • 14