2

I need to find out if a decimal value will fit into a type (the destination type is detected at runtime) and truncate it to its max or min value if not so I can send it thru the network.

I would like to avoid big switch sentences with types and I thought that maybe there is already something in the .NET Framework.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
  • 1
    How do you get the value in the first place? Is it in some other type, or text? – Ron Beyer May 11 '15 at 12:18
  • It is a decimal storing the value. Added in the question, thanks – Ignacio Soler Garcia May 11 '15 at 12:18
  • So you want to place it in the smallest type that will hold it? – Ron Beyer May 11 '15 at 12:19
  • I'm not entirely sure but I think a simple explicit cast will do exactly what you want ( so `(targetType)sourceVariable`) – Nils O May 11 '15 at 12:20
  • 2
    @IgnacioSolerGarcia How about http://stackoverflow.com/a/2683487/426894 ? – asawyer May 11 '15 at 12:20
  • @RonBeyer: no, I want to send the value thru the network and I have to convert it to the required network type. I want to leave the value as it is if it fits the type or truncate it to the Max / Min value if not. – Ignacio Soler Garcia May 11 '15 at 12:22
  • @NilsO: this will throw if the value does not fit as far as I know. – Ignacio Soler Garcia May 11 '15 at 12:22
  • 3
    *I would like to avoid big switch sentences with types and I thought that maybe there is already something in the .Net Framework.* Big switch types are the way to go. I can cook a completely unreadable `Expression` tree generator that will spit out dynamic code to check your values if you want... Guaranteed to be the the life of the party. – xanatos May 11 '15 at 12:33
  • @xanatos Can I join that party? ;P – Yuval Itzchakov May 11 '15 at 12:35
  • @IgnacioSolerGarcia I think you are asking about *validation*, not type checking. What do you mean by `network type`? The question doesn't make much sense as it is - .NET can't check for types it doesn't know about. Validating values against some schema *does* make sense - does my `System.Decimal` fit into the limits of attribute `X`defined in an XSD document, or database field? – Panagiotis Kanavos May 11 '15 at 12:41
  • 1
    why would you want a value of 12345.6789 fit into, for an example, one byte? what is the value of that bad value? or what you want is to split that large value into multiple byte and reconstruct it at the end? – Fredou May 11 '15 at 12:44
  • @Fredou: sorry, I thought this was explained in the question. There I say that the value should be truncated if it does not fit in the required type. In this case I would transfer 255 as it is the max value of a byte. – Ignacio Soler Garcia May 11 '15 at 12:46
  • @PanagiotisKanavos: it is not a network type, it is a .Net value type to be sent thru the network so it all relates to .Net types. – Ignacio Soler Garcia May 11 '15 at 12:46
  • @IgnacioSolerGarcia I think you should explain the *actual* problem you are trying to solve. `decimal` is a perfectly good .NET type. Did you use `decimal` as a `generic` numeric type and now have problems converting it to the actual types you need? Is that what prevents you from casting to the target type or calling, eg `Convert.ChangeType` ? – Panagiotis Kanavos May 11 '15 at 12:51
  • @YuvalItzchakov The code is read if you want to take a look. – xanatos May 11 '15 at 13:01
  • This action, restricting a value to a range by putting the value to the min or the max if necessary, has a name. It is done in electronics... Does anyone remember it? – xanatos May 11 '15 at 13:02
  • @xanatos Are you talking about a *clamper*? (*A clamper is an electronic circuit that fixes either the positive or the negative peak excursions of a signal to a defined value by shifting its DC value.*) – Yuval Itzchakov May 11 '15 at 13:05
  • 1
    @YuvalItzchakov I think it is [clipping](http://en.wikipedia.org/wiki/Clipping_%28audio%29)... But perhaps as a verb, when it is done voluntarily, to clamp is better than to clip. – xanatos May 11 '15 at 13:10
  • @PanagiotisKanavos: I really don't know why you need to know my real problems. I think the question is clear: I have a value with type decimal and I want to know if that value fits on another type and in case it does not I want to truncate the value to the type's max or min. Why is more information needed? – Ignacio Soler Garcia May 11 '15 at 13:59
  • @IgnacioSolerGarcia because what you are asking is data transformation, not type casting. .NET doesn't have any data transformation facilities. You are trying to solve the wrong problem – Panagiotis Kanavos May 11 '15 at 14:28
  • @PanagiotisKanavos: I agree that I'm trying to transform data, not to type casting. And what I was looking for is if .Net had this kind of facilities or not. As your answer is no please post it so I can accept it. I will implement my own switch statement. – Ignacio Soler Garcia May 11 '15 at 14:45

2 Answers2

5

This action has a name in signal processing: clipping.

And here there is the totally useless DecimalRestrictor<T>, based on a dinamically built Expression Tree in the form

x => x <= min ? min : x >= max ? max : (T)x;

plus an exception for decimal, float and double: all the three types can accept any decimal value.

The code:

public static class DecimalClipper<T>
{
    public static readonly Func<decimal, T> Clip;

    static DecimalClipper()
    {
        ParameterExpression value = Expression.Parameter(typeof(decimal), "value");

        Expression<Func<decimal, T>> lambda;

        if (typeof(T) == typeof(decimal))
        {
            lambda = Expression.Lambda<Func<decimal, T>>(value, value);
        }
        else if (typeof(T) == typeof(float) || typeof(T) == typeof(double))
        {
            lambda = Expression.Lambda<Func<decimal, T>>(Expression.Convert(value, typeof(T)), value);
        }
        else
        {
            T min = (T)typeof(T).GetField("MinValue", BindingFlags.Static | BindingFlags.Public).GetValue(null);
            Expression minT = Expression.Constant(min);
            Expression minDecimal = Expression.Constant(Convert.ToDecimal(min));

            T max = (T)typeof(T).GetField("MaxValue", BindingFlags.Static | BindingFlags.Public).GetValue(null);
            Expression maxT = Expression.Constant(max);
            Expression maxDecimal = Expression.Constant(Convert.ToDecimal(max));

            UnaryExpression cast = Expression.Convert(value, typeof(T));
            ConditionalExpression greaterThanOrEqual = Expression.Condition(Expression.GreaterThanOrEqual(value, maxDecimal), maxT, cast);
            ConditionalExpression lesserThanOrEqual = Expression.Condition(Expression.LessThanOrEqual(value, minDecimal), minT, greaterThanOrEqual);

            lambda = Expression.Lambda<Func<decimal, T>>(lesserThanOrEqual, value);
        }

        Clip = lambda.Compile();
    }
}

public static class DecimalEx
{
    public static T Clip<T>(this decimal value)
    {
        return DecimalClipper<T>.Clip(value);
    }
}

I'm even including an extension method...

Examples of use:

int i1 = decimal.MaxValue.Clip<int>();
int i2 = decimal.MinValue.Clip<int>();
int i3 = 5.5M.Clip<int>();
int i4 = -5.5M.Clip<int>();
byte i5 =(-5.5M).Clip<byte>();
//char i6 = decimal.MaxValue.Clip<char>();
float i7 = decimal.MaxValue.Clip<float>();
double i8 = decimal.MaxValue.Clip<double>();
decimal i9 = decimal.MaxValue.Clip<decimal>();

Ah... the expression tree is generated only once for each type T used and then cached thanks to the working of generic types and static members.

The char, IntPtr, UIntPtr aren't supported at this time.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • is this thread safe? if not, just for the heck of it make it threadsafe :) – Fredou May 11 '15 at 13:16
  • @Fredou The only non-thread safe moment could be the initialization (the static constructor), but that is guaranteed to be thread safe by the clr. So it is as much thread safe as you can have something. Mmmh someone could have a `struct MyNum { public static MyNum MinValue = 0; public static MyNum MaxValue = 0; }` and then write continuously to `MinValue` in another thread... that wouldn't be thread safe :-) – xanatos May 11 '15 at 13:20
3

that would be my solution;

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            decimal value = 123456.789M;

            Console.WriteLine(ConvertOrMinMax<Byte>(value));
            Console.WriteLine(ConvertOrMinMax<Int16>(value));
            Console.WriteLine(ConvertOrMinMax<Int32>(value));
            Console.WriteLine(ConvertOrMinMax<Int64>(value));
            Console.WriteLine(ConvertOrMinMax<Decimal>(value));
            Console.WriteLine(ConvertOrMinMax<Double>(value));
            Console.WriteLine(ConvertOrMinMax<float>(value));

            Console.Read();
        }

        static T ConvertOrMinMax<T>(decimal v)
        {
            var myType = typeof(T);

            if(myType == typeof(double) || myType == typeof(float))
                return (T)Convert.ChangeType(v, myType);

            decimal min = (decimal)Convert.ChangeType(myType.GetField("MinValue").GetValue(null), typeof(decimal));

            if (v < min)
                return (T)Convert.ChangeType(min, myType);

            decimal max = (decimal)Convert.ChangeType(myType.GetField("MaxValue").GetValue(null), typeof(decimal)); ;

            if (v > max)
                return (T)Convert.ChangeType(max, myType);

            return (T)Convert.ChangeType(v, myType);
        }
    }
}
Fredou
  • 19,848
  • 10
  • 58
  • 113