1 public BigDecimal(char[] in, int offset, int len, MathContext mc) {// 使用字符数组的构造方法,一般我们推荐使用的是一String类为参数的构造以保证精度不会丢失
2 // protect against huge length.
3 if (offset + len > in.length || offset < 0)// 索引偏移量不能为负数且索引偏移量与使用的字符数之和不能超过字符数组的长度,否则抛出数字格式化异常
4 throw new NumberFormatException("Bad offset or len arguments for char[] input.");
5 // This is the primary string to BigDecimal constructor; all
6 // incoming strings end up here; it uses explicit (inline)
7 // parsing for speed and generates at most one intermediate
8 // (temporary) object (a char[] array) for non-compact case.
9
10 // Use locals for all fields values until completion
11 int prec = 0; // record precision value
12 int scl = 0; // record scale value
13 long rs = 0; // the compact value in long
14 BigInteger rb = null; // the inflated value in BigInteger,以上定义了4个变量用于最后对成员变量赋值使用,若值未改变则世界使用当前默认值
15 // use array bounds checking to handle too-long, len == 0,
16 // bad offset, etc.
17 try {
18 // handle the sign,首先处理第一个字符,因其可能为符号位需要特殊处理
19 boolean isneg = false; // assume positive,先假定为正数,因此定义一个标志位isneg为false
20 if (in[offset] == '-') {
21 isneg = true; // leading minus means negative
22 offset++;
23 len--;// 若第一个字符为'-'号,表明该数值为负数,标志位isneg为true,同时将索引偏移量自增,将需要的字符个数自减
24 } else if (in[offset] == '+') { // leading + allowed
25 offset++;
26 len--;// 若第一个字符为'+'号,则不需要改变标志位,将索引偏移量自增,将需要的字符个数自减
27 }
28
29 // should now be at numeric part of the significand,接下来处理数组的数值部分字符
30 boolean dot = false; // true when there is a '.',首先定义小数点标志位dot,默认为false
31 long exp = 0; // exponent,定义指数值exp为0
32 char c; // current character,定义当前字符c,用于循环使用
33 boolean isCompact = (len <= MAX_COMPACT_DIGITS);// MAX_COMPACT_DIGITS为常量18,用于判断最终数值的数字位数,若小于等于18,则该数值的数值部分(不考虑小数点)可以使用long类型表示
34 // integer significand array & idx is the index to it. The array
35 // is ONLY used when we can't use a compact representation.
36 int idx = 0;
37 if (isCompact) {// len小于等于18
38 // First compact case, we need not to preserve the character
39 // and we can just compute the value in place.
40 for (; len > 0; offset++, len--) {// 开启循环取出字符数组中的数值
41 c = in[offset];// c代表当前字符
42 if ((c == '0')) { // c为'0'
43 if (prec == 0)// 若有效数位为0,则赋值为1(刚开始觉得这里有问题,如果第一个字符就是0则有效位数不应该自增才对,这里其实与第二个if语句体对应,且往下看)
44 prec = 1;
45 else if (rs != 0) {// 若有效数位不为0,rs也为0,将rs变为原值的10倍(即上升一个进位制),并将有效数位自增
46 rs *= 10;
47 ++prec;
48 } // else digit is a redundant leading zero
49 if (dot)// 若之前循环出现过字符'.'也就是小数点,则将有效小数位自增
50 ++scl;
51 } else if ((c >= '1' && c <= '9')) { // 若c为数值1~9,计算字符的实际映射的数值并赋值给digit变量
52 int digit = c - '0';
53 if (prec != 1 || rs != 0)// 不执行此步骤出现的情况:字符数组前几位均为字符'0',此时出现第一个数值字符时有效数值位数不需要改变(因为在这种情况下,在第一次出现'0'时已经提前增加了有效数位prec的值)
54 ++prec; // prec unchanged if preceded by 0s,这一个与上边的疑问对应,若在第一个数值字符出现之前有多个'0'字符出现,则有效位数一定为1,此时当前数值字符出现时我们不改变prec的值
55 rs = rs * 10 + digit;// 十进制计算数值
56 if (dot)
57 ++scl;
58 } else if (c == '.') { // 当前字符为'.'小数点
59 // have dot
60 if (dot) // two dots,之前循环已经存在一个小数点则目前有两个则抛出异常
61 throw new NumberFormatException();
62 dot = true;
63 } else if (Character.isDigit(c)) { // 字符是其他类型的数字(Unicode编码的非阿拉伯数字)
64 int digit = Character.digit(c, 10);// 获取字符所映射的数值,一下步骤与上面的分析是一样的
65 if (digit == 0) {
66 if (prec == 0)
67 prec = 1;
68 else if (rs != 0) {
69 rs *= 10;
70 ++prec;
71 } // else digit is a redundant leading zero
72 } else {
73 if (prec != 1 || rs != 0)
74 ++prec; // prec unchanged if preceded by 0s
75 rs = rs * 10 + digit;
76 }
77 if (dot)
78 ++scl;
79 } else if ((c == 'e') || (c == 'E')) {// 若当前字符是'e'或'E',则代表科学计数法
80 exp = parseExp(in, offset, len);// 解析字符'e'或'E'之后的指数值后,若通过校验则结束循环
81 // Next test is required for backwards compatibility
82 if ((int) exp != exp) // overflow,溢出(已经超出int的表数范围)则抛出格式化异常,一般指数也不会这么大int可以大概表数正负21亿
83 throw new NumberFormatException();
84 break; // [saves a test]
85 } else {// 当前字符不是数值或者指数标识符抛出异常
86 throw new NumberFormatException();
87 }
88 }
89 if (prec == 0) // no digits found,循环结束后若一个数值(包括'0')也没找到则抛出格式化异常,
90 throw new NumberFormatException();
91 // Adjust scale if exp is not zero.
92 if (exp != 0) { // had significant exponent,科学计数法表达式指数不为0则需要调整有效小数位数
93 scl = adjustScale(scl, exp);// 特别的有效小数位数可以为负数
94 }
95 rs = isneg ? -rs : rs;// 应用数值符号标志位恢复正负
96 int mcp = mc.precision;// 获取MathContext上下文的有效位数
97 int drop = prec - mcp; // prec has range [1, MAX_INT], mcp has range [0, MAX_INT];
98 // therefore, this subtract cannot overflow
99 if (mcp > 0 && drop > 0) { // do rounding,若上下文的有效位数>0,且解析出的数值的有效位数大于上下文的有效位数则需要舍入
100 while (drop > 0) {
101 scl = checkScaleNonZero((long) scl - drop);// 根据原有效小数位数与需要舍去的位数对有效小数位数进行修正
102 rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);// 根据舍入模式与需要舍入的位数对rs进行舍入处理
103 prec = longDigitLength(rs);// longDigitLength()方法获取long类型数值rs的有效数字位数
104 drop = prec - mcp;// 重新计算需要舍去的位数,若大于0则继续循环
105 }
106 }
107 } else {// 若需要解析的数值位数(包括小数点与指数表达式大于18)
108 char coeff[] = new char[len];// 定义缓存字符数组coeff,长度等于需要解析的字符数量
109 for (; len > 0; offset++, len--) {
110 c = in[offset];// 获取当前字符
111 // have digit
112 if ((c >= '0' && c <= '9') || Character.isDigit(c)) {// 当前字符为数字
113 // First compact case, we need not to preserve the character
114 // and we can just compute the value in place.
115 if (c == '0' || Character.digit(c, 10) == 0) {
116 if (prec == 0) {// 当前字符为0且当前有效位数为0(表明是第一次解析出'0'字符),则保存至缓存字符数组
117 coeff[idx] = c;
118 prec = 1;// 将有效位数置为1,(即若第一字符为'0'也将计入有效数位),但是idx索引并不更新
119 } else if (idx != 0) {// 此时表明解析出的0是"中间"的'0'字符,此时将保存至缓存字符数组
120 coeff[idx++] = c;// 更新缓存数组的索引
121 ++prec;// 更新有效位数
122 } // 其实还存在第三种情况,即继首位为0之后连续存在多个0,此时什么也不做(不保存也不更新有效位数与缓存索引)
123 } else {// 当前字符是不为'0'的数字
124 if (prec != 1 || idx != 0)// 不执行此步骤唯一的情况是:首位或其后续连续几位为'0',则此时prec与idx都是0(这与上面的疑问处的处理是一样的,此时不增加有效位数)
125 ++prec; // prec unchanged if preceded by 0s
126 coeff[idx++] = c;// 只要出现非'0'数字都要保存至缓存字符数组
127 }
128 if (dot)// 若之前循环已经出现过'.'小数点,则有效小数位数自增,并且结束本次循环
129 ++scl;
130 continue;
131 }
132 // have dot
133 if (c == '.') {// 小数点,与上边的处理相同
134 // have dot
135 if (dot) // two dots
136 throw new NumberFormatException();
137 dot = true;
138 continue;
139 }
140 // exponent expected
141 if ((c != 'e') && (c != 'E'))// 若当前字符既不是数字也不是小数点也不是指数标志,则抛出数字格式化异常
142 throw new NumberFormatException();
143 exp = parseExp(in, offset, len);// 否则解析指数表达式的值,方法与上边相同
144 // Next test is required for backwards compatibility
145 if ((int) exp != exp) // overflow,指数溢出抛出异常
146 throw new NumberFormatException();
147 break; // [saves a test]
148 }
149 // here when no characters left
150 if (prec == 0) // no digits found,为解析出数值则抛出异常
151 throw new NumberFormatException();
152 // Adjust scale if exp is not zero.
153 if (exp != 0) { // had significant exponent,指数不为0则需要调整有效小数位数
154 scl = adjustScale(scl, exp);// 特别的有效小数位数可以为0(可以尝试一下,存在指数表达式时)
155 }
156 // Remove leading zeros from precision (digits count)
157 rb = new BigInteger(coeff, isneg ? -1 : 1, prec);// 根据解析出来的字符缓存数组构建BigInteger对象,这个里边大有文章,下片源码解析在进行分析
158 rs = compactValFor(rb);// 根据创建的BigInteger对象获取该对象所表示的数值(若可以表示使用long类型实际表示,否则使用long类型的最小值表示,例如超出表数范围)
159 int mcp = mc.precision;// 获取MathContext上下文中的有效小数位数(这个才是最终的有效小数位数,可以对原生的BigDecimal对象中的值的有效位数进行修改)
160 if (mcp > 0 && (prec > mcp)) {// 这个与上面的代码分析差不多,即MathContext中的有效位数要小于原生生成的BigDecimal对象中值的有效位数,需要进行舍入操作
161 if (rs == INFLATED) {// 若rs表示的数值是long类型的最小值(即该BigInteger的数值已经超出long类型的表数范围)
162 int drop = prec - mcp;// 获取需要舍去的有效位数
163 while (drop > 0) {// 循环舍去直到drop小于等于0
164 scl = checkScaleNonZero((long) scl - drop);// 若原有效小数位数减去舍去的位数,超过int的表数范围则抛出异常
165 rb = divideAndRoundByTenPow(rb, drop, mc.roundingMode.oldMode);// 进行舍去,见

BigDecimal源码的更多相关文章

  1. JDK8 BigDecimal API-创建BigDecimal源码浅析三

    第三篇 先介绍以BigInteger为构造参数的构造器 public BigDecimal(BigInteger val) {// 根据BigInteger创建BigDecimal对象 scale = ...

  2. JDK8 BigDecimal API-创建BigDecimal源码浅析二

    第二篇,慢慢来 根据指数调整有效小数位数 // 上一篇由字符串创建BigDecimal代码中,有部分代码没有给出,这次补上 // 这个是当解析字符数组时存在有效指数时调整有小小数位数方法 privat ...

  3. Solr DIH JDBC 源码解析

    Solr DIH 源码解析 DataImportHandler.handleRequestBody()中的importer.runCmd(requestParams, sw) if (DataImpo ...

  4. 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析

    项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...

  5. MyBatis源码分析(2)—— Plugin原理

    @(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...

  6. 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)>为大家介绍了mybatis中别名的使用,以及其源码.本篇将为大家介绍TypeH ...

  7. 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, ...

  8. 【Pig源码分析】谈谈Pig的数据模型

    1. 数据模型 Schema Pig Latin表达式操作的是relation,FILTER.FOREACH.GROUP.SPLIT等关系操作符所操作的relation就是bag,bag为tuple的 ...

  9. JFinal 源码分析 [DB+ActiveRecord]

    我记得以前有人跟我说,“面试的时候要看spring的源码,要看ioc.aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇文章一下, ...

随机推荐

  1. Codeforces 513D2 Constrained Tree

    Constrained Tree 没写出来好菜啊啊. 首先根据输入我们能算出某些节点的左儿子的范围, 右儿子的范围(此时并不准确) 然后我们在划分u这个节点的时候我们从左右开始用树状数组check每一 ...

  2. SpringBoot使用Nacos服务发现

    本文介绍SpringBoot应用使用Nacos服务发现. 上一篇文章介绍了SpringBoot使用Nacos做配置中心,本文介绍SpringBoot使用Nacos做服务发现. 1.Eureka闭源 相 ...

  3. BigDecimal 准确的 double , float 计算

    public class ArithUtil { private static final int DEF_DIV_SCALE = 10; private ArithUtil() { } /** * ...

  4. Docker操作笔记(一)使用镜像

    使用镜像 一)获取镜像 从Docker镜像仓库获取命令的格式是: docker pull [选项] [Docker Registry 地址[:端口号]] 仓库名[:标签] 具体的选项可以通过docke ...

  5. fflush()函数:更新缓冲区

    fflush()的作用是用来刷新缓冲区: fflush(stdin)刷新标准输入缓冲区,把输入缓冲区里的东西丢弃:stdin是standard input的缩写,即标准输入,一般是指键盘:标准输入缓冲 ...

  6. ubantu16.04安装ns2.34 错误

    把ns2.34解压缩之后,sudo ./install 出现的错误: 错误一:安装NS2.34过程中出现如下的错误:tools/ranvar.cc: In member function ‘virtu ...

  7. Ganglia 调试技巧

    转自:http://blog.csdn.net/xxd851116/article/details/25109043 Gmond # 检查Gmond服务是否正在运行,发出如下命令:ps aux | g ...

  8. AJAX-快速上手(四个步骤)

    ## 1, ajax ajax是使用js进行在不重新加载页面的情况下,使得页面局部刷新.而传统的页面加载即需要,重新加载整个页面.它的加载是异步进行的,即在加载的同时,页面的其他部分可以正常使用,不会 ...

  9. (AB)Codeforces Round #528 (Div. 2, based on Technocup 2019 Elimination Round

    A. Right-Left Cipher time limit per test 1 second memory limit per test 256 megabytes input standard ...

  10. apt下载open-jdk8报错add-apt-repository: command not found

    今天下载jdk8报错 在Ubuntu下,时不时会有这个错误的. add-apt-repository: command not found sudo apt-get install software- ...