【问题标题】:How to check whether a (generic) number type is an integral or nonintegral type in C#?如何在 C# 中检查(通用)数字类型是整数类型还是非整数类型?
【发布时间】:2011-12-15 13:47:23
【问题描述】:

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

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

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

背景信息

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

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

【问题讨论】:

  • 整数也是实数...
  • @StevenJeuris - 不,C# 中的整数必须是实数,所以你想弄清楚的不是很清楚。
  • @ThomasLevesque:但并非所有实数都是整数。
  • @StevenJeuris - 你想知道一个实数是否是一个整数,对吧?即,以 10 为基数表示时是否缺少非零小数部分。我假设您使用非实数(即因子为 sqrt(-1) 的数字) .
  • @EsotericScreenName:最后一次更新了问题(希望如此)。完全删除了对数学的任何引用。 :) 我正在寻找的是积分与非积分;不知道如何将其转化为数学。

标签: c# generics math


【解决方案1】:

你试过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));
}

【讨论】:

  • 在这里,我正在编写一个通用方法来完成ChangeType 所做的事情。 +1。
  • 不知道该不该接受。 :) 它没有回答问题,但是是的,它是解决让我问这个问题的情况的方法。但是,我敢打赌它非常慢(与有条件地调用Math.Round() 相比),而且可以经常调用它。它还依赖于正在实施的IConvertible,这不是问题。我宁愿检查一次类型是否为整数,并根据缓存结果有条件地调用Math.Round()
  • 老实说,我本以为表现还不错。主要是因为它似乎完全符合您的要求,而且我认为 .NET 人员已经尽可能高效地编写了它。我可以看到在 cmets 中讨论的其他方法似乎不仅仅使用Math.Round()(例如我可以看到关于 Expression.Convert 的讨论),所以我也不确定它们的性能。我建议实际测试我建议的内容,看看它的表现如何。您可能会发现任何替代方案最终都会有很多逻辑来处理边缘情况等。
  • 另外作为说明,我刚刚检查了 Convert.ChangeType 的工作原理,它基本上会遍历 iConvertible 中定义的所有可能类型,并在适当的情况下调用 ToX 方法。我原以为这应该很快。
【解决方案2】:

这是一个方法,它可以确定存储在通用数字类型中的特定值是否是一个没有硬编码的整数。经过测试在 .NET 4 上为我工作。正确处理所有内置数字类型(如底部的 MSDN 链接中定义),但 BigInteger 除外,它不实现 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 有一些示例代码可能会有所帮助。具体来说,它包括一个被认为是数字类型的列表。

【讨论】:

  • 这个解决方案的问题是你必须通过testNumber。我不知道类型,所以我不能传递样本值。但是,我可以将 double 值转换为未知类型,但此时如果它是整数类型,小数部分可能已经丢失。
  • 好点,我只想着一个方向。另外,我的代码检测 value 是否为整数,但您想知道 type 是否为整数。即将编辑。
  • 还想在某个时候使用Activator.CreateInstance。但是对于没有空构造函数的自定义类型,这将失败。 :)查看my answer。还没有实现它,但相信我可能会做一些事情,你可能也会感兴趣。
  • 非常正确。棘手的一点是,如果没有至少一个该类型的实例,似乎没有一种方法可以检查一个类型是否是整数。将研究使用反射来为自定义类型找到合适的构造函数。您的答案中的划分非常巧妙!
  • 哦,完全跳过了0.1d 部分。我不明白你为什么需要Activator :/。 double -> T -> double 可以工作!这只留下了一个问题,对于 float 以外的某些自定义类型是否由于精度问题会以不同的方式四舍五入?为您为此付出的所有努力 +1!
【解决方案3】:

我不是 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));
}

【讨论】:

  • 关于IConvertible,一点也不坏。现在正在考虑。明确检查类型是我想要避免的。
  • ... 但是,这并不能解决示例中提到的我的实际问题。 ;p 我需要检查它是否是非整数的,以便我现在是否必须对值进行四舍五入。
  • 那么如果结果不是整数,你想把它四舍五入为整数,是吗?
  • 不,如果我要转换的类型也是整数类型,我只想将它舍入为整数,而不是例如双倍。
  • 这是否正确:您有一个双精度数,并且您想将其转换为 T,但如果 T 是整数类型,则需要适当舍入?见编辑。
【解决方案4】:

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

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

我正在尝试以下实现:

  1. 321 都转换为所需的未知类型。 (假设可以从 int 转换为数字类型,无论如何这应该始终是可能的。)
  2. 如果是3 / 2 == 1,它是一个整数类型。否则,它是非整数类型。

此解决方案不依赖于知道类型,仅使用转换和计算。

【讨论】:

  • 对于自定义类,您不能假设使用 int 进行隐式转换,并且它没有为 BigInteger 定义。
  • @EsotericScreenName:我的错误。它不必是隐含的。我正在考虑使用Expression.Convert,它也允许显式。至于假设它是可转换的,这是我愿意依赖的要求。数字类型至少可以转换为整数类型是有意义的。
  • @EsotericScreenName 我刚刚意识到,这与您的解决方案有类似的 "problem" 。假设您制作了一个适合 0 到 1 之间非常精确范围的自定义类型。将无法转换为 2 和 3。
  • 这就是为什么我一直在与 0.1 进行比较。假设存在到 double 的转换,我的代码将正确测试您的高精度自定义类型。但是,对于没有转换为 double 的自定义类型,我的会失败。此方法的另一个问题是它需要定义除法运算符。一般来说,我认为不可能与自定义类型进行比较。我无法与BigInteger 进行比较。您不能从原始数字转换为它。您可以使用构造函数,但这并不好,因为它引入了更多限制性要求。
  • @EsotericScreenName:未定义除法运算符的类型不是数字类型。对于比较,您可以使用IComparable,或者只是比较运算符,无论如何它也应该实现。对于正确的数字类型,预计也将支持转换为double但是,我遇到类似问题的意思是,例如精度为 .5 的虚构自定义浮点不适用于您的解决方案。但这一切都是虚构的。 :)
猜你喜欢
  • 1970-01-01
  • 2013-09-15
  • 1970-01-01
  • 2011-05-03
  • 1970-01-01
  • 2022-07-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-07
相关资源
最近更新 更多