Java Integer 进制转化的实现(附源码),对模与补码的理解

1.toBinaryString方法的实现

 1 public static String toBinaryString(int i) {
 2 return toUnsignedString0(i, 1);
 3 }
 4 private static String toUnsignedString0(int val, int shift) {
 5 // assert shift > 0 && shift <=5 : "Illegal shift value";
 6 int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
 7 int chars = Math.max(((mag + (shift - 1)) / shift), 1);
 8 char[] buf = new char[chars];
 9
10 formatUnsignedInt(val, shift, buf, 0, chars);
11
12 // Use special constructor which takes over "buf".
13 return new String(buf, true);
14 }

Integer.SIZE

这是用来二进制补码形式表示 int 值的比特位数。

简单提下为什么需要用2进制的补码来表示呢?

简单的来说,补码就是取反加1以方便把减法当作加上带负号的数进行加法运算。

在计算机系统中,数值一律用补码进行储存。 主要原因:使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补

码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃

int是4字节,一共是32位。所以Size是常数为32

Integer.numberOfLeadingZeros(val);

在指定 int 值的二进制补码表示形式中最高位(最左边)的 1 位之前,返回零位的数量

那么 mag的含义就是为了进行运算,排除掉数值的2进制中无关位的影响(int是4字节32位)

chars什么含义并没有看太懂 我们先且试一个数

例如9(00000000 00000000 00000000 0000 1001)

mag=4 chars=4暂时还是不明白chars 和shift的用途,后面对比8进制 16进制转换的代码 就会发现shift取值为1 3 4就是后文模运算(&运算)的关键之一。(2^1=2 2^3=8 2^4=16))

继续向下读代码会发现formatUnsignedInt是实现转化的核心函数

     

 1            formatUnsignedInt(val, shift, buf, 0, chars);
 2 static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
 3 int charPos = len;
 4 int radix = 1 << shift;
 5 int mask = radix - 1;
 6 do {
 7 buf[offset + --charPos] = Integer.digits[val & mask];
 8 val >>>= shift;
 9 } while (val != 0 && charPos > 0);
10
11 return charPos;
12 }

对比一下参数的传递很容易得到chars对应len 长度

这个计算长度的原理是什么呢?

mag + (shift - 1)) / shift mag是val二进制的长度,最高位一定为1 我们举八进制的例子来说明val在2进制中长为mag因为2^3=8 所以二进制中的3位就相当于8进制中的一位(这和Ip点分十进制又非常类似)shoft-1则是为了处理10 11 位的情况 10/3=3而实际情况是10位的val该是4位的八进制数终上:
 chars = Math.max(((mag + (shift - 1)) / shift), 1);

radit<<shift等于2的shift次方。也就是radit对应到底是几进制转化

mask = radix - 1;就是进制转化对应的模的大小需要注意的是这和求补码的模不一样 这个模是为了得出val最后几位 对应的进制的值例如 8(0000 1000)%7(0000 0111) =1 buf 的最后一位的值就是1

do while(当value不为0并且长度>0时)循环里做什么呢?

buf[offset + --charPos]这和高精度运算很有几分相似,都是从末尾开始写入值。

通过把10进制的数通过位运算后 一位一位的写入buf这个数组中来 来实现进制的转换。

而val & mask 这个运算实际上就是模2运算、模8运算、模16运算( 后文附上对模与补码理解)。

Integer.digits[]在api中没有找到介绍,网上搜索也只找到integerDigits[n] integerDigits[n,2]的介绍。结合integerDigits[n,2]的介绍和运行知道Integer.digits[]的作用

例如9模2后为00000000 00000000 00000000 0000 0001 应该是只取出最后一位并转换成char型 存入buf数组中

最后一个string(buf,true)完成进制的转化输出。

文末附上对模的认识

模的概念:把一个计量单位称之为模或模数。例如在模为16的系统中-10其实和+6带来的影响是一样的,即

10和6在模12的系统中互为补数(补码)。计算机的硬件结构中只有加法器,所以大部分的运算都必须最终转换为加法。

就采用的模的概念对原码,反码进行改进以方便运算,即计算机中的运算都是补码之间的加法运算。个人认为:模的概念在2进制中就是和按位取反是一个东西。模概念的出现就是为了人直观的进行反码计算.

我们知道补码是反码加1 例如:-9(10001001)--(11110110)--(111101

11)实际就是-9的模128后的2进制+1,为什么是模128,计算机的计数器是8位的,第一位是符号位所以8位实际能记得数就是256-2^7=128

a mod b = - (-a mod b) -9模128后为(11110110).

那补码和模到底什么关系呢?

模的出现是为了方便计算机对负数直接进行带上符号位的加法运算。模到底是如何实现这一功能的

在计算机网络的IP地址的点分十进制转换中经常会发现

255=2^7+2^7-1;2^7=2^6+2^5…………2^1+2^0+1
2^k=2^(k-1)+2^(k-2)+…………+2^1+2^0+1

//这可以通过迭代法来证明

2^k=(1+1)*2^(k-1)=2^(k-1)+2^(k-1)

//用k-1 去替代k即k=k-1

2^(k-1)=2^(k-2)+2^(k-2)
以此类推会有2^k=2^(k-1)+2^(k-2)+…………+2^1+2^1 
即原式得证 

这跟模与补码有什么联系呢?

假如有一个正数a=flag1*2^(n-1)+flag2*2^(n-2)+ …… +flag(n-1)*2^1+flag(n)*2^0

其中flag取0或者1

1 a=2^n-2^n+a//正数的原,反,补一致没有讨论的必要  -a=2^n-2^n-a//替换右边的一个2^n和a,会有如下等式2^n-a=2^(n-1)+2^(n-2)+…………+2^1+2^0+1-(flag1*2^(n-1)+flag2*2^(n-2)+ …… +flag(n-1)*2^1+flag(n)*2^0)
2^n-a=(1-flag1)*2^(n-1)+(1-flag2)*2^(n-2)+…………+(1-flagn)*2^0+1
(1-flag1)*2^(n-1)+(1-flag2)*2^(n-2)+…………右边这一行:由于flag是只能取0.1实际这就是按位取反最后+1
2^n-a:2的n次方就是模这点上文已经提到

终上所述:模-原码=反码

对于第一符号位的处理和把符号位当作数值位进行运算的理解尚且不足 第一篇博客 水平有限 诸君见谅

时间: 2024-10-12 21:59:04

Java Integer 进制转化的实现(附源码),对模与补码的理解的相关文章

Java设计模式-代理模式之动态代理(附源码分析)

Java设计模式-代理模式之动态代理(附源码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的区别就是:动态代理是在运行时刻动态的创建出代理类及其对象.上篇中的静态代理是在编译的时候就确定了代理类具体类型,如果有多个类需要代理,那么就得创建多个.还有一点,如果Subject中新增了一个方法,那么对应的实现接口的类中也要相应的实习该方法,不符合设计模式原则. 动态代理的做法:在运行时刻,可以动态创建出一个实现了多个接口的代理类.每个代理类的对象都会关联一个表示内部处理

java crm 进销存 springmvc SSM 项目 源码 系统

系统介绍: 1.系统采用主流的 SSM 框架 jsp JSTL bootstrap html5 (PC浏览器使用) 2.springmvc +spring4.3.7+ mybaits3.3  SSM 普通java web(非maven, 附赠pom.xml文件)  数据库:mysql 3.开发工具:myeclipse  eclipse idea 均可, 没有限制. 我这边myeclipse 2014 导出来的项目源码 ---------------------------------------

Java之内部类语法详解(附源码)

前言   可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性.然而必须要了解,内部类与组合是完全不同的概念,这一点很重要. 在最初,内部类可能看起来比较奇怪,而且要花些时间才能在设计中轻松地使用它们.对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,在接下来的几个章节中就会发现内部类的益处. 示例源码 package com.mufeng.thetenthchapter;

Java之嵌套接口详解(附源码)

示例源码 接口可以嵌套在类或其他接口中.这揭示了许多非常有趣的特性: package com.mufeng.theninthchapter; class A { interface B { void f(); } public class BImp implements B { @Override public void f() { // TODO Auto-generated method stub } } private class BImp2 implements B { @Overrid

Java 10进制转2、8、16进制转换 / 2、8、16进制转10进制转换

public static void main(String[] args) { int i = 10; System.out.println("***********10进制转换2进制.8进制.16进制************"); System.out.println(Integer.toBinaryString(i)); // 10转换2进制 System.out.println(Integer.toOctalString(i)); // 10转换8进制 System.out.p

标识符,进制转化,原反补码等

标识符 Java对包.类.方法.参数.变量等要素命名时使用的字符序列. 规则:***** 1.由字母(含中.英.日.俄等).数字.下划线_和美元符号$组成. 2.不能以数字开头   int 123a=1; 3.区分大小写  int a=1和int A=1是不一样的. 4.长度无限制.(一般编程长度不超过15个字符) 5.不能是Java中的保留字和关键字    int class="1"    int ainta=1 保留字和关键字都有哪些?我们知道以后应该避开它们. 标识符命名习惯:*

c语言进制转化

#include <stdio.h> // 进制转化 int main(void) { int i1 = 12; int i2 = 88; int i3 = 0x32C; printf("八进制输出i1 = %o\n", i1); // 14 printf("十六进制输出i2 = %x\n", i2); // 58 printf("十六进制输出i3 = %x\n", i3); // 32c printf("十进制输出i3 =

java 16进制转换10进制

public static String toHexString2(byte[] b) { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < b.length; ++i) { buffer.append(toHexString2(b[i])); } return buffer.toString(); } public static String toHexString2(byte b) { char[] buffer =

Java的进制转换操作(十进制、十六进制、二进制)

2014-05-06 17:34 吴文付 最近由于工作上的需要,遇到进制转换的问题.涉及到的进制主要是 十进制,十六进制,二进制中间的转换. 这里整理一下.具体的计划为:封装一个转换类,一个测试类. 1.十进制 转 十六进制: 2.十进制 转 二进制 3.十六进制 转 十进制: 4.十六进制 转 二进制: 5.二进制转 十进制: 6.二进制转十六进制: Java的进制转换操作(十进制.十六进制.二进制),布布扣,bubuko.com