【实际开发11】- 统计 / 科学计算 - 1

目录

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 就可以搞定了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值