黑马程序员技术交流社区

标题: 【送分】第五题 关于位移 答案以公布 [打印本页]

作者: 刘蕴学    时间: 2012-5-15 20:21
标题: 【送分】第五题 关于位移 答案以公布
本帖最后由 了无尘 于 2012-5-17 07:10 编辑

关于自增和自减的第四题,我估计大家也学到些东西,当然老程序员都会。
今天的第五题是
int x = ?;
System.out.println(x << 32);
其中?为任意数,问题是,为什么x << 32还是x?

起来晚了,所以。。。还是不废话了,一下答案摘自我的个人笔记
初中毕业的桑不起啊,语文是体育老师教的,第四题我就表达不清楚,这回还是上代码通俗易懂
  1. public static void main(String[] args)
  2.         {
  3.                 //位运算符
  4.                 // << 左移运算符
  5.                 //5 的二进制是 0000 0101
  6.                 //左移一位的话0000 1010
  7.                 //5 左移一位是10,是不是相当于5*2
  8.                 System.out.println(5 << 1);
  9.                 //3左移一位正好是6
  10.                 System.out.println(3 << 1);
  11.                 //3左移两位是12,也就是*4
  12.                 System.out.println(3 << 2);
  13.                 //对比下,其实这个表达式跟上方的位移的结果
  14.                 //是一致的,运算效率上却要慢很多,这里只是
  15.                 //描述下,左移相当于乘上2的几次幂
  16.                 System.out.println((int)(3 * Math.pow(2, 2)));
  17.                 //下边发现左移0和32还是5,为什么呢?
  18.                 System.out.println(5 << 0);
  19.                 System.out.println(5 << 32);
  20.                 //木哈哈,这个绝对大杀器,为什么-1是31,-2是30?
  21.                 System.out.println(5 << -1);
  22.                 System.out.println("====================================>");
  23.                
  24.                 //打印一下x的位移情况,发现左移32位,x还是
  25.                 //原值,为什么会这样?
  26.                 //分析下最后几行,发现,并不是逐位补位的,
  27.                 //最后一行的像突然插进去的一样。
  28.                 int x = 7;
  29.                 for (int shift = 0; shift <= 32; shift++)
  30.         {
  31.                 System.out.println(getIntegerToBinary(x << shift));
  32.         }
  33.                 System.out.println("====================================>");
  34.                 //到这里可以看见,貌似从32开始,又开始像0-31
  35.                 //那样子逐位位移了,到第64次的时候又会在继续
  36.                 //这看起来是不是像模运算呢?
  37.                 //其实这不难理解,比如没有这个现象的话,咱就单
  38.                 //说位移,32位之后就都是0了吧,显然没有意义,
  39.                 //对于一个int的有效位移次数仅是0-31
  40.                 System.out.println(getIntegerToBinary(x << 33));
  41.                 System.out.println("====================================>");
  42.                 //事实证明,猜想是对的,但问题是,真的是这样?
  43.                 //模运算是很慢的,如果真是这样,位移操作还能比
  44.                 //乘法快?
  45.                 for (int shift = 0; shift <= 32; shift++)
  46.         {
  47.                 System.out.println(getIntegerToBinary(x << shift % 32));
  48.         }
  49.                 System.out.println("====================================>");
  50.                 //既然有效位移次数是0-31,那么31的2进制是什么?
  51.                 //是11111五个一,咱是不是只需要拿到位移次数的
  52.                 //低五位就可以了?你可以打印下0-64的2进制,看看
  53.                 //后5位是什么情况
  54.                 //如果这个实在难以理解,你就当成是32进制,每次取
  55.                 //这个数的32进制的个位数
  56.                 for (int shift = 0, mask = 0x0000001f; shift <= 32; shift++)
  57.         {
  58.                 System.out.println(getIntegerToBinary(x << (shift & mask)));
  59.         }
  60.                 System.out.println("====================================>");
  61.                 //这个眼熟不?如果 % 的方式是对的?左移-1位你怎么
  62.                 //解释?当然你理解成0相当于32,-1相当于31这是可以
  63.                 //但是,你怎么解释-1和31的关系?有什么证据?
  64.                 //总不能说-1%32是31,那还不让人笑掉大牙?
  65.                 //-1 & 0x0000001f是多少?取后五位正好是31
  66.                 System.out.println(getIntegerToBinary(x << (-1 % 32)));
  67.                 System.out.println(getIntegerToBinary(x << (-1 & 0x0000001f)));
  68.                 //将上边的两行化简,最后就是这样的了,结果一样,那
  69.                 //也就是说-1被处理,跟%没有一毛钱关系,问题发生在
  70.                 //-1怎么变成31,呵呵,已经很明显了吧?
  71.                 System.out.println(getIntegerToBinary(x << -1));
  72.                 System.out.println(getIntegerToBinary(x << 31));
  73.                 System.out.println("====================================>");
  74.         }
  75.         
  76.         public static final String DEFAULT_INT_ZERO = "00000000000000000000000000000000";
  77.         public static final String getIntegerToBinary(int value)
  78.         {
  79.                 String binary = Integer.toBinaryString(value);
  80.                 int length = 32 - binary.length();
  81.                 return DEFAULT_INT_ZERO.substring(0, length) + binary;
  82.         }
复制代码

作者: 高云飞    时间: 2012-5-15 20:33
本帖最后由 高云飞 于 2012-5-15 20:38 编辑

因为java的int类型在内存中占用4个字节,也就是32bit,<<不足的位置上,用原来的数补齐,相当于循环左移32位后,每一位上的数又回到原来的位置上了,当然还是原来的x了。
作者: 蒋映辉    时间: 2012-5-15 20:37
可不可以这么回答,因为Int只有32位,当移动位数大于32的时候,就会对移动位数对32求余,再进行移位运算。而32就是刚好不移。。。。
作者: 林豪    时间: 2012-5-15 20:52
以前看过,貌似  int类的  x<<n等价于 x<<n%32....所以x<<32  =  x<<(32%32) =   x<<0  就不变
作者: 林豪    时间: 2012-5-15 20:55
还有 int  x>>n和  x>>>n也是同个道理,long好像是  x>>(n%64)
作者: 任睦强    时间: 2012-5-15 21:04
1) 如果x是byte, short, char类型, 则将x提升为int;

  2) 如果x是byte, short, char, int类型, 则n被重新赋值(过程是:取n的补码的低5位再转成十进制的int值,相当对n取32模: n=n%32);

  如果x是long型, 则n被重新赋值(过程是:取n的补码的低6位再转成十进制的int值,相当对n取64模: n=n%64);

  (因为int类型为4个字节,即32位,移动32位将没有任何意义.对于long则是模64)

  3) 对x左移n个位数, 整个表达式产生一个新值(x的值不变);

  <<是左移符号,列x<<1,就是x的内容左移一位(x的内容并不改变)

  >>是带符号位的右移符号,x>>1就是x的内容右移一位,如果开头是1则补1,是0责补0,(x的内容并不改变).

  >>>是不带符号位的右移,x>>>1就是x的内容右移一位,开头补0(x的内容并不改变)


作者: 胡文杰    时间: 2012-5-15 21:07
好吧 我表示崩溃  肿么那规则来出问题呢?!
作者: 冯心程    时间: 2012-5-15 21:09
这题我会啊 分给我留着老师

数用32位表示 假如这个数是3那么它再计算机中32位是这样的
0000-0000 0000-0000 0000-0000 0000-0011
假如这个数在电视屏幕里从左到右布满 如果向左移动2位 前面消失的两个0就在数的后面补上  。。。(比喻的还算恰当吧、、)
当移动到31位的时候第一个1从左面消失但是从后面补上   向左移动完32位后最后一个1也从左面消失但补在后面  

所以向左移动32位后的结果和移动前是一样的  
作者: 冯心程    时间: 2012-5-15 21:24
冯心程 发表于 2012-5-15 21:09
这题我会啊 分给我留着老师

数用32位表示 假如这个数是3那么它再计算机中32位是这样的

晕 最低位用0补  我再缕缕
作者: 林豪    时间: 2012-5-15 21:30
{:soso_e126:}崩溃了·~难道32没意义JVM就不算了~~~~
求答案..
作者: 于陈    时间: 2012-5-15 22:03
实验了一下:
1.   byte x=5;
System.out.println(x<<8);
输出是0
2.   int x=5;
System.out.println(x<<64);
输出是5
3.   byte x=5;
System.out.println(x<<32);
输出是5
4.   int x=5;
System.out.println(x<<31);
输出是-2147483648
所以前面说的位移32位又刚好回到原来的数的观点肯定不对
那么原因可能是因为int和long在位移运算的时候只有低五位,低6位才会有用,也就是说位移超过31和63就会无效运算
byte,short在位移的时候会自动转成int,所以就能解释为什么我开始实验的时候出现的结果了。
作者: 冯心程    时间: 2012-5-15 22:06
冯心程 发表于 2012-5-15 21:24
晕 最低位用0补  我再缕缕

老师V5  给跪了  求答案:loveliness:
作者: 于陈    时间: 2012-5-15 22:11
本帖最后由 于陈 于 2012-5-15 22:16 编辑

半对额.......我都不知道哪一半对了...我再想想.....老师这个问题真心很伤啊~去看下老毕第二天了
作者: 冯心程    时间: 2012-5-15 22:20
冯心程 发表于 2012-5-15 22:06
老师V5  给跪了  求答案

比我强的都是老师  不要谦虚  么么哒:loveliness:
果断滚回去看视频了  看完答出来分还算我的中不:loveliness:
作者: 王德南    时间: 2012-5-15 22:30
<<(左移运算符):是将左操作数向左移动右操作数指定的位数,右边移出的空位以0填充。
而当左操作数为int类型时,移位运算符右侧的操作数只有低5位是有效的(低5位的十进制最大值为31),
因为int类型只有32位,这样就可以避免将数据全部移出而失去意义。
我假如题目中的x为27,即现在是要计算27<<32。所以我们先取右侧操作数的低5位,32的补码为:
0000-0000 0000-0000 0000-0000 0010-0000
提取低5位后为:00000,这也即是十进制的0,所以,27<<32相当于:27<<0。
也即是x移动0位,所以x<<32仍然是x。
作者: 于陈    时间: 2012-5-15 22:35
本帖最后由 于陈 于 2012-5-15 22:40 编辑

根据毕老师说的:左移运算就是乘以2的移动位数次幂
                          3<<1  就是3*2的1次方
                         3<<2  就是3*2的2次方
                         3<<3  就是3*2的3次方
                         .......
                         3<<31 就是3*2的31次方
                         3<<32就是3*2的0次方  2的0次方就是1,x*1不就等于x么?
                        
                         3<<33就是3*2的1次方
是这样么?
作者: 于陈    时间: 2012-5-15 22:43
于陈 发表于 2012-5-15 22:35
根据毕老师说的:左移运算就是乘以2的移动位数次幂
                          3

啊啊啊啊....悲剧...我再去仔细看下视频~要学校断网了,看来只能明天了~
作者: 冯心程    时间: 2012-5-15 22:48
手慢了  和15楼想的一样  他说的对吧  我是与分无缘了{:soso_e199:}
作者: 王永旺    时间: 2012-5-15 22:51
关于这道题,我的开始的想法是可能是java内存中可能会有一块区域存储x的原始的值。

1.   但之后我查阅了Thinking in java 发现一段话:
       “对char, byte或者short进行移位处理,那么在移位进行之前,它们会自动转换成一个int,得到的也是一个int类型的值只有数值右侧的低5个低位才会有用。这样可防止我们位移超过int型所具有的位数。若对一个long类型的的数值进行处理,最后得到的结果也是long。此时只会用到数值右端的低6位
                                                                                                    ————(机械工艺出版社,java编程思想第四版,50页)


这是书中的原文,我感觉翻译的不是很好,很晦涩。但是我们应该能够理解其内容:
这段话即是说当遇到X<<32情况,只有32的二进制形式(100000)的后五位有效,即x<<32等于x<<0。所以我们发现它x<<32会打印出x的原始值。

2.    同样我也对这段话中提到的其他类型进行了验证(long左移64位,其他类型左移32位):
        long  x =3;
        char  c =5;
        byte  b =7;
        short s= 9;
        System.out.println(x<<64);
        System.out.println(c<<32);
        System.out.println(b<<32);
        System.out.println(s<<32);       

得到的结果仍然为:3 5 7 9 可以基本说明这段话是正确的。

1.jpg (80.42 KB, 下载次数: 102)

1.jpg

作者: 王晓龙    时间: 2012-5-15 22:54
看来 我学的不认真啊 从头再看一遍吧
作者: 古银平    时间: 2012-5-15 23:10
int 型4个字节共32位,左移32位超出界限,左移的时候就用左移的位数减去32,则为x<<(32-32)=x<<o=x
作者: 黄燕京    时间: 2012-5-15 23:10
其实没有那么复杂
int类型的范围2147483641~-2147483648  
而位移运算就是 m<<n即在数字没有溢出的前提下,对于正数和负数,左移n位都相当于m乘以2的n次方.
除了0之外最小整数是1, 1<<32就等于1*2^32=4294967296 ,已经超出了int的范围了,会出错 ,当n为32时,没有任何意义,所以系统会为n对32取摸
作者: 徐然    时间: 2012-5-15 23:14
System.out.println((7<<0));  结果是7
System.out.println((7<<1));  结果是14
System.out.println((7<<2));  结果是28

System.out.println((7<<32)); 结果是7
System.out.println((7<<33)); 结果是14
System.out.println((7<<34));  结果是28

System.out.println((7<<64));结果是7
System.out.println((7<<65));结果是14
System.out.println((7<<66));结果是28

貌似有点规律了,左移32位相当于7<<0位
而int类型的最小值是-2147483648,也就是10000000000000000000000000000000
而任何int类型值<<31都得到这个数
也就是说int类型的值只要左移32都是没有意义的,就相当于左移0位
超过32的都以大于他的数来计移动位数
那么就以32位作为一个周期,再次回到int的范围


发现的一点小规律,不知道会不会对大家的分析解决这个问题有帮助
作者: 黄奕豪    时间: 2012-5-16 00:02
我要回答这个问题“如果%可以的话,请问为什么位移运算快?”
根据15楼的说法(学习到了,谢谢楼主和15楼及各位)
1、移位运算时计算机可以直接根据左操作数(被移位的数)的类型来取舍右操作数(即要移的位数)的有效低位位数,取舍出来是二进制,可以直接进行移位!
2、用%32(对32取余)时,因为计算机要先对%32进行先运算得到移位的位数,才能进行移位,在这个过程中,计算机多了帮数字“32”申请内存,调用%指令,返回%32结果的时间!所以移位运算要比这个速度更快!

个人的粗浅看法,不知道跟标准答案怎么样?还望赐教!!
作者: Fc10232    时间: 2012-5-16 13:58
嘿嘿,,  我个人感觉比较喜欢做这种题目
作者: xieshuhua    时间: 2012-5-16 17:45
本帖最后由 谢述华 于 2012-5-16 21:30 编辑

上面的兄弟已经讲的很全面了,还是得不到3分,不知是不是和底层有关,某个系统文件用函数的方式定义了位运算的详细步骤。
1、类运行都是由底层的命令实现的,所以我查了下汇编语言的书籍,从中得知汇编程序已经能够实现左移右移命令。
2、我编译了只含有右移命令的,最小的java文件,希望知道右移命令是怎么被执行的。图中可知只引用的Object类。我用反编译工具打开了Object类文件,找不到线索。。
疑点:编译时期java文件就转换为了二进制数据(即进位命令已经执行),Object是如何实现的,是不是通过汇编语言实现的?
自己还在思考中,不过也特别希望得到指点。。。

3333.png (2.49 KB, 下载次数: 31)

3333.png

116.png (50.36 KB, 下载次数: 29)

116.png

Object.rar

640 Bytes, 下载次数: 109


作者: 追忆...    时间: 2012-5-16 20:45
看完15楼的终于懂了
作者: xieshuhua    时间: 2012-5-17 00:55
1<<5应该算是编译运算吧。。。我很想确定<<的具体运算过程 在那个文件可以找到;找不到的话,在那个书里有提及。
作者: 闾丘日月    时间: 2012-5-23 17:41
看完楼主和15楼的表述,算是大概明白了。
之前一直以为位运算到32是无意义的,全部会为0.
原来java为了避免这种情况,封装了一个&31啊。
作者: 揭耀祖    时间: 2012-5-24 14:25
本帖最后由 jxj4227982333 于 2012-5-24 14:38 编辑

我觉得楼主把问题讲的复杂化了,其实很简单的一个问题吗。
一个数左移32位,比如1<<32;
我们可以这么看的:
1。先把1左移31位就是 1<<31 二进制表示形式就是:1000-0000 0000-0000 0000-0000 0000-0000
2.再把上面那个数再左移一位,但由于它的首位为1,所以把它移出去后补进来的不再是0了而是1,那么得到的结果为
    0000-0000 0000-0000 0000-0000 0000-0001

也就是1咯!
其它的数也是这个道理。
楼主你说呢!{:soso_e112:}

15楼的我也看了下,他说超过32就只取低5位的数,这很好理解,因为32是一个周期,就好像正弦函数一样,不管你在x轴上走了多少个周期,我只想知道你在最后一个周期内在哪里我就可以算出y值了。

作者: 揭耀祖    时间: 2012-5-24 14:49
本帖最后由 jxj4227982333 于 2012-5-26 09:42 编辑


刘蕴学  我建议你手动试试2左移31位,你看看最低位还是不是1


楼主你看着啊!
2的二进制表示是              0000-0000 0000-0000 0000-0000 0000-0010
向左移30位就是               1000-0000 0000-0000 0000-0000 0000-0000
再左移一位总共就是31位
由于首位为1所以补1        0000-0000 0000-0000 0000-0000 0000-0001   //这里我错了 是下面这样子的
再左移一位总共就是31位
丢弃最高位1之后就变成了       0000-0000 0000-0000 0000-0000 0000-0000   //修改后的      
再左移一位就共32位了
32%32 =0实际左移0位    0000-0000 0000-0000 0000-0000 0000-0010   =  2
楼主!

作者: 赵茹艳    时间: 2012-5-24 22:52
对于x<<n,当n>=32时等价于 b<<(n%32)
这样做是为了防止我们在一个int数里移动不切实际的位数。超出其存储范围的位移是没有意义的。因为int总共就32位,如果不做处理,左移32位就相当于全部移空了,那么n>32时,结果全部都等于0,这样就完全没有意义。所以Jva内部做了规定,当n>=32时等价于 b<<(n%32),当n=32时就等价于b<<(32%32),也就是b<<0,所以结果不变。
若对byte或者short进行移位处理,那么在移位进行之前,它们会自动转换成一个int。只有右侧的5个低位才会用到。long只有右侧的6个低位才会用到,char只有右侧的4个低位才会用到。
作者: 揭耀祖    时间: 2012-5-26 01:57
本帖最后由 jxj4227982333 于 2012-5-26 09:33 编辑

点评
刘蕴学  你自己敲代码移一下吧,这个东西不是这样的  发表于 1 小时前
代码我敲了,跟我的理论是一致的,其实想也想的到,移位超过32之后还有意义吗,没有了!所以超过之后只要取它的低5位就可以了,要不然移位运算比加减乘除还要慢。

int i = 0x40000000; //16进制的40000000,2进制的01000000...0000
i = i << 1;
那么,i在左移1位之后就会变成0x80000000,也就是2进制的100000...0000,符号位被置1,其他位全是0,变成了int类型所能表示的最小值,32位的int这个值是-2147483648,溢出.
如果再接着把i左移1位会出现什么情况呢?C语言中采用了丢弃最高位的处理方法
,丢弃了1之后,i的值变成了0.
以上摘自一篇文章

红字的地方很好的解释了为什么2<<31位后会变成0的。
楼主我并没有否定你的结论,你说的是对的,只是问题似乎并没有你说的那么复杂而已。把1+1=2这样的问题描述的让博士都听的一头雾水就没必要吧,而把爱因斯坦的高深的相对论说的跟小孩子躲猫猫一样浅显易懂这才是深入浅出的最高境界,楼主你说对吧。


作者: xieshuhua    时间: 2012-6-3 21:43
1、javap可知引用了ishl
  1. 0: bipush 12
  2. 2: istore_1
  3. 3: iload_1
  4. 4: bipush 32
  5. 6: ishl
  6. 7: istore_2
  7. 8: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
  8. 11: iload_2
  9. 12: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
  10. 15: return
复制代码
2、查询JVM文档可知

JVM文档.png (73.08 KB, 下载次数: 33)

JVM文档.png





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2