【问题标题】:BigDecimal summary statisticsBigDecimal 汇总统计
【发布时间】:2018-08-02 04:18:48
【问题描述】:

我有一个 BigDecimal 列表。

List<BigDecimal> amounts = new ArrayList<>()

如何使用 Java 8 流获得上述列表的摘要统计信息,而不会丢失 BigDecimal 的多达 3-4 位小数的精度?

【问题讨论】:

    标签: java java-8 java-stream bigdecimal


    【解决方案1】:

    我为this answer 的通用汇总统计收集器创建了一个BigDecimal 特化,它允许对其进行扩展以支持求和,因此还可以计算平均值:

    /**
     * Like {@code DoubleSummaryStatistics}, {@code IntSummaryStatistics}, and
     * {@code LongSummaryStatistics}, but for {@link BigDecimal}.
     */
    public class BigDecimalSummaryStatistics implements Consumer<BigDecimal> {
    
        public static Collector<BigDecimal,?,BigDecimalSummaryStatistics> statistics() {
            return Collector.of(BigDecimalSummaryStatistics::new,
                BigDecimalSummaryStatistics::accept, BigDecimalSummaryStatistics::merge);
        }
        private BigDecimal sum = BigDecimal.ZERO, min, max;
        private long count;
    
        public void accept(BigDecimal t) {
            if(count == 0) {
                Objects.requireNonNull(t);
                count = 1;
                sum = t;
                min = t;
                max = t;
            }
            else {
                sum = sum.add(t);
                if(min.compareTo(t) > 0) min = t;
                if(max.compareTo(t) < 0) max = t;
                count++;
            }
        }
        public BigDecimalSummaryStatistics merge(BigDecimalSummaryStatistics s) {
            if(s.count > 0) {
                if(count == 0) {
                    count = s.count;
                    sum = s.sum;
                    min = s.min;
                    max = s.max;
                }
                else {
                    sum = sum.add(s.sum);
                    if(min.compareTo(s.min) > 0) min = s.min;
                    if(max.compareTo(s.max) < 0) max = s.max;
                    count += s.count;
                }
            }
            return this;
        }
    
        public long getCount() {
            return count;
        }
    
        public BigDecimal getSum()
        {
          return sum;
        }
    
        public BigDecimal getAverage(MathContext mc)
        {
          return count < 2? sum: sum.divide(BigDecimal.valueOf(count), mc);
        }
    
        public BigDecimal getMin() {
            return min;
        }
    
        public BigDecimal getMax() {
            return max;
        }
    
        @Override
        public String toString() {
            return count == 0? "empty": (count+" elements between "+min+" and "+max+", sum="+sum);
        }
    }
    

    它可以类似于DoubleSummaryStatistics对应物使用,就像

    BigDecimalSummaryStatistics bds = list.stream().collect(BigDecimalSummaryStatistics.statistics());
    

    作为一个完整的例子:

    List<BigDecimal> list = Arrays.asList(BigDecimal.ZERO, BigDecimal.valueOf(-2), BigDecimal.ONE);
    BigDecimalSummaryStatistics bds = list.stream().collect(BigDecimalSummaryStatistics.statistics());
    System.out.println(bds);
    System.out.println("average: "+bds.getAverage(MathContext.DECIMAL128));
    
    3 elements between -2 and 1, sum=-1
    average: -0.3333333333333333333333333333333333
    

    【讨论】:

    • 看来你可以用BigDecimal.max().min()代替if(…compareTo(…))…
    • @DidierL 确实,这些适用于BigDecimal。此代码使用compareTo 变体,因为它源自链接的通用解决方案,我认为,我保持这种方式,因为它更容易适应其他元素类型。这让我觉得……将maxmin 作为default 方法提供给所有Comparables 是个好主意……
    • 我认为最好为没有它们的类型编写自定义 min() / max() 方法,但我理解你的意思?(我想知道他们是否考虑过将它们添加到Comparator 界面)
    【解决方案2】:

    您可以将 Java Streams 与 Eclipse Collections 一起使用,它有一个 BigDecimalSummaryStatistics 类:

    List<BigDecimal> amounts = 
            Lists.mutable.with(BigDecimal.ONE, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ONE);
    
    BigDecimalSummaryStatistics stats =
            amounts.stream().collect(Collectors2.summarizingBigDecimal(each -> each));
    
    Assert.assertEquals(BigDecimal.ZERO, stats.getMin());
    Assert.assertEquals(BigDecimal.TEN, stats.getMax());
    Assert.assertEquals(BigDecimal.valueOf(12L), stats.getSum());
    Assert.assertEquals(BigDecimal.valueOf(3L), stats.getAverage());
    Assert.assertEquals(4L, stats.getCount());
    

    注意:我是 Eclipse Collections 的提交者

    【讨论】:

      【解决方案3】:

      如果您愿意使用第三方库(与 Java 8 流兼容),您可以使用jOOλ,您可以使用它来编写:

      Tuple5<
          Long, 
          Optional<BigDecimal>, 
          Optional<BigDecimal>, 
          Optional<BigDecimal>, 
          Optional<BigDecimal>
      > tuple
      amounts.stream()
             .collect(Tuple.collectors(
                 Agg.sum(),
                 Agg.count(),
                 Agg.avg(),
                 Agg.<BigDecimal>min(),
                 Agg.<BigDecimal>max()
             ));
      

      这不会导致精度损失,但可能比聚合双精度数要慢得多

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-10
        • 1970-01-01
        • 1970-01-01
        • 2019-03-19
        • 1970-01-01
        • 2017-02-25
        • 2023-03-24
        • 1970-01-01
        相关资源
        最近更新 更多