JDK8 BigDecimal API-创建BigDecimal源码浅析二
第二篇,慢慢来
根据指数调整有效小数位数
// 上一篇由字符串创建BigDecimal代码中,有部分代码没有给出,这次补上
// 这个是当解析字符数组时存在有效指数时调整有小小数位数方法
private int adjustScale(int scl, long exp) {// 参数:scl-->>原生有效小数位数,exp:有效指数
long adjustedScale = scl - exp;// 这个其实自己写个例子就能明白,例如数值1.234*10^-3次方,此时实际数值位0.001234scl=3,exp=-3,
// 实际有效小数位数是不是3-(-3)=6
if (adjustedScale > Integer.MAX_VALUE || adjustedScale < Integer.MIN_VALUE)// 前提是这个scl-exp还在int的表数范围,否则抛出异常
throw new NumberFormatException("Scale out of range.");
scl = (int) adjustedScale;// 若还在int的表数范围则强转为int并返回
return scl;
}
解析字符数组中的指数表达式
// 解析指数表达式
private static long parseExp(char[] in, int offset, int len){
long exp = 0;// 先定义一个指数值exp
offset++;// 将索引自增,在调用该方法时,索引尚还在指数标识符'e'/'E'这里,索引需要自增,不清楚的看上一篇文章
char c = in[offset];// 获取当前字符c,并将需要解析的字符长度自减
len--;
boolean negexp = (c == '-');// 判断指数表达式的首位是不是符号位,并以negexp标识位表示是否为负
// optional sign
if (negexp || c == '+') {// 首位就是符号位,不是'-'就是'+'
offset++;// 解析出符号位,索引自增
c = in[offset];// 继续获取当前字符c,长度自减
len--;
}
if (len <= 0) // no exponent digits,若第一位不是符号位,但是长度已经小于等于0,就代表该指数表达式为空将抛出异常
throw new NumberFormatException();
// skip leading zeros in the exponent
while (len > 10 && (c=='0' || (Character.digit(c, 10) == 0))) {// 去除除符号位外的前置字符'0',因为并没有什么卵用
offset++;
c = in[offset];
len--;
}
if (len > 10) // too many nonzero exponent digits,若除'0'后指数位数还是大于10则抛出异常,一般指数都很少,
//10^100:指数才3位,但是这个数量级可是比整个宇宙的行星数量还多
throw new NumberFormatException();
// c now holds first digit of exponent
for (;; len--) {// 需要解析的字符长度小于等于10进入循环
int v;// 定义当前循环中解析字符c的实际数值
if (c >= '0' && c <= '9') {// 若字符c是数字值,此时获取c的实际数字值并赋值给v
v = c - '0';
} else {
v = Character.digit(c, 10);
if (v < 0) // not a digit
throw new NumberFormatException();
}
exp = exp * 10 + v;// 计算当前已经解析出的指数表达式的值,因多解析出一位因此整个表达式的值:原指数值exp上升一个进位制需要扩大10倍
if (len == 1)// 若此时需要解析的长度已经是1则结束循环(此时当前字符的的位置尚未自减)
break; // that was final character
offset++;// 若还未解析到最后的字符则长度自减,并获取下一个字符并赋值给c
c = in[offset];
}
if (negexp) // apply sign,解析完成以后根据符号位negexp获取指数的实际值并返回
exp = -exp;
return exp;
}
BigDecimal的构造方法,这些构造包含推荐使用的以String为构造参数的方法最终调用的都是上篇文章所分析的以字符数组为参数的构造器
//
public BigDecimal(char[] in) {
this(in, 0, in.length);
}
//
public BigDecimal(char[] in, MathContext mc) {
this(in, 0, in.length, mc);
}
//
public BigDecimal(String val) {
this(val.toCharArray(), 0, val.length());
}
//
public BigDecimal(String val, MathContext mc) {
this(val.toCharArray(), 0, val.length(), mc);
}
接下来看一下其它构造器,这些其实都不是推荐使用的构造器
public BigDecimal(double val) {// 这个方法并不推荐使用,因为浮点数在计算机中听不一定能准确表示,因此使用该构造方法创建的实际值可能与传入参数的看到的表面值并不相同
this(val,MathContext.UNLIMITED);// 方法转发,调用下面的方法
} public BigDecimal(double val, MathContext mc) {
if (Double.isInfinite(val) || Double.isNaN(val))// 传入的参数为无穷大或者为非数,则抛出异常
throw new NumberFormatException("Infinite or NaN");
// Translate the double into sign, exponent and significand, according
// to the formulae in JLS, Section 20.10.22.
long valBits = Double.doubleToLongBits(val);// 该方法还没细看,作用就是在存储层面将double转变为long类型,double与long类型都是64位,但是double首位为符号位,
接下来的11位为指数位,最后的52位为尾数位.而long类型64位都是尾数.这个方法就是将double不按照原有的解析逻辑解析,
而是直接按照long类型解析
int sign = ((valBits >> 63) == 0 ? 1 : -1);// 判断最高位符号位的正负对sign进行赋值
int exponent = (int) ((valBits >> 52) & 0x7ffL);// 0x7ffL的二进制表示:低位11位均为1其余都是0,这一步就是获取double双精度浮点数的中间11位的指数部分
long significand = (exponent == 0
? (valBits & ((1L << 52) - 1)) << 1
: (valBits & ((1L << 52) - 1)) | (1L << 52));// valBits & ((1L << 52) - 1):用于获取double的尾数位
exponent -= 1075;// 自减1075,因为在原始的double指数位中存储的是指数实际值+1023,至于为什么可以取看看浮点数的底层原理,但是这里不仅减去了1023
// 又减去了52,这是因为double的实际值=((-1)^sign)*significand*2^exponent,而标准的表示中significand前默认是"1."
// 即significand全部都是小数,这也是为啥在指数不为0时进行"| (1L << 52)"操作.此时significand为long类型的整数也就是
// 上升了52个进位制,因此需要减去52,索引一共减去1075
// At this point, val == sign * significand * 2**exponent. /*
* Special case zero to supress nonterminating normalization and bogus
* scale calculation.
*/
if (significand == 0) {// 若处理完成后的尾数为0,这是特殊情况:此时BigDecimal值为0,有效位数为1
this.intVal = BigInteger.ZERO;
this.scale = 0;
this.intCompact = 0;
this.precision = 1;
return;
}
// Normalize
while ((significand & 1) == 0) { // i.e., significand is even,去除尾数右侧的0
significand >>= 1;// 若尾数为偶数则缩小二倍并将指数自增(实际值还不变)
exponent++;
}
int scale = 0;
// Calculate intVal and scale
BigInteger intVal;// 定义有效小数位数与intVal
long compactVal = sign * significand;
if (exponent == 0) {
intVal = (compactVal == INFLATED) ? INFLATED_BIGINT : null;
} else {
if (exponent < 0) {
intVal = BigInteger.valueOf(5).pow(-exponent).multiply(compactVal);
scale = -exponent;
} else { // (exponent > 0)
intVal = BigInteger.valueOf(2).pow(exponent).multiply(compactVal);// 根据double的实际值=((-1)^sign)*significand*2^exponent
// 获取实际值
}
compactVal = compactValFor(intVal);// 之前分析过就是根据intVal获取简洁值(前提是intVal对象的值可以使用long类型表示而不溢出)
}
int prec = 0;int mcp = mc.precision;// 定义有效位数及获取MathContext中的有效位数
if (mcp > 0) { // do rounding
int mode = mc.roundingMode.oldMode;
int drop;
if (compactVal == INFLATED) {// 代表intVal对象的实际值已经溢出
prec = bigDigitLength(intVal);// 见JDK8 BigDecimal API-创建BigDecimal源码浅析二的更多相关文章
- ReentrantLock和condition源码浅析(二)
转载请注明出处... 接着上一篇的ReentrantLock和condition源码浅析(一),这篇围绕着condition 一.condition的介绍 在这里为了作对比,引入Object类的两个方 ...
- SparkConf加载与SparkContext创建(源码阅读二)
紧接着昨天,我们继续开搞了啊.. 1.下面,开始创建BroadcastManager,就是传说中的广播变量管理器.BroadcastManager用于将配置信息和序列化后的RDD.Job以及Shuff ...
- LinkedList类源码浅析(二)
1.上一节介绍了LinkedList的几个基本的方法,其他方法类似,就不一一介绍: 现在再来看一个删除的方法:remove(Object o) remove方法接受一个Object参数,这里需要对参数 ...
- ArrayList类源码浅析(二)
1.removeAll(Collection<?> c)和retainAll(Collection<?> c)方法 第一个是从list中删除指定的匹配的集合元素,第二个方法是用 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- Android开发之Theme、Style探索及源码浅析
1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...
- CountDownLatch源码浅析
Cmd Markdown链接 CountDownLatch源码浅析 参考好文: JDK1.8源码分析之CountDownLatch(五) Java并发之CountDownLatch源码分析 Count ...
- Bytom侧链Vapor源码浅析-节点出块过程
Bytom侧链Vapor源码浅析-节点出块过程 在这篇文章中,作者将从Vapor节点的创建开始,进而拓展讲解Vapor节点出块过程中所涉及的源码. 做为Vapor源码解析系列的第一篇,本文首先对Vap ...
- Phoenix创建索引源码过程
date: 2020-09-27 13:50:00 updated: 2020-09-28 16:30:00 Phoenix创建索引源码过程 org.apache.phoenix.index.Inde ...
随机推荐
- Java基础知识及学习规划【图】
结构图:
- 前后台得到WEB应用的名称
前台得到当前应用的名称: ${ pageContext.request.contextPath } 后台得到当前应用的名称: request.getServletContext().getContex ...
- 消息队列——ActiceMQ
1.下载apache-activemq-5.xx.x,\bin\win64目录下运行activemq.bat.之后可进入管理员界面http://localhost:8161/admin,账号密码均为a ...
- jQuery中height()不能精确计算的问题
jQuery中关于高度的计算有三个方法:outerHeight().innerHeight().height() outerHeight():获取元素集合中第一个元素的当前计算高度值,包括paddin ...
- 使用boost.python封装C++库
使用boost.python封装C++库 C++以高性能著称,但是编写较为复杂.而简洁是Python的强项.如果能珠联璧合,就能发挥两家之长.本文尝试用boost库的python模块封装C++ 前期准 ...
- __http原理__HTTP 协议简介
HTTP 协议通信流程 超文本 除了文本以外,还有其他数据类型的内容 HTTP 协议 指计算机网络通信中 两台计算机之间所必须遵守的规定或规则 Hypertext Transport Protocol ...
- jdk1.8的环境下打包成jdk1.6
第一步: 选中项目 ------->properties 第二部:(修改java build path和java conpiler) 第三部:需要打包成jdk6的jar包,就改成jre6. 注意 ...
- js表单提交到后台对象接收
$.extend({ StandardPost:function(url,args){ var form = $("<form method='post' target='_blank ...
- MySql 主从复制 mysql-proxy实现读写分离
1.安装和配置Docker 服务器版本阿里云CentOS7.4 docker版本18.06.0-ce docker安装步骤https://docs.docker.com/install/linux/d ...
- NetBeans配置subli
NetBeans主题设置: ①.去https://netbeansthemes.com/rank/网址下载喜欢的主题 ②.然后打开NetBeans-->工具->选项->外观-> ...