Java 源码阅读 | 数字类型

Number

Number 是 Byte, Double, Float, Integer, Long, Short, BigDecimal, BigInteger 这几个数据类型的父类,只要继承了 Number 类型,就可以在这几个类型之间互相转换。

Integer i = new Integer(200);
float v = i.floatValue();
long l = i.longValue();
byte b = i.byteValue();
double d = i.doubleValue();
short s = i.shortValue();

当然,实际上内部就是对其进行强制类型转换,下面的写法反而避免了 box and unbox

int i = 200;
float v = (float) i;
long l = (long)i;
byte b = (byte) i;
double d = (double) i;
short s = (short) i;

不过请注意类型转换时出现不一致的情况,比如上面的 int 转 byte 时,十进制 200 转换为二进制是 11001000,由于计算机存储数据都是补码形式存储的,所以二进制 11001000 需要转换为补码,补码 = 原码取反(符号位不变) + 1。

原码取反:11001000 -> 10110111

反码+1:10110111 + 1 -> 10111000

二进制 10111000 转换为十进制是 -56,因此 int 类型的 200 转为 byte 类型实际上是 -56。

Integer

缓存特性

Integer 内部有个内部类 IntegerCache,这个类内部维护了 low、high、cache 数组。

low 默认是 -128,high 最小是 127,可由 java.lang.Integer.IntegerCache.high 这个 JVM 参数设置 high 的值。

cache 数组是 [-128, high] 这个区间的数字,当你需要的数字在这个区间内时,可以直接从这个数组中获取,避免了创建 Integer 对象的开销,这就是 Integer 的缓存特性。

比较特性

Integer 实现了 Comparable 接口。

public int compareTo(Integer anotherInteger) {
  return compare(this.value, anotherInteger.value);
}

public static int compare(int x, int y) {
  return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

x.compareTo(y) 当 x 小于 y 时,返回 -1;等于返回 0;大于返回 1

常量

Integer.MIN_VALUE, Integer.MAX_VALUE都是我们知道并且常用的。

digits 表示一个字符类型的数字数组

final static char[] digits = {
  '0' , '1' , '2' , '3' , '4' , '5' ,
  '6' , '7' , '8' , '9' , 'a' , 'b' ,
  'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
  'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
  'o' , 'p' , 'q' , 'r' , 's' , 't' ,
  'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};

静态方法

  • Integer.bitCount(200) 可以返回 200 表示的二进制中 1 的个数

  • Integer.parseInt("1000", 2) 将二进制 1000 转换成 int

  • Integer.parseInt("1000") 默认十进制 1000

  • Integer.valueOf(100) 将 int 100转换为 Integer 包装类型

public static Integer valueOf(int i) {
  // 当 i 在 [-128,127] 区间内,直接从缓存数组中获取
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  // 否则包装 i
  return new Integer(i);
}
  • Integer.valueOf("100") 将字符串 “100” 转换为 Integer 包装类型
public static Integer valueOf(String s) throws NumberFormatException {
  return Integer.valueOf(parseInt(s, 10));
}

可以看出当传入字符串时,实际上先做了一次 parseInt 的转换,该方法过程为:String -> int -> Integer。所以当你不需要返回包装类型时,可以直接调用 parseInt 方法。

Long

缓存特性

Long 内部有个内部类 LongCache,相比 Integer,Long 内部只维护了一个 cache 数组,且区间固定为 [-128, 127]。

比较特性

Long 也实现了 Comparable 接口。

常量

Long.MIN_VALUELong.MAX_VALUE

静态方法

和 Integer 基本相同

Short 和 Byte

同 Long

Float

比较特性

Float 也实现了 Comparable 接口。

常量

Float.MAX_VALUEFloat.MIN_VALUE

Float.POSITIVE_INFINITY 正无穷

Float.NEGATIVE_INFINITY 负无穷

Float.NaN 不是一个数字的表示形式

静态方法

借助 FloatingDecimal,可以将 String 转为 Float 类型,或者将 Float 类型转为 String

FloatingDecimal.toJavaFormatString(float f) 该方法首先将 float 转为二进制,再将二进制转为 String

FloatingDecimal.parseFloat(String s) 该方法先将 String 转为二进制,再将二进制转为 float

Double

比较特性

Double 也实现了 Comparable 接口。

常量

同 Float

静态方法

Double 也是借助的 FloatingDecimal,将 String 转为 Double 类型,或者将 Double 类型转为 String

BigDecimal

从 BigDecimal 开始,我们从例子出发,来学习内部实现。

BigDecimal b1 = new BigDecimal("0.01");
BigDecimal b2 = new BigDecimal("9.99");
BigDecimal res = b1.add(b2);
System.out.println(res);   // 10.00

上面代码的运行结果是 10.00,符合我们的预期。

我们先来看 BigDecimal 的构造方法,来看下我们输入的字符串时如何被转换成 BigDecimal 对象的。

public BigDecimal(String val) {
    this(val.toCharArray(), 0, val.length());
}
public BigDecimal(char[] in, int offset, int len) {
    this(in,offset,len,MathContext.UNLIMITED);
}
public BigDecimal(char[] in, int offset, int len, MathContext mc) {
  // ...
  this.scale = scl;
  this.precision = prec;
  this.intCompact = rs;
  this.intVal = rb;
}

我们看到最后四行代码,实际上就是初始化 BigDecimal 的四个变量:

  • scale:表示小数点后面的位数
  • precision:用于操作的位数。比如传入 0.01,precision 为 1,传入 9.990,precision 为 4
  • intCompact:表示去掉小数点后,看成一个 int 所表示的值,比如传入 0.01,intCompact 为 1,传入9.990,intCompact 为 9990
  • intVal:当 precision 所表示的位数超过 18 位时,intVal 就会被使用,intVal 使用 BigInetger 来表示传入的数据。当 intVal 的值超过 Long 表示的最大区间 [-9223372036854775808, 9223372036854775807] 时,intCompact 的值就变成了 Long.MIN_VALUE = -9223372036854775808

接下来看 add 方法是如何做的。

public BigDecimal add(BigDecimal augend) {
  if (this.intCompact != INFLATED) {
    if ((augend.intCompact != INFLATED)) {
      // 简单 long 计算
      return add(this.intCompact, this.scale, augend.intCompact, augend.scale);
    } else {
      // 超出 long 的范围,使用 BigInteger 计算
      return add(this.intCompact, this.scale, augend.intVal, augend.scale);
    }
  } else {
    if ((augend.intCompact != INFLATED)) {
      return add(augend.intCompact, augend.scale, this.intVal, this.scale);
    } else {
      return add(this.intVal, this.scale, augend.intVal, augend.scale);
    }
  }
}

从构造方法我们知道,当 intCompact 的大小为 INFLATED=Long.MIN_VALUE 时,会使用 BigInetger 表示的 intVal 来计算。同理,减乘除都是一样的。

在除法运算时,我们还需要指定舍入模式,常用的舍入模式有如下 8 种,在 RoundingMode 枚举中列出 。

  • UP:不看符号,直接进位,比如 6.666 保留小数点后一位,直接进位就是 6.7
  • DOWN:去掉多余的位数。比如 6.666 保留小数点后一位,直接去掉后面的数字就是 6.6
  • CEILING:正数进位,负数舍位。比如 1.1 进位为 2,-1.1 舍位为 -1
  • FLOOR:正数舍位,负数进位。比如 1.1 舍位为 1,-1.1 进位为 -2
  • HALF_UP:不看符号,四舍五入(包括 5)。比如 1.5 四舍五入为 2,-1.5 四舍五入为 -2
  • HALF_DOWN:不看符号,四舍六入(不包括 5)。比如 1.5 四舍六入为 1,1.6 四舍六入为 2
  • HALF_EVEN:如果舍弃部分左边的数字为偶数,则为 ROUND_HALF_DOWN;否则为 ROUND_HALF_UP。比如 1.5 舍弃 5 后左边是奇数 1,使用四舍五入结果为 2;2.5 舍弃 5 后左边是奇数 2,使用四舍六入结果还是 2。
  • UNNECESSARY:对于有精确结果的计算,可以使用 UNNECESSARY,表示不需要进位或舍位,如果结果位数大于你指定的位数,则会抛出异常。比如 3/8 = 0.375,你指定保留小数点后 2 位,使用 UNNECESSARY 模式则会报错,如果你指定保留 3 位及以上,则 ok。

加减乘有指定位数的方法吗?当然有,只不过是以另一种方式提供的。

实际上,当你 new BigDecimal("3") 时,会默认指定一个 MathContext.UNLIMITED 的参数,如下代码所示:

public BigDecimal(String val) {
    this(val.toCharArray(), 0, val.length());
}
public BigDecimal(char[] in, int offset, int len) {
    this(in,offset,len,MathContext.UNLIMITED);
}
public static final MathContext UNLIMITED =
    new MathContext(0, RoundingMode.HALF_UP);

MathContext 对象的第一个参数 0 表示使用传入参数的所有位数来计算,第二个参数就是舍入模式了,默认是四舍五入的。

当我们在计算时也可以指定参与运算的位数:

BigDecimal b1 = new BigDecimal("0.11");
BigDecimal b2 = new BigDecimal("9.99");
BigDecimal res = b1.add(b2);
System.out.println(res);  // 10.10

默认是所有位数参数运算,结果是 10.10

我们修改代码限制只有两位参与运算,且使用四舍五入模式:

BigDecimal b1 = new BigDecimal("0.11");
BigDecimal b2 = new BigDecimal("9.99");
BigDecimal res = b1.add(b2, new MathContext(2, RoundingMode.HALF_UP));
System.out.println(res);  // 10

结果是 10,表示只有 0.1 和 9.9 参与了运算,符合了我们预期。

BigInteger

我们还是从例子出发,来学习内部实现。

String s1 = Long.MAX_VALUE + "1";
String s2 = Long.MAX_VALUE + "2";
BigInteger i1 = new BigInteger(s1);
BigInteger i2 = new BigInteger(s2);
BigInteger res = i1.add(i2);
System.out.println(res);   // 92233720368547758071 + 92233720368547758072 = 184467440737095516143

我们先来看 BigInteger 的构造方法,来看下我们输入的字符串时如何被转换成 BigInteger 对象的。

public BigInteger(String val) {
    this(val, 10);
}
public BigInteger(String val, int radix) {
  // 关注 signum 和 mag 数组
}

mag 存储的是字符十进制转为二进制后计算所得 int 类型的值:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值