目录
1. sql 统计返回值为 null 时 , 赋值 为 : 0 ( return UI )
1. 手动 null 判断 , 进行 “0” 赋值
2. XxxxVO 展示对象 , 初始化时 , 赋值默认值 ( 待优化 )
2. 统计异常
1. 注意中间表数据的维护 ( 同步删除 / 避免手动删数据 )
3. 精度损失
1. Java 类型 float、double 用于计算会有 "精度丢失" 问题
2. BigDecimal 使用不当也有精度丢失的情况 , 如 : double 的构造方法
4. BigDecimal
1. BigDecimal 加减乘除
1. + - * /
2. BigDecimal 的比较大小
3. BigDecimal 写个 for 循环
2. BigDecimal 的常用方法 × 10
3. list 对象 BigDecimal 属性 : 最大值/最小值/总和/平均值 (Java8)
4. stream 对 list 集合中的bigdecimal 分组求和/均值/最大值/最小值
1. 新建接口 : ToBigDecimalFunction
2. 新建工具类 : CollectorsUtil
3. 新建实体类 : Person / 测试用例 / 开始测试
5. BigDecimal 的结果格式化
1. 将BigDecimal计算的结果 toString() 输出 , 非科学计数法的格式
1. sql 统计返回值为 null 时 , 赋值 为 : 0 ( return UI )
1. 手动 null 判断 , 进行 “0” 赋值
2. XxxxVO 展示对象 , 初始化时 , 赋值默认值 ( 待优化 )
2. 统计异常
1. 注意中间表数据的维护 ( 同步删除 / 避免手动删数据 )
3. 精度损失
在《Effective Java》这本书中也提到这个原则 , float 和 double 只能用来做科学计算或者是工程计算 , 在商业计算中我们要用 java.math.BigDecimal
1. Java 类型 float、double
用于计算会有 "精度丢失" 问题
保留两位小数 ( 精度损失问题 )
package com.hanchan.codeTest_2020_11;
/**
* @Author: menghuan
* @Date: 2021/2/3 9:50
*/
public class FloatAndDouble {
public static void main(String[] args) {
test1();
// test2();
}
private static void test1() {
double totalAmount = 0.09;
double feeAmount = 0.02;
double tradeAmount = totalAmount - feeAmount;
System.out.println(tradeAmount);
}
}
"H:\Idea\IntelliJ IDEA 2019.2\jbr\bin\java.exe"
0.06999999999999999
Process finished with exit code
浮点数可能丢失精度 , 浮点十进制数通常没有完全相同的二进制的表示形式 , 这是CPU所采用的浮点数据表示形式的副作用。为此 , 可能会有一些精度丢失 , 并且一些浮点运算可能会产生未知的结果。
浮点运算很少是精确的 , 只要是超过精度能表示的范围就会产生误差。所以 , 在使用float、double作精确运算的时候一定要特别小心 , 除非能容忍精度丢失 , 不然产生的误差也是会造成双方对账不一致的结果。
2. BigDecimal 使用不当也有精度丢失的情况 , 如 : double 的构造方法
BigDecimal适合更精度的运算 , 也提供了丰富的操作符类型 , 小数位控制 , 四舍五入规则等。
不过 , 使用BigDecimal不当也有精度丢失的情况 , 如double的构造方法:
BigDecimal(double val)
package com.hanchan.codeTest_2020_11;
import java.math.BigDecimal;
/**
* @Author: menghuan
* @Date: 2021/2/3 9:50
*/
public class FloatAndDouble {
public static void main(String[] args) {
test1();
System.out.println("==================BigDecimal不当也有精度丢失的情况 , 如double的构造方法==================");
test2();
System.out.println("====================金额运算尽量使用 BigDecimal(String val)进行运算====================");
test3();
}
private static void test1() {
double totalAmount = 0.09;
double feeAmount = 0.02;
double tradeAmount = totalAmount - feeAmount;
System.out.println(tradeAmount);
}
/**
* BigDecimal(double val) 精度丢失问题
* BigDecimal(String val) 推荐 ( 所以 , 一定要使用String的构造方法 )
*/
private static void test2() {
double totalAmount = 0.09;
double feeAmount = 0.02;
BigDecimal tradeAmount = new BigDecimal(totalAmount).subtract(new BigDecimal(feeAmount));
System.out.println(tradeAmount);
}
/**
* 金额运算尽量使用 BigDecimal(String val)进行运算
*/
private static void test3() {
double totalAmount = 0.09;
double feeAmount = 0.02;
BigDecimal tradeAmount = new BigDecimal(String.valueOf(totalAmount)).subtract(new BigDecimal(String.valueOf(feeAmount)));
System.out.println(tradeAmount);
}
}
"H:\Idea\IntelliJ IDEA 2019.2\jbr\bin\java.exe"
0.06999999999999999 ------------------BigDecimal不当也有精度丢失的情况 , 如double的构造方法------------------ 0.0699999999999999962529972918900966760702431201934814453125 --------------------金额运算尽量使用 BigDecimal(String val)进行运算-------------------- 0.07
Process finished with exit code 0
总结
-
金额运算尽量使用BigDecimal(String val)进行运算。
-
数据库存储金额 , 一般有整型和浮点型两种存储方式。如果是有汇率转换的 , 建议使用浮点数decimal进行存储 , 可以灵活的控制精度 , decimal直接对应java类型BigDecimal。当然 , 用整数存储分这种形式也可以 , 转账的时候单位为元而如果忘了转换分为元 , 那就悲剧了。
4. BigDecimal
参考:
你以为用了BigDecimal后,计算结果就一定精确了? 你以为用了BigDecimal后,计算结果就一定精确了
1. BigDecimal 加减乘除
在java 里面 , int 的最大值是:2147483647 , 现在如果想用比这个数大怎么办?
换句话说 , 就是数值较大 , 这时候就用到了 BigDecimal
1. + - * /
BigDecimal bignum1 = new BigDecimal("10");
BigDecimal bignum2 = new BigDecimal("5");
BigDecimal bignum3 = null;
//加法
bignum3 = bignum1.add(bignum2);
System.out.println("和 是:" + bignum3);
//减法
bignum3 = bignum1.subtract(bignum2);
System.out.println("差 是:" + bignum3);
//乘法
bignum3 = bignum1.multiply(bignum2);
System.out.println("积 是:" + bignum3);
//除法
bignum3 = bignum1.divide(bignum2);
System.out.println("商 是:" + bignum3);
{
"params": "{\"hw1\":\"39\",\"hw2\":\"37\",\"ηh\":\"1.2\",\"hz1\":\"120\",\"hz2\":\"100\"}",
"code": "import com.alibaba.fastjson.JSON; def toDo(vv){ def dd =null; vv = vv.split(\";\"); String a = vv[0];Map<String,String> map = JSON.parseObject(a);BigDecimal num1 = new BigDecimal(map.get(\"hw1\"));BigDecimal num2 = new BigDecimal(map.get(\"hw2\"));BigDecimal num3 = new BigDecimal(map.get(\"ηh\"));BigDecimal num4 = new BigDecimal(map.get(\"hz1\"));BigDecimal num5 = new BigDecimal(map.get(\"hz2\"));try { result1 = num1.subtract(num2);result2 = result1.multiply(num3);result3 = num4.subtract(num5);result4 = result2.divide(result3);dd = result4; }catch (Exception e){ dd = 0;} \r\n return \"$dd\"; }"
}
2. BigDecimal 的比较大小
3. BigDecimal 写个 for 循环
for (BigDecimal i = new BigDecimal("0"); i.compareTo(new BigDecimal("10")) != 1; i = i.add(new BigDecimal("1"))) {
System.out.print(i + "\t");
}
控制台打印的是从 0 到 10
2. BigDecimal 的常用方法 × 10
加 : add(BigDecima)
减 : subtract(BigDecimal)
乘 : multiply(BigDecimal)
除 : divide(BigDecimal)
乘方 : pow(int)
取绝对值 : abs()
取反 : negate()
对比 : compareTo(BigDecimal)
设置小数点精确度 : setScale(int)
设置保留小数点精确度并添加保留方式(直接加1或者四舍五入) : setScale(int , int)
3. list 对象 BigDecimal 属性 : 最大值/最小值/总和/平均值 (Java8)
@Slf4j
public class TestList {
@Test
public void test01() throws IOException {
// 数据源
User user1 = new User(2 , "Steven" , "@sun123" , new Date() , 2000.0 , new BigDecimal(2000));
User user2 = new User(3 , "Steven" , "@sun123" , new Date() , 3000.0 , new BigDecimal(3000));
User user3 = new User(4 , "Steven" , "@sun123" , new Date() , 4000.0 , new BigDecimal(4000));
List<User> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
userList.add(user3);
//求最大值
BigDecimal max = userList.stream().map(User::getWeight).max((x1 , x2) -> x1.compareTo(x2)).get();
//求最小值
BigDecimal min = userList.stream().map(User::getWeight).min((x1 , x2) -> x1.compareTo(x2)).get();
//求和
BigDecimal sum = userList.stream().map(User::getWeight).reduce(BigDecimal.ZERO , BigDecimal::add);
//求平均值
BigDecimal average = userList.stream().map(User::getWeight).reduce(BigDecimal.ZERO , BigDecimal::add).divide(BigDecimal.valueOf(userList.size()) , 2 , BigDecimal.ROUND_HALF_UP);
log.info(max.toString());
log.info(min.toString());
log.info(sum.toString());
log.info(average.toString());
}
}
输出结果:
10:43:59.260 [main] INFO TestList - 4000
10:43:59.267 [main] INFO TestList - 2000
10:43:59.267 [main] INFO TestList - 9000
10:43:59.267 [main] INFO TestList - 3000.00
4. stream 对 list 集合中的bigdecimal 分组求和/均值/最大值/最小值
Java8 原生只提供了summingInt、summingLong、summingDouble三种基础类型的方法 ,
想要对BigDecimal类型的数据操作需要自己新建工具类如下:
1. 新建接口 : ToBigDecimalFunction
@FunctionalInterface
public interface ToBigDecimalFunction<T> {
BigDecimal applyAsBigDecimal(T value);
}
2. 新建工具类 : CollectorsUtil
public class CollectorsUtil {
static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();
private CollectorsUtil() {
}
@SuppressWarnings("unchecked")
private static <I , R> Function<I , R> castingIdentity() {
return i -> (R) i;
}
static class CollectorImpl<T , A , R> implements Collector<T , A , R> {
private final Supplier<A> supplier;
private final BiConsumer<A , T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A , R> finisher;
private final Set<Characteristics> characteristics;
CollectorImpl(Supplier<A> supplier , BiConsumer<A , T> accumulator , BinaryOperator<A> combiner ,
Function<A , R> finisher , Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
}
CollectorImpl(Supplier<A> supplier , BiConsumer<A , T> accumulator , BinaryOperator<A> combiner ,
Set<Characteristics> characteristics) {
this(supplier , accumulator , combiner , castingIdentity() , characteristics);
}
@Override
public BiConsumer<A , T> accumulator() {
return accumulator;
}
@Override
public Supplier<A> supplier() {
return supplier;
}
@Override
public BinaryOperator<A> combiner() {
return combiner;
}
@Override
public Function<A , R> finisher() {
return finisher;
}
@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
}
//求和方法
public static <T> Collector<T , ? , BigDecimal> summingBigDecimal(ToBigDecimalFunction<? super T> mapper) {
return new CollectorImpl<>(
() -> new BigDecimal[]{BigDecimal.ZERO} ,
(a , t) -> { a[0] = a[0].add(mapper.applyAsBigDecimal(t)); } ,
(a , b) -> { a[0] = a[0].add(b[0]) ; return a; } ,
a -> a[0] , CH_NOID);
}
//求最大值
public static <T> Collector<T , ? , BigDecimal> maxBy(ToBigDecimalFunction<? super T> mapper) {
return new CollectorImpl<>(
() -> new BigDecimal[]{new BigDecimal(Long.MIN_VALUE)} ,
(a , t) -> { a[0] = a[0].max(mapper.applyAsBigDecimal(t)); } ,
(a , b) -> { a[0] = a[0].max(b[0]) ; return a; } ,
a -> a[0] , CH_NOID);
}
//求最小值
public static <T> Collector<T , ? , BigDecimal> minBy(ToBigDecimalFunction<? super T> mapper) {
return new CollectorImpl<>(
() -> new BigDecimal[]{new BigDecimal(Long.MAX_VALUE)} ,
(a , t) -> { a[0] = a[0].min(mapper.applyAsBigDecimal(t)); } ,
(a , b) -> { a[0] = a[0].min(b[0]) ; return a; } ,
a -> a[0] , CH_NOID);
}
//求平均值
public static <T> Collector<T , ? , BigDecimal> averagingBigDecimal(ToBigDecimalFunction<? super T> mapper , int newScale , int roundingMode) {
return new CollectorImpl<>(
() -> new BigDecimal[]{BigDecimal.ZERO , BigDecimal.ZERO} ,
(a , t) -> { a[0] = a[0].add(mapper.applyAsBigDecimal(t)); a[1] = a[1].add(BigDecimal.ONE); } ,
(a , b) -> { a[0] = a[0].add(b[0]) ; return a; } ,
a -> a[0].divide(a[1] , BigDecimal.ROUND_HALF_UP).setScale(newScale , roundingMode) , CH_NOID);
}
}
3. 新建实体类 : Person
/ 测试用例 / 开始测试
新建实体类Person
@Data
class Person{
private String sex;
private Integer age;
private BigDecimal score;
public Person(String sex , Integer age , BigDecimal score) {
this.sex = sex;
this.age = age;
this.score = score;
}
}
测试用例
List<Person> list = new ArrayList<>();
list.add(new Person("男" , 18 , new BigDecimal(100)));
list.add(new Person("男" , 19 , new BigDecimal(90)));
list.add(new Person("女" , 20 , new BigDecimal(80)));
list.add(new Person("女" , 20 , new BigDecimal(70)));
list.add(new Person("女" , 20 , null));
开始测试
//单条件筛选
//按照性别分组求分数总和
Map<String , BigDecimal> scoreCount = list.stream()
.filter(t -> t.getScore() != null)
.collect(Collectors.groupingBy(Person::getSex , CollectorsUtil.summingBigDecimal(Person::getScore)));
System.out.println("----按照性别分组求分数总和----");
scoreCount.forEach((k , v) -> System.out.println("key: " + k + " , " + "value: " + v));
//按照性别求分数平均值
Map<String , BigDecimal> scoreAvg = list.stream()
.filter(t -> t.getScore() != null)
.collect(Collectors.groupingBy(Person::getSex , CollectorsUtil.averagingBigDecimal(Person::getScore , 2)));
System.out.println("----按照性别求分数平均值----");
scoreAvg.forEach((k , v) -> System.out.println("key: " + k + " , " + "value: " + v));
//多条件筛选
//多条件筛选分组属性
private static String fetchGroupKey(Person p) {
return p.getAge() + "#" + p.getSex();
}
//按照性别年龄分组求分数总和
Map<String , BigDecimal> ageScoreCount = list.stream()
.filter(t -> t.getScore() != null)
.collect(Collectors.groupingBy(p -> fetchGroupKey(p) , CollectorsUtil.summingBigDecimal(Person::getScore)));
System.out.println("----按照性别年龄分组求分数总和----");
ageScoreCount.forEach((k , v) -> System.out.println("key: " + k + " , " + "value: " + v));
//按照性别年龄分组求分数平均值
Map<String , BigDecimal> ageScoreAvg = list.stream()
.filter(t -> t.getScore() != null)
.collect(Collectors.groupingBy(p -> fetchGroupKey(p) , CollectorsUtil.averagingBigDecimal(Person::getScore , 2)));
System.out.println("----按照性别年龄分组求分数平均值----");
ageScoreAvg.forEach((k , v) -> System.out.println("key: " + k + " , " + "value: " + v));
输出结果为:
----按照性别分组求分数总和----
key: 女 , value: 150
key: 男 , value: 190
----按照性别求分数平均值----
key: 女 , value: 75.00
key: 男 , value: 95.00
----按照性别年龄分组求分数总和----
key: 20#女 , value: 150
key: 19#男 , value: 90
key: 18#男 , value: 100
----按照性别年龄分组求分数平均值----
key: 20#女 , value: 75.00
key: 19#男 , value: 90.00
key: 18#男 , value: 100.00
参考自:
Java8关于BigDecimal的求和,求平均,最小,最大,支持分组_wohennis1的博客-CSDN博客_.summingbigdecimal
java8对list分组,速度起飞
5. BigDecimal 的结果格式化
1. 将BigDecimal计算的结果 toString() 输出 , 非科学计数法的格式
将 BigDecimal 计算的结果 toString()输出 , 不是按科学计数法的格式的 ,
如果想改成这种格式 , 可以使用 DecimalFormat 进行转换 ,
具体如下:
private static final String EXPR_PATTERN = "0.##########E0";
private static final String PATTERN = "0.##########";
private static final String INTEGER_MIN_VALUE_CHANGE_TO_EXPR = "10000000";
private static final String DECIMAL_MIN_VALUE_CHANGE_TO_EXPR = "0.0001";
/**
* Tpv loy.ouyang: jude number is able to convert to expr display
*/
private static boolean numberStringCanConvertToExpr(BigDecimal bd) {
if (bd == null) {
return false;
}
boolean result = false;
BigDecimal absDB = bd.abs();
if ((absDB.compareTo(new BigDecimal(DECIMAL_MIN_VALUE_CHANGE_TO_EXPR)) <= 0) ||
(absDB.compareTo(new BigDecimal(INTEGER_MIN_VALUE_CHANGE_TO_EXPR)) >= 0)) {
result = true;
}
if (absDB.compareTo(new BigDecimal(0)) == 0) {
result = false;
}
return result;
}
/**
* Tpv loy.ouyang: format string to expr to display
*/
public static String formatStringToExpr(BigDecimal bd) {
if (bd == null) {
return null;
}
DecimalFormat df = new DecimalFormat();
if (numberStringCanConvertToExpr(bd)) {
df.applyPattern(EXPR_PATTERN);
} else {
df.applyPattern(PATTERN);
}
return df.format(bd);
}
定义了超大数和超小数的显示PATTERN , 0.##########代表最多显示10位小数 , 并判定绝对值多大的数和绝对值多小的数需要转化位科学记数法 ,
最后用 DecimalFormat 就可以搞定了。