什么是移位,这得从1972年谈起!

1972年的第一场雪,比以往……不好意思,走错片场了……

为什么要从1972年说起,没别的,只是因为1972年是闰年,其实从2016年谈起也行,但是不能从2019年谈起,因为2019年不是闰年~~~

那么问题来了:

我问你怎么判断某年是不是闰年?

你肯定会说满足下列两个条件之一即可:

1)能被400整除

2)能被4整除但不能被100整除

那么代码怎么实现呢?

それは簡単です!!

    public static boolean isLeap(int year) {
        return 0 == year % 400                          //条件1
                || (0 == year % 4 && 0 != year % 100);  //条件2
    }

好!实现了~呃……好像大家都知道啊……但是我记得有一次我没有写过这个方法,我去调用isLeap(int)居然成功了,其原因在于我

import static java.time.Year.isLeap;

JDK当中已经有了这个方法,在java.time.Year里面,当时我点开之前心中想的,肯定和上面写的一模一样

其实不然……

    /**
     * Checks if the year is a leap year, according to the ISO proleptic
     * calendar system rules.
     * <p>
     * This method applies the current rules for leap years across the whole time-line.
     * In general, a year is a leap year if it is divisible by four without
     * remainder. However, years divisible by 100, are not leap years, with
     * the exception of years divisible by 400 which are.
     * <p>
     * For example, 1904 is a leap year it is divisible by 4.
     * 1900 was not a leap year as it is divisible by 100, however 2000 was a
     * leap year as it is divisible by 400.
     * <p>
     * The calculation is proleptic - applying the same rules into the far future and far           past.
     * This is historically inaccurate, but is correct for the ISO-8601 standard.
     *
     * @param year  the year to check
     * @return true if the year is leap, false otherwise
     */
    public static boolean isLeap(long year) {
        return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
    }

这是个什么东西??怎么没看懂!!!为什么是 year & 3 == 0 ??

要满足 year & 3 == 0,就得满足 year 的二进制最后两位都是0,因为3的二进制是11,为了研究一下这个东西,我们先把1 - 100数字中满足 year & 3 == 0 的数字找出来

    for (int i = 1; i != 101; ++i) {
        if (0 == (i & 3)) {
            System.out.println(i + "\t" + Integer.toBinaryString(i));
        }
    }

运行结果如下:

4    100
8    1000
12    1100
16    10000
20    10100
24    11000
28    11100
32    100000
36    100100
40    101000
44    101100
48    110000
52    110100
56    111000
60    111100
64    1000000
68    1000100
72    1001000
76    1001100
80    1010000
84    1010100
88    1011000
92    1011100
96    1100000
100    1100100

不难看出,所有数字都能被4整除!数学上的证明过程,我没法完成,但是可以想象一下,二进制为100的数字是4,是可以被4整除的,如果要通过加减数字来改变二进制100前面的1,那么需要一个跨度,这个跨度刚好是4,因为3的二进制是11,100+11=111,前面的1还是没能改变到,所以得加4,100+100=1000,这样就可以~每次的跨度都是4,所以 满足y & 3 == 0的数是一定可以被4整除的。

至于后面的 (year % 100) != 0 || (year % 400) == 0 就好理解了:

1)要么不能被100整除,这种情况下已经满足上面的条件2,是闰年

2)要么能被400整除,这种情况下满足上面的条件1,是闰年

但是为什么好好的 year % 4 == 0不写,非要 year & 3 == 0呢?

原因是从效率上看,使用移位指令有更高的效率,因为移位指令占2个机器周期,而乘除法指令占4个机器周期。从硬件上看,移位对硬件更容易实现。

其实主要就是效率,既然一个闰年的算法都要考虑,那么JDK中的其它方法呢?

无独有偶,java.lang.Long.toString(long)方法中会调用getChars(int,int,char[])方法,里面的实现有一段是这样的

    while (i > Integer.MAX_VALUE) {
        q = i / 100;
        // really: r = i - (q * 100);
        r = (int)(i - ((q << 6) + (q << 5) + (q << 2)));
        i = q;
        buf[--charPos] = Integer.DigitOnes[r];
        buf[--charPos] = Integer.DigitTens[r];
    }
r = (int)(i - ((q << 6) + (q << 5) + (q << 2))) 上面有一行注释“really: r = i - (q * 100)”

似乎是在说实际上进行的操作是 r = i - (q * 100),假设一个数x,x * 100 真的等于  (x << 6) + (x << 5) + (q << 2)吗?

我并不怀疑jdk中的方法与注释,要知道如果有问题早就被别的大神发现了,哪会等到我这个菜鸡……等等……好像有点规律!!! 

2 ^ 6 + 2 ^ 5 + 2 ^ 2 = 64 + 32 +4 = 96 + 4 = 100!!!

有点母牛的生殖器。

如果是乘以其它数呢?是不是一样可以通过这个位移运算实现??答案是可以的,我已经试过了,送出找到位移的函数:

inline void calc(const int value)
{
	if (0 == value)return;
	bool flag = true;
	cout << value << " = ";
	for (int i = 0; i != 31; ++i)
	{
		if (1 & (value >> i))
		{
			if (flag)
			{
				flag = false;
			}
			else
			{
				cout << " + ";
			}
			cout << "2 ^ " << i;
		}
	}
	cout << endl;
}

void start()
{
	string str;
	for (;;)
	{
		cout << "输入(按Q/q退出):" << endl;
		cin.clear();
		cin >> str;
		if ("q" == str || "Q" == str)break;
		try
		{
			int value = stoi(str);
			calc(value);
		}
		catch (const std::exception& e)
		{
			cerr << e.what() << endl;
		}
	}
}

运行结果大概长这个样子:

意思是 :

1)x * 556325 等同于 x + (x << 2)+ (x <<5)+ (x << 8)+ (x << 10) + (x << 11) + (x << 12) + (x << 13) + (x << 14) + (x << 19) 

2)x * 198394 等同于 (x << 1) + (x << 3) + (x << 4) + (x << 5) + (x << 6) + (x << 7) + (x << 9) + (x << 10) + (x << 16) + (x << 17)

验证:

#include <iostream>
//x * 198394 等同于(x << 1) + (x << 3) + (x << 4) + (x << 5) + (x << 6) + (x << 7) + (x << 9) + (x << 10) + (x << 16) + (x << 17)
inline int mul198394(int x)
{
	return (x << 1) + (x << 3) + (x << 4) + (x << 5) + (x << 6) + (x << 7) + (x << 9) + (x << 10) + (x << 16) + (x << 17);
}
//x * 556325 等同于 x + (x << 2)+ (x <<5)+ (x << 8)+ (x << 10) + (x << 11) + (x << 12) + (x << 13) + (x << 14) + (x << 19) 
inline int mul556325(int x)
{
	return x + (x << 2) + (x << 5) + (x << 8) + (x << 10) + (x << 11) + (x << 12) + (x << 13) + (x << 14) + (x << 19);
}
int main()
{
	using namespace std;
	int input;
	cin >> input;
	cout << 198394 * input << endl;
	cout << mul198394(input) << endl;
	cout << endl;
	cout << 556325 * input << endl;
	cout << mul556325(input) << endl;
	return 0;
}

运行结果如下:

可以看到值是完全相等的,我不知道计算机执行乘法计算是怎么计算的,但是就这个而言,我测试的时候,各计算1,000,000次,直接乘法要5毫秒,移位只要2毫秒,的确是可以提升效率的。

类似的其实还有很多,比如IPv4地址存储,一个int 32位,分成4个8位,2^8-1 = 255,刚好可以~

再比如,某个事情有多个不同的状态,存入多个bool固然可以,但是一来字段变多,二来如果后面增加新的状态,会修改数据库,非常大的工程量,应当直接存入一个int,转成二进制,1对应ture,0对应false……

再比如ARGB颜色的组成,就是Alpha,Red,Green,Blue……

再比如……等等

写这个博客未曾组织过语言,想到什么写什么了,大家当故事看吧,哈哈!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值