java long 位操作_java java.lang.Long详解之三 大显神通的位移运算

本篇主要讲述位移运算在Long中所扮演的重要角色,有些出神入化的我根本无法理解,但是我一直秉承着无论对错都要记录思考的过程的宗旨写每一篇文章。这一篇也不例外,读Long这个类确实需要比较广泛的知识面,我也是一边在OSChina和stackoverflow上提问,一边慢慢的钻研,难免会存在偏差。

先来看个简单的。

public static int signum(long i) {

// HD, Section 2-7

return (int) ((i >> 63) | (-i >>> 63));

}

这个函数作用就是返回参数i的符号,如果返回-1则是负数,如果返回0则是0,如果返回1则是正数。算法就是(int) ((i >> 63) | (-i >>> 63)),如果是正数的话i有符号右移63位后为0,-i无符号右移63位之后结果为1,或操作之后结果就是1.如果i为负数,那么有符号右移63位后就变成了1,然后-i无符号右移63位后就只剩下符号位,最后做或(|)操作结果就是-1. 如果参数i为0,那么移位后结果就是0.

System.out.println(Long.signum(100L));

System.out.println(Long.signum(0L));

System.out.println(Long.signum(-100L));

输出结果:

1

0

-1

接着是一个很少用到,但是实现方式不错的两个方法,循环左移和循环右移方法。

public static long rotateLeft(long i, int distance) {

return (i << distance) | (i >>> -distance);

}

public static long rotateRight(long i, int distance) {

return (i >>> distance) | (i << -distance);

}

实现的代码量可以说已经精简到最少了,有一点要注意的是,循环移位时,参数distance可以接受负数,当distance为负数时,这个等式是成立的,rotateLeft(i, distance) = rotateRight(i, -distance)。这个方法中有两点值得借鉴的,第一从整体上讲循环移位的实现方式;第二是distance与-distance的巧妙运用。

就拿循环左移先来说说第二点吧,前置条件,我们首先假设distance大于0,起先我是很不理解i >>> -distance的,后来在stackoverflow上发问,有人给出了解释,在移位的时候,如果distance小于0,会根据被移位数的长度进行转换。就比如说这里我们对long进行移位,那么-distance就会被转换成(64 + distance)(注,这里的distance是小于0的)。这样的话,如果distance大于0时,(i << distance) | (i >>> -distance);就会被转化成(i << distance) | (i >>> 64 + distance);

清楚了第二点,那么第一点也就不难理解了。用一幅图来解释循环左移。

22f49c7e83b92ce1335b5105717b768c.png

在distance大于0的前提下,先左移distance位,然后再右移64-distance,最终用或运算相加,就是循环移位的结果。图中为了省事儿用了8位做了个演示,先左移3位,然后右移(8-3)位,或运算之后就是结果啦。关于-distance在stackoverflow上的提问在这里。

下面是个更给力的方法-reverse(long i),可以说就是高效率的化身。

public static long reverse(long i) {

// HD, Figure 7-1

i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;

i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;

i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;

i = (i << 48) | ((i & 0xffff0000L) << 16) |

((i >>> 16) & 0xffff0000L) | (i >>> 48);

return i;

}

从整体上说,这个reverse方法集移位与二分算法于一身,堪称经典。 第一步以单位为单位,奇偶位交换 第二步以两位为单位,完成前后两位的交换。 第三步以四位为单位,完成前后四位的交换。 第四步以八位为单位,完成前后八位的交换。 最后一步没有按常理继续二分,而是通过一个转换一步就完成了以16和32位为单位的交换。进而结束了整个64位的反转。

现在一步一步剖析都是如何实现的。

i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;

16进制的5为0101,或操作前半部分首先取出i的所有奇数位,然后整体左移一位,这样实现i的奇数位左移一位变成偶数位;或操作后半部分先右移,即将偶数位右移变成奇数位,然后再取出奇数位。这样就完成了64位中奇数位与偶数位的交换。

i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;

这句同样是实现交换,只不过3对应的16进制为0011,即本次交换以2个字节为单位,交换完成了4个字节的反转。 Liquid error: Flag value is invalid: -O ”” 直到这行代码,实现了以字节为单位的反转,最后仅仅使用一行代码就实现了一两个字节和四个字节为单位的反转。 为了方便画图,现在对操作进行编号,另外从以字节为单位交换开始,之前的细节忽略。

(i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;

(i << 48)

(i & 0xffff0000L) << 16)

(i >>> 16) & 0xffff0000L)

(i >>> 48);

这幅图描述每个编号代码执行之后64位的变化。

498f2de78ee67ee17e753d8ed1a792e8.png

不多做解释,由于这个reverse的最后一行不是按常理”出牌”,所以我使用纯粹的二分法来实现reverse。

public static long reverse(long i) {

// HD, Figure 7-1

i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;

i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;

i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;

i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;

i = (i & 0x0000ffff0000ffffL) << 16 | (i >>> 16) & 0x0000ffff0000ffffL;

i = (i & 0x00000000ffffffffL) << 32 | (i >>> 32) & 0x00000000ffffffffL;

return i;

}

至于为什么要采用那种方式,而不是用”纯粹”的二分法,在stackoverflow上有人提到,可能是因为前一种实现方式需要9个操作,而后一种实现方式需要10个操作。具体是出于怎样的目的可能只有作者才知道。关于reverse我在stackoverflow上的提问在这里。

最后看一个方法。

public static int bitCount(long i) {

// HD, Figure 5-14

i = i - ((i >>> 1) & 0x5555555555555555L);

i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L);

i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL;

i = i + (i >>> 8);

i = i + (i >>> 16);

i = i + (i >>> 32);

return (int)i & 0x7f;

}

这个方法是返回一个long类型的数字对应的二进制中1的个数,其实google上有很多种,这里采用的叫平行算法实现的,算法如下图。

8c7947275fdf36a0cc5afc291ade61be.png

但是这个方法的第一行又采取了一个特别的方式实现i中奇数位+偶数位,我有点儿没想明白。总体上就是像图中所示那样,相邻的两位相加的结果再相邻的四位相加,最后得到二进制中1的个数。

还有一点值得提一下,就是最后一行与上7f,因为long类型,1的个数最多也不会超过64个,所以只取最后7位即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《运算放大器参数解析与ltspice应用仿真》这本书是一本关于运算放大器的参数解析与仿真应用的指南。其中介绍了许多关于运算放大器参数的知识,例如输入阻抗、输出阻抗、增益带宽积、共模抑制比等等。这些参数信息对于运算放大器的使用和设计非常重要,因为它们可以影响运算放大器的性能和稳定性。在书中,还介绍了如何使用SPICE软件(如LTspice)对运算放大器进行仿真和分析。LTspice是一种强大的电路仿真工具,可以帮助用户更好地理解电路的行为和性能。通过对运算放大器进行LTspice仿真,用户可以深入了解其特性和应用,同时还能快速验证设计的正确性。总之,《运算放大器参数解析与ltspice应用仿真》这本书是一本非常有价值的参考书籍,对于从事电路设计的人来说尤为重要。 ### 回答2: 《运算放大器参数解析与ltspice应用仿真 pdf》是一本介绍运算放大器的参数和使用ltspice进行仿真的书籍。运算放大器是电子工程中常用的模拟电路元件,其具有高增益、高输入阻抗、低输出阻抗等特点。因此,在电路设计和分析中,运算放大器经常被用来放大信号、求和、求差、积分、微分等。 本书详细介绍了运算放大器的管脚功能、特性曲线、直流参数、动态参数、输入偏置电流、输入偏置电压、共模抑制比、差模抑制比、带宽等参数,并告诉读者如何选择适合自己的运放参数。此外,本书还介绍了如何使用ltspice进行运放信号放大、积分电路、微分电路、滤波器、非线性元件等仿真分析,大显神通。 总之,《运算放大器参数解析与ltspice应用仿真 pdf》是一本对于电子工程师和学生都有很大帮助的参考书,对于了解和应用运算放大器及其仿真分析有着很深入的介绍,是学习和掌握运算放大器及其应用的很好的首选读物。 ### 回答3: 运算放大器参数解析与ltspice应用仿真pdf是一份关于运算放大器参数的分析报告和在LTSpice电路仿真软件中使用运算放大器的教程。 运算放大器通常被广泛应用于电子电路中,因此本文首先对运算放大器的基本原理进行了简要介绍,包括其构成、工作原理和基本特性。接着,本文详细讲解了运算放大器的各项参数,如放大倍数、带宽、输入偏置电压和输出电阻等,这些参数对于设计和应用运算放大器的电路非常重要。 除了参数分析,本文还介绍了如何使用LTSpice软件进行运算放大器电路的仿真。LTSpice是一款常用的电路仿真软件,具有强大的仿真能力和易于使用的界面。本文通过一个简单的实例演示了如何使用LTSpice搭建运算放大器电路,并展示了仿真结果。 总的来说,运算放大器参数解析与ltspice应用仿真pdf提供了一个全面的关于运算放大器参数分析和LTSpice仿真的教程,非常适用于对运算放大器感兴趣的学习者和从业者。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值