测试浮点数是否为整数

20

这段代码是可行的(C# 3)

double d;
if(d == (double)(int)d) ...;
  1. 有更好的做法吗?
  2. 出于其他原因,我想避免双重转换,那么除此之外还有什么好方法吗?(即使它们不那么好)

注意: 尽管下面的操作可能不是最佳方案,但由于浮点数等于符号的误差问题,在这种情况下,期望值在0到几百之间,而且它们应该是整数(非整数是错误的)。

12个回答

36
d == Math.Floor(d)

换句话说,它做的是同样的事情。

顺便提醒一下:希望你意识到在进行这种操作时必须非常小心;浮点数/双精度数很容易积累微小误差,导致精确比较(例如此处)无法明显失败。


1
不完全是同样的事情。我认为这更好。对于可以存储在double中但无法存储在int中的大整数,强制转换为int将不起作用。 - Maciej Hehl
正如您所提到的,进行任何类型的浮点数相等比较时都应该非常小心——在大多数情况下,使用一个小的epsilon因子是更好的策略。 - Cade Roux
我喜欢这个答案!但是在我的情况下,我也需要检测溢出(或任何舍入误差),尽管我会将问题留在更一般的形式。 - BCS
@Cade Roux:如果你正在考虑舍入操作,精确的FP比较是有用的。 - BCS
@BCS,没问题 - 只需要记住 == 表示“真正相等” - 我在 VB 中遇到过日期问题,因为浮点表示法的差异 - 这是 VB 甚至无法显示的。 - Cade Roux
2
@Mike F:另一种策略是使用d == Math.Truncate(d),因为这种方法使用了更简单的舍入模式。 - user7116

8
我想这样做是可行的:

我认为这样做是可行的:

if (d % 1 == 0) {
  //...
}

我对负数的 int mod 有点疲劳,我不确定我是否想为 FP 解决这个问题。 - BCS
@BCS,请问一下,“负数取模”有什么问题吗? - user1234567
当一个或两个操作数为负数时,整数模块至少有两种不同的解释。由于人们不记得当前语言使用哪种方式,因此存在很多错误的代码。我不知道在FP中发生了什么。并且我怀疑答案有太多不同的“取决于”以便于安全使用。例如,如果没有详细阅读标准,我不能确定在某些情况下(或语言的某个版本中),FP是否会在模块之前被转换为int。 - BCS

5
我不能回答关于C#特定问题的部分,但我必须指出您可能会错过浮点数的通用问题。
通常,浮点数上的整数并不是很明确。与相等性不明确的原因相同,浮点运算通常包括舍入和表示误差。
例如:1.1 + 0.6 != 1.7
是的,这就是浮点数的工作方式。
在这里,1.1 + 0.6 - 1.7 == 2.2204460492503131e-16
严格来说,你可以使用渐进精度比较来比较浮点数。
如果这还不够,你必须使用带有内置误差范围的浮点数表示、十进制数字表示或符号计算来处理。

一个很好的观点并且描述得很清楚,然而在我的情况下,期望的行为已经被原始代码定义得很清楚了。任何模仿它的都是有效的。 - BCS
就浮点数而言,即使是0.1也不等于0.1。尾数必然截断二进制值0.0001100110011001100110011001100... 尝试使用0.1F == 0.1。 - ccook

5
如果您的double是另一个计算的结果,您可能需要这样做:
d == Math.Floor(d + 0.00001);

这样,即使存在轻微的四舍五入误差,它仍将匹配。


1
@Khoth 请问您能否提供一个案例,其中 (d==Math.Floor(d)) != (d==Math.Floor(d+0.00001))? - aka.nice
你应该使用 double.Epsilon... 或者如果你正在考虑 x 次操作,可以使用 x*double.Epsilon。 - ccook

3

一个简单的测试,如 'x == floor(x)',在任何固定精度的浮点数中都可以数学保证正确运行。

所有合法的固定精度浮点编码表示不同的实数,因此对于每个整数x,最多只有一个固定精度浮点编码与之完全匹配。

因此,对于每个可以用这种方式表示的整数x,我们必须有 x == floor(x),因为按照定义,floor(x)返回最大的FP数y,使得y <= x且y表示一个整数;所以floor(x)必须返回x。


3

如果你只是要进行转换,Mike F / Khoth的回答很好,但并没有完全回答你的问题。如果你要实际测试,并且这非常重要,我建议你实现一个包括误差范围的东西。

例如,如果你考虑到钱的因素,想要测试是否为整数美元金额,你可以按照Khoth的模式说:

if( Math.abs(d - Math.Floor(d + 0.001)) < 0.001)

换句话说,取值与其整数表示之间的差的绝对值,并确保它很小。

或者 is_almost_integer(d, eps) 可以简单地返回 Math.Floor(d-eps)!=Math.Floor(d+eps)。 - aka.nice

2

您不需要在这里加上额外的(双倍)内容。这样就可以正常工作:

if (d == (int)d) {
 //...
}

可以了!顺便问一下,你确定它会完全一样吗? - BCS
嗯,这个怎么处理? d= 3e30; if (d == (int)d) { - tzot
是的,它不能完全按照我的要求执行,但它会做我需要的事情。 - BCS

2

使用 Math.Truncate()


1

这将让您选择所需的精度,加上或减去半个刻度,以解决浮点漂移问题。比较也是整数的,这很好。

static void Main(string[] args)
{
    const int precision = 10000;

    foreach (var d in new[] { 2, 2.9, 2.001, 1.999, 1.99999999, 2.00000001 })
    {
        if ((int) (d*precision + .5)%precision == 0)
        {
            Console.WriteLine("{0} is an int", d);
        }
    }
}

输出结果为

2 is an int
1.99999999 is an int
2.00000001 is an int

0

为了处理双精度的精度问题...

Math.Abs(d - Math.Floor(d)) <= double.Epsilon

考虑以下情况,一个小于双倍 Epsilon 的值无法与零进行比较。
// number of possible rounds
const int rounds = 1;

// precision causes rounding up to double.Epsilon
double d = double.Epsilon*.75;

// due to the rounding this comparison fails
Console.WriteLine(d == Math.Floor(d));

// this comparison succeeds by accounting for the rounding
Console.WriteLine(Math.Abs(d - Math.Floor(d)) <= rounds*double.Epsilon);

// The difference is double.Epsilon, 4.940656458412465E-324
Console.WriteLine(Math.Abs(d - Math.Floor(d)).ToString("E15"));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接