简体   繁体   中英

How to check whether a (generic) number type is an integral or nonintegral type in C#?

I've got a generic type T . Using Marc's Operator class I can perform calculations on it.

Is it possible to detect by mere calculations whether the type is an integral or a nonintegral type?

Perhaps there is a better solution? I'd prefer to support any possible type, so I'd like to prevent hard-coding which types are integral/nonintegral.

Background info

The situation I find myself in is I want to cast a double to T but round to the nearest value of T to the double value.

int a = (int)2.6 results in 2 while I want it to result it in 3 , without knowing the type (in this case int ). It could also be double , in which case I want the outcome to be 2.6 .

Have you tried Convert.ChangeType ? Something like:

Convert.ChangeType(1.9d, typeof (T))

It will work for all numeric types I think (as long as the first parameter is iConvertible and the type is a supported one which all basic numerics should be I believe).

Its important to mention that this will call something like double.ToInt32 which rounds values rather than truncates (bankers rounding I believe).

I tested this in a little LinqPad program and it does what I think you want:

void Main()
{
    var foo = RetNum<decimal>();
    foo.Dump();
}

public static T RetNum<T>()
{
    return (T)Convert.ChangeType(1.9d, typeof (T));
}

Here's a method which will determine if a particular value stored in a generic numeric type is an integer without hardcoding. Tested working for me on .NET 4. Correctly handles all built in numeric types (as defined in the MSDN link at the bottom) except BigInteger , which doesn't implement IConvertible .

        public static bool? IsInteger<T>(T testNumber) where T : IConvertible
        {
            // returns null if T is non-numeric
            bool? isInt = null;
            try
            {
                isInt = testNumber.ToUInt64(CultureInfo.InvariantCulture) == testNumber.ToDouble(CultureInfo.InvariantCulture);
            }
            catch (OverflowException)
            {
                // casting a negative int will cause this exception
                try
                {
                    isInt = testNumber.ToInt64(CultureInfo.InvariantCulture) == testNumber.ToDouble(CultureInfo.InvariantCulture);
                }
                catch
                {
                    // throw depending on desired behavior
                }
            }
            catch
            {
                // throw depending on desired behavior
            }
            return isInt;
        }

Here's a method which will determine whether a particular type is an integral type.

    public static bool? IsIntegerType<T>() where T : IConvertible
    {
        bool? isInt = null;
        try
        {
            isInt = Math.Round((double)Convert.ChangeType((T)Convert.ChangeType(0.1d, typeof(T)),typeof(double)), 1) != .1d;
            // if you don't round it and T is float you'll get the wrong result
        }
        catch
        {   
            // T is a non numeric type, or something went wrong with the activator
        }
        return isInt;
    }

Convert.ChangeType is the way to convert, with rounding, between two generic numeric types. But for kicks and curiosity, here's a way to convert a generic numeric type to an int , which could be extended to return a generic type without too much difficulty.

    public static int GetInt32<T>(T target) where T : IConvertible
    {
        bool? isInt = IsInteger<T>(target);
        if (isInt == null) throw new ArgumentException(); // put an appropriate message in
        else if (isInt == true)
        {
            try
            {
                int i = target.ToInt32(CultureInfo.InvariantCulture);
                return i;
            }
            catch
            {   // exceeded size of int32
                throw new OverflowException(); // put an appropriate message in
            }
        }
        else
        {
            try
            {
                double d = target.ToDouble(CultureInfo.InvariantCulture);
                return (int)Math.Round(d);
            }
            catch
            {   // exceeded size of int32
                throw new OverflowException(); // put an appropriate message in
            }
        }
    }

My results:

        double d = 1.9;
        byte b = 1;
        sbyte sb = 1;
        float f = 2.0f;
        short s = 1;
        int i = -3;
        UInt16 ui = 44;
        ulong ul = ulong.MaxValue;
        bool? dd = IsInteger<double>(d); // false
        bool? dt = IsInteger<DateTime>(DateTime.Now); // null
        bool? db = IsInteger<byte>(b); // true
        bool? dsb = IsInteger<sbyte>(sb); // true
        bool? df = IsInteger<float>(f); // true
        bool? ds = IsInteger<short>(s); // true
        bool? di = IsInteger<int>(i); // true
        bool? dui = IsInteger<UInt16>(ui); // true
        bool? dul = IsInteger<ulong>(ul); // true
        int converted = GetInt32<double>(d); // coverted==2
        bool? isd = IsIntegerType<double>(); // false
        bool? isi = IsIntegerType<int>(); // true

Additionally, this MSDN page has some example code which might be helpful. Specifically, it includes a list of types considered to be numeric.

I'm not 100% sure what you're asking, but:

To check if it's an integral type , use this: if (obj is float || obj is double) , or if typeof(T) == typeof(float) || typeof(T) == typeof(double)) if typeof(T) == typeof(float) || typeof(T) == typeof(double))

To check if it's an integral value , cast it to a double, and then do if(value == Math.Round(value))

Of course, that is assuming that you have a number in the first place. I believe that the Operator class you're using supports things like DateTime. Would it be better to make your generic method have a generic constraint where T : IConvertible ? That way there'd be explicit ToDouble and ToInteger methods.

Edit :

I think I understand: you've got two local variables, double d; T num; double d; T num; . You want to cast d to type T , but with proper rounding if T is a integral type. Is that correct?

Assuming that's correct, here's what I'd do:

public void SomeMethod<T>()
{
    double d;
    // I think I got all the floating-point types. There's only a few, so we can test for them explicitly.
    if(typeof(T) != typeof(double) && typeof(T) != typeof(float) && typeof(T) != typeof(Decimal))
    {
        d = Math.Round(d);
    }
    T converted = Convert.ChangeType(d, typeof(T));
}

Chris's answer gives a possible solution to the scenario I mentioned, but for performance reasons I am still attempting to answer the actual question.

The assumption (untested) is, Convert.ChangeType is much slower than Math.Round() . Ideally, I can check one time whether the given type is integral or not, and conditionally call Math.Round() from then on to obtain a much more efficient solution than calling Convert.ChangeType() constantly.

I'm attempting the following implementation:

  1. Convert both 3 , 2 and 1 to the desired unknown type. (This assumes a conversion from an int to the numeric type is possible, which should always be possible anyhow.)
  2. In case 3 / 2 == 1 , it is an integral type. Otherwise, it is a nonintegral type.

This solution doesn't rely anywhere on knowing the type and solely uses conversions and calculations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM