第二篇,慢慢来

根据指数调整有效小数位数

    // 上一篇由字符串创建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源码浅析二的更多相关文章

  1. ReentrantLock和condition源码浅析(二)

    转载请注明出处... 接着上一篇的ReentrantLock和condition源码浅析(一),这篇围绕着condition 一.condition的介绍 在这里为了作对比,引入Object类的两个方 ...

  2. SparkConf加载与SparkContext创建(源码阅读二)

    紧接着昨天,我们继续开搞了啊.. 1.下面,开始创建BroadcastManager,就是传说中的广播变量管理器.BroadcastManager用于将配置信息和序列化后的RDD.Job以及Shuff ...

  3. LinkedList类源码浅析(二)

    1.上一节介绍了LinkedList的几个基本的方法,其他方法类似,就不一一介绍: 现在再来看一个删除的方法:remove(Object o) remove方法接受一个Object参数,这里需要对参数 ...

  4. ArrayList类源码浅析(二)

    1.removeAll(Collection<?> c)和retainAll(Collection<?> c)方法 第一个是从list中删除指定的匹配的集合元素,第二个方法是用 ...

  5. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  6. Android开发之Theme、Style探索及源码浅析

    1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...

  7. CountDownLatch源码浅析

    Cmd Markdown链接 CountDownLatch源码浅析 参考好文: JDK1.8源码分析之CountDownLatch(五) Java并发之CountDownLatch源码分析 Count ...

  8. Bytom侧链Vapor源码浅析-节点出块过程

    Bytom侧链Vapor源码浅析-节点出块过程 在这篇文章中,作者将从Vapor节点的创建开始,进而拓展讲解Vapor节点出块过程中所涉及的源码. 做为Vapor源码解析系列的第一篇,本文首先对Vap ...

  9. Phoenix创建索引源码过程

    date: 2020-09-27 13:50:00 updated: 2020-09-28 16:30:00 Phoenix创建索引源码过程 org.apache.phoenix.index.Inde ...

随机推荐

  1. Swagger-概述

    前言 Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件.本文简单介绍了在项目中集成swagger的方法和一些常见问题.如果想深入分析项目源码,了解更多内容,见参考资料. S ...

  2. Yarn集群的搭建、Yarn的架构和WordCount程序在集群提交方式

    一.Yarn集群概述及搭建 1.Mapreduce程序运行在多台机器的集群上,而且在运行是要使用很多maptask和reducertask,这个过程中需要一个自动化任务调度平台来调度任务,分配资源,这 ...

  3. HttpSession与JSESSIONID的"盗用"

    https://blog.csdn.net/qq1437715969/article/details/75331652

  4. vue实现数据双向绑定的原理

    一.知识准备Object.defineProperty( )方法可以直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象.Object.defineProperty(obj,pr ...

  5. es6中的...三个点

    ...是es6中新添加的操作符,可以称为spread或rest 定义一个数组 let name=['小红','小明','小白']; 我们在控制台输出   console.log(name); 结果: ...

  6. PBRT笔记(3)——KD树

    茎节点与叶子节点 茎节点与叶子节点皆适用KdAccelNode来表示 注意:这里使用了匿名union union有个特性:内部类型共用一段内存,且大小为内部最大类型的大小. struct KdAcce ...

  7. 浅析uWSGI、uwsgi、wsgi

    WSGI协议 首先弄清下面几个概念: WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web ...

  8. react-native run-android时 SDK location not found.报错

    报错 原因 缺少local.properties文件(SDK location) 解决 方法一:在android Studio中打开项目android目录,会自动创建local.properties文 ...

  9. Java-IO流之输入输出流基础示例

    一.理论: 1.什么是输入输出? 输入输出的对象是数据,数据的存储区域是磁盘或者光盘等设备,我们知道还有一个存储数据的空间----内存,其中磁盘的速度比较慢,内存的速度比较快,把数据读入内存的动作称作 ...

  10. css边框渐变

    在实际开发中,我们经常遇见边框需要背景渐变的实现要求,那么如何去实现呢,今天给大家分享依稀几种情况 1.直角的背景渐变 <!DOCTYPE html> <html lang=&quo ...