ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)
  rWLfpslpeeJP 2023年11月02日 61 0


前言

(1)今天看到一个有意思的问题,如何判断一个数字是否为2的若干次幂。这个问题并不难,但是对于我们的C语言功底还是有一点点的考验的。
(2)希望各位可以先自行思考,实在想不出来再看后面的讲解。提示,C语言的位运算是一个好东西。

解析

2的若干次幂数所存在的特征点

(1)首先,我们需要知道2的若干次幂所存在的特征点。当我们知道了这个特征点之后,就可以将这个特征点与其他数进行分离了。
(2)我们都知道,计算机是2进制系统。如果让一个数字乘以2,我们是不是可理解为,让这个二进制数右移动一位呢?

ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)_c语言

(3)既然我们知道了,在计算机中乘以2就是进行一次右移操作。那么2的n次幂,是不是就是二进制数1进行右移n次呢?例如,数字2换成二进制是10,而十进制的数字2恰好就是2的1次幂,因而二进制的1进行一次右移操作。
(4)好,我们再进行扩展,既然2的n次幂就是数字1进行右移n次。那么2的n次幂在二进制中是不是有一个特点,那就是只有一个位为1!(不理解的同学可以看一下下面这几个例子,我们发现2的n次幂的数8和4只有一个位为1)

ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)_c语言_02

如何提取这个特征点

(1)现在我们知道了2的若干次幂在二进制中,只会有一个位是1,其他位是数字0。因此,我们是不是得想个办法,判断一个二进制数只存在一个1。
(2)于是,我们可以想到"&"运算。他的特点在于,有0出0。假设我们的二进制数是100,那么让100减去1变成011。这样原来那个存放唯一数字1的地方就会变成0了。
(3)然后100&011,就会变成0。最后逻辑取反即可。

!((x)&(x-1))

ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)_#include_03

(4)但是肯定有朋友会想举其他例子尝试反驳,很不幸的是你找不到的。
(5)为什么这么说呢?因为,我们要抓住这个方法的巧妙之处,我们是将唯一的存放1的位变成0。
(6)但是,我们假设有一个可以反驳的数1010。(注意,这里是假设)1010-1=1001,有没有发现一个问题,这里的最高位的数字1永远无法被消除。因此,进行如上操作之后,最终还是可以分辨出这不是2的n次幂。

上述代码存在bug?

bug1 — 不承认1为2的若干次幂

(1)看到上述代码,有一些东西肯定想到了一个刁钻的角度。数字1经过上述代码,最终输出的也是1啊。所以你这个代码有问题!这个时候我建议还是重新学一下小学数学啊(苦笑)。1就是2的0次幂呀
(2)但是有一些朋友就是不承认1怎么办呢?也很简单,判断这个数是不是1呗。

x == 1 ? 0 : !((x)&(x-1))

bug2 — 数字0所导致的问题

(1)数字1导致的问题解决了,我们角度再刁钻一点,假设这个数字是0呢?真的可以吗?
(2)我们在Linux中执行如下代码。

#include <stdio.h>
#include <stdlib.h>

#define if_2_equation(x)  !((x)&(x-1))

int main(int argc,char** argv)
{
	char i;
	//如果输入参数小于2个,打印本程序使用方法
	if(argc < 2)
	{
		printf("Usage: \r\n");
		printf("%s <string> \r\n",argv[0]);
		return -1;
	}
	i = strtol(argv[1], NULL, 0);
	printf("number = %d \r\n",i);
	if(if_2_equation(i))
	{
		printf("yes\r\n");
	}
	else
	{
		printf("no\r\n");
	}
	return 0;
}

ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)_补码_04

(3)执行发现,数字0也被当成了2的若干次幂,这个从数学的角度上来看,怎么也说不清楚啊。为什么会发生这种情况呢?很简单,0&所有的数,都是0。因此,这里就存在bug。
(4)怎么处理呢?很简单,进行一次判断呗。

#define if_2_equation(x)  x == 0 ? 0 : !((x)&(x-1))

(5)但是有些骚年会想,我1和0这两个数都不打算当成2的若干次幂来判断,这个怎么处理呢?也很简单,进行两次判断呗。

#define if_2_equation(x)  x == 0 ? 0 : (x == 1 ? 0 : !((x)&(x-1)))

bug3 — 负数可能带来的问题

-128结果和推测的不一样

(1)我们现在知道了,进行我们就是要判断是否只有最高位有1,其他位为0。
(2)那么,我就有一个疑惑了。像-128转换为补码就是0x80,那么-128是不是会被上面这个宏给判断成2的若干次幂呢?
(3)于是,带着疑惑,我敲下了如下代码。

#include <stdio.h>
#include <stdlib.h>

#define if_2_equation(x)  x == 0 ? 0 : !((x)&(x-1))

int main(int argc,char** argv)
{
	char i;
	if(argc < 2)
	{
		printf("Usage: \r\n");
		printf("%s <string> \r\n",argv[0]);
		return -1;
	}
	i = strtol(argv[1], NULL, 0);	
	printf("number = %d \r\n",(int)i);
	printf("number = 0x%x \r\n",(int)i & 0xff);
	printf("number = 0x%x \r\n",(int)(i-1) & 0xff);
	printf("!(0x80 & 0x7f)  = 0x%x \r\n",!(0x80 & 0x7f));
	printf("if_2_equation(i)  = 0x%x \r\n",if_2_equation(i));
	if(if_2_equation(i))
	{
		printf("yes\r\n");
	}
	else
	{
		printf("no\r\n");
	}
	return 0;
}

(4)这个结果很有意思,!(0x80 & 0x7f) = 0x1 。但是if_2_equation(i) = 0x0 ,这两个居然不一样?!

ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)_学习_05

(5)这个问题纠结了我许久,后面突然想到,我是不是可以看看汇编指令呢?
(6)查看汇编代码可知,我们这里虽然是按照1字节的char型数据来存储,但是在汇编代码中,他依旧是会按照4字节的方式来进行计算,最终的if_2_equation()返回的数据其实是一个4字节的数据。
(7)于是,最终的结果和预期的不同:
<1>预期结果:-128补码是0x80,-128-1补码是0x7F。最终0x80 & 0x7F= 0x00,逻辑取反之后 !0x00 = 1。
<2>实际结果:-128补码是0xFF80,-128-1补码是0xFF7F。最终0xFF80 & 0xFF7F = 0xFF00,逻辑取反之后 !0xFF00 = 0。

ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)_#include_06

-2147483648结果和推测的不一样

(1)既然这个宏会将数据扩展成为4字节来进行计算。那么我就找到int型数据的存储最小值来进行计算呗。
(2)但是实操之后发现, -2147483648居然输出的也是0。
<1>预期结果:-2147483648补码是0x8000 0000,-2147483648-1补码是0x7FFF FFFF。最终0x8000 0000 & 0x7FFF FFFF= 0x0000 0000,逻辑取反之后 !0x0000 0000= 1。
<2>推测实际结果:-2147483648补码是0xFFFF FFFF 8000 0000,-128-1补码是0xFFFF FFFF 7FFF FFFF。最终0xFFFF FFFF 8000 & 0xFFFF FFFF 7FFF FFFF = 0xFFFF FFFF 0000 0000,逻辑取反之后 !0x 0xFFFF FFFF 0000 0000 = 0。
(3)依旧是直接看汇编代码,我们发现-2147483648虽然可以被4字节存放,但是他居然被扩展称为了8字节数据。

ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)_c语言_07

(1)以上都是空想,那么实测一下。与猜测的实际结果一致。

ARTS 挑战打卡的第9天 --- 如何知道一个数是否为2的若干次幂(Algorithm)_#include_08

#include <stdio.h>
#include <stdlib.h>

#define if_2_equation(x)  x == 0 ? 0 : !((x)&(x-1))

int main(int argc,char** argv)
{
	long int i;
	//如果输入参数小于2个,打印本程序使用方法
	if(argc < 2)
	{
		printf("Usage: \r\n");
		printf("%s <string> \r\n",argv[0]);
		return -1;
	}
	i = strtol(argv[1], NULL, 0);
	printf("sizeof(-2147483647) = 0x%ld \r\n",sizeof(-2147483647));
	printf("sizeof(-2147483648) = 0x%ld \r\n",sizeof(-2147483648));

	printf("number = %ld \r\n",i);


	printf("number = 0x%lx \r\n",i);
	printf("number-1 = 0x%lx \r\n",i-1);

	printf("number = 0x%lx \r\n",i&0xff);
	printf("number-1 = 0x%lx \r\n",(i-1)&0xff);
	

	printf("if_2_equation(-2147483648) = 0x%x \r\n",if_2_equation(-2147483648));
	printf("!(0x80000000&0x7fffffff) = 0x%x \r\n",!(0x80000000&0x7fffffff));

	if(if_2_equation(i))
	{
		printf("yes\r\n");
	}
	else
	{
		printf("no\r\n");
	}
	return 0;
}

结果如何保证代码的百分百可行

(1)虽然说,下面这个宏可以判断一个数是否为2的若干次幂。但是经过上面的分析发现。似乎还是存在不确定性,因为我们不知道编译器是否一定会进行扩展。虽然我查看了几种不同的汇编代码发现-2147483648都会被扩展为8字节,但是为了百分百的可靠性,我们还是做好一个完全准备。

#define if_2_equation(x)  x == 0 ? 0 : !((x)&(x-1))

(2)我们先使用is_Positive_number()这个宏判断这数是否为负数,如果为负数,那么直接返回0。为正数再进行判断。

#define is_Positive_number(A)  A>=0 
#define if_2_equation(x)  is_Positive_number(x) == 0 ? 0 : !((x)&(x-1))


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  TKwJ4YzyA8uz   2024年05月17日   48   0   0 C语言
  6Df3JnWQUC5m   2024年04月24日   59   0   0 C语言
  fHBiUfJyY67V   2024年04月26日   43   0   0 C语言
  V88gxnVgnp1F   2024年05月08日   92   0   0 C语言
  6Df3JnWQUC5m   2024年05月08日   86   0   0 C语言
  o1ZcTI9mJsxK   2024年05月08日   121   0   0 C语言
  H5oyQecqjP4R   2024年04月26日   41   0   0 C语言
  6Df3JnWQUC5m   2024年04月25日   52   0   0 C语言
  nmX9dIiR6BtA   2024年04月28日   49   0   0 C语言
  6Df3JnWQUC5m   2024年05月17日   59   0   0 C语言
  6Df3JnWQUC5m   2024年04月25日   52   0   0 C语言
rWLfpslpeeJP