如何在C#中检查(通用)数字类型是否为整数或非整数类型?

时间:2011-12-15 13:47:24

标签: c# generics math

我有一个通用类型T。使用Marc's Operator class我可以对其进行计算。

是否可以仅通过计算来检测类型是integral还是nonintegral类型?

也许有更好的解决方案?我更愿意支持任何可能的类型,所以我想防止硬编码哪些类型是整数/非整数。

背景信息

我发现自己的情况是,我想将double转换为T,但舍入到T的最近值double

int a = (int)2.6会产生2,而我希望它在3中生成,而不知道类型(在本例中为int)。它也可以是double,在这种情况下,我希望结果为2.6

4 个答案:

答案 0 :(得分:6)

你试过Convert.ChangeType吗?类似的东西:

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

它适用于我认为的所有数字类型(只要第一个参数是iConvertible,并且类型是受支持的类型,所有基本数字应该是我相信的。)

重要的是要提到这会调用像double.ToInt32这样的方法来舍入值而不是截断(我相信银行家四舍五入)。

我在一个小的LinqPad程序中对它进行了测试,它完成了我认为你想要的东西:

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

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

答案 1 :(得分:3)

这是一种方法,它将确定存储在通用数字类型中的特定值是否为没有硬编码的整数。测试在.NET上为我工作4.正确处理除BigInteger之外的所有内置数字类型(如底部的MSDN链接中所定义),但不实现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;
        }

这是一种确定特定类型是否为整数类型的方法。

    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是通过舍入在两个通用数字类型之间进行转换的方法。但是对于踢腿和好奇心,这里有一种将通用数字类型转换为int的方法,可以扩展为返回泛型类型而不会有太多困难。

    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
            }
        }
    }

我的结果:

        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

此外,this MSDN page有一些示例代码可能会有所帮助。具体来说,它包括一个被认为是数字的类型列表。

答案 2 :(得分:2)

我不是100%肯定你在问什么,但是:

要检查它是否为完整的类型,请使用:if (obj is float || obj is double)if typeof(T) == typeof(float) || typeof(T) == typeof(double))

要检查它是否为整数,请将其转换为双精度数,然后执行if(value == Math.Round(value))

当然,假设您首先有一个数字。我相信您使用的Operator类支持DateTime之类的东西。使通用方法具有通用约束where T : IConvertible会更好吗?这样就会有明确的ToDoubleToInteger方法。

修改

我想我明白了:你有两个局部变量double d; T num;。您希望将d转换为T类型,但如果T是整数类型,则需要进行适当的舍入。这是对的吗?

假设这是正确的,这就是我要做的事情:

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));
}

答案 3 :(得分:2)

Chris's answer为我提到的场景提供了一个可能的解决方案,但出于性能原因,我仍然试图回答实际问题。

假设(未经测试)是,Convert.ChangeTypeMath.Round()慢得多。理想情况下,我可以检查一次给定类型是否为完整,并从此开始有条件地调用Math.Round()以获得比不断调用Convert.ChangeType()更有效的解决方案。

我尝试以下实施:

  1. 321转换为所需的未知类型。 (这假设可以从int转换为数字类型,无论如何都应该是可能的。)
  2. 3 / 2 == 1的情况下,它是一个整数类型。否则,它是非整数类型。
  3. 此解决方案并不依赖于知道类型,只使用转换和计算。