是非之地

博客园首席处男所在的小窝

 

2011年3月3日

一名曾经的测试人员, 看大家贡献的关于2的n次方的检测的代码

合格

刚刚首页多了一篇文章, 关于判断一个数字是否是2的n次方

大家贡献了一大堆各种想法。

 

整理了下, 有以下这么多。

me以前是做测试的, 现在乱七八糟什么活都做,一直没好好学算法, 没法写出很好的算法来娱乐大家。

me算法很弱的。

但是me挑刺的习惯倒是没有改正。  所以很high的开始分析别人的代码。

让我们分析一下大家回复的几种典型代码(运行正确的咱们就不分析了):

 

1 宏定义。 

 

#define ISPOW2(x) (x) & (x-1) ? false : true
这个我刚写了一篇文章来证明他存在一点小的瑕疵。 那就是当x为0的时候,

 

我们知道0不是2的n次方, 简单的说就是LOG(2,0) 是没有值的。

而对于这个判断, 0和-1进行按位与, 必然是0. 居然可以让判断通过。

一个反例否定一种算法, 这不算过分。

当然, 修饰一下, 这个算法还是挺不错的。 至少我自己看的时候觉得挺震惊。 毕竟是第一次见到。

=。=

不要嘲笑我见识少。。。我真的是第一次。

2   7楼的修正版

 

    public static bool floor_7(int num)
    {
        
if (num <= 1)
        {
            
return false;
        }
        
else
        {
            
return ((num & (num - 1)) == 0? true : false;
        }
    }

 

 7楼似乎自己就意识到了这个问题, 就进行了判断。

遗憾的是, 他多判断了一个1. 我们知道, 1是2的0次方。 1应该是符合要求的。

 

3  8楼的修正版

 

    public bool floor_8(int n)
    {
        
if (n < 0)
            
throw new InvalidOperationException();
        
if (n < 2)
            
return false;

        
return n & (n - 1== 0;
    }

 8楼还意识到了负数的问题。 可惜他和7楼存在相同的问题。不过这只是小错误。

 4 22楼的对数算法

    public bool floor_22(int x)
    {
        
float ret = log(x) / log(2);
        
return abs((int)ret - ret) <= 0.00001;
    }

 

22楼的对数算法比较有趣, 可惜, 浮点误差毕竟不是个容易避开的问题。

因为浮点数不能直接比较, 所以用了一个0.00001来做尺度。

这就存在了一个问题:当x很大的时候呢?

我找了一个变态的数字来测试:

 

0x10000001

 

结果是true。因为结果的小数部分实在是太小了。

 =。=

(我是不是有点心理变态啊。。 )

又是一个反例推翻一个算法的思路。

5 37楼的算法。

    public static bool floor_37(int num)
    {
        
double result = Math.Log(num, 2);
        
if (result.ToString().IndexOf(".">= 0)
        {
            
return false;
        }
        
else
        {
            
return true;
        }
    }

 

 相同的问题。 只要使用了LOG, 就无法避免掉浮点数丢精度的问题。 这是没办法的事情。

 

 所以总结了下, (x)&(x-1)的算法还没有被证明, 不知道除了0还有没有别的反例。

因为毕竟这个算式没有严密的证明过程, 参见我的另一篇文章:

 

http://www.cnblogs.com/ArthasCui/archive/2011/03/03/1969935.html

 

因此我觉得, 最保险的还是位运算, 看多少个1, 来的最实在。

当然这里存在一个负数的问题。第一位是1, 剩下全是0的问题。 不过有一位聪明的回复者提供了一个很强大的方法来避开负数的用例:

他给参数定的类型是uint!

好吧你赢了。

 

刚看到一个最牛的:

 

public bool is_(int num)
{
    
switch (num)
    {
        
case 0x0002:
        
case 0x0004:
        
case 0x0008:
        
case 0x0010:
        
case 0x0020:
        
case 0x0040:
        
case 0x0080:
        
case 0x0100:
        
case 0x0200:
        
case 0x0400:
        
case 0x0800:
        
case 0x1000:
        
case 0x2000:
        
case 0x4000:
        
case 0x8000:
            
return true;
        
default:
            
return false;
    }
}

 

 如果再加个0x0001, 我觉得就完美了。。。

 当然还要继续写下去, 确实这个满累人的。

 

 

posted @ 2011-03-03 15:34 undefined 阅读(2649) 评论(39) 编辑

结果正确不代表思路正确

today有人发了个文章, 说笔试题, 问, 判断一个数是不是2的n次方。
他写了个不断对2取余数之类的循环来做的。
问有没有什么快的办法?

me崩溃了。 回了句:
做2的n次方的判断显然是要用位运算啊,这相当于问, 怎样判断一个数只存在一个1, 剩下的全是0.
然后写了个循环移位看有多少个1的做法。

然后回复完一看, 得, 有人写了个更NB的:
return (x & (x-1))==0;

这言简意赅的, 让我崩溃了。me写的代码也没好意思贴。
但是分析下人家什么意思:
说, 一个数如果是1, 后面一堆0, 那么减一之后, 就是0后面一堆1.
so, 按位与, 得到0.

me当时火大了。 靠,你这扯淡呀。

我们不说这计算对不对(既然号称经典算法, 肯定是有效的),

单说这个思路, 就完全是错误的。
因为:
他证明了“2的n次方一定符合这个条件”, 却并没有证明“符合这个条件的一定是2的n次方”呀!

更没有证明“不符合条件的一定是2的n次方”呀。

 

这是一个很显然的逻辑思维方式的错误。

 

我们要的是“一个可以判断是不是2的n次方的条件”呀

当然, 也许最后可以找到证明方法来证明这个写法是对的。 但是那并不代表他就对了。

最后的小插曲就是, 我没有找到证明他对的办法, 也没有找到证明他错的办法。
于是我就用我们两个的算法分别从0跑到1000试试。
幸运的是, 0直接挂了。

因为呢, 0本身是全0, 和任何人做与运算, 都会得到0的。
而实际上0不是2的n次方。因为LOG(2,0)是没有值的。 我们知道一个简单的对数函数在0处是没有值的。它趋向于负无穷。

posted @ 2011-03-03 14:52 undefined 阅读(224) 评论(4) 编辑

导航

统计

公告