java封装BigDecimal进行高精度计算

kotlin 复制代码
package com.wis.leading.common.util;

import cn.hutool.core.util.NumberUtil;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;


/**
 * 此类封装了 {@link BigDecimal},提供了一套流畅(Fluent)的链式API,用于执行高精度的算术运算。旨在简化 {@link BigDecimal} 的使用。
 * <p>
 * 此类为可变对象(Mutable)!</b> 所有算术运算方法(如 {@link #add}, {@link #mul})都会修改其内部状态(即 {@code this} 对象)。
 * 由于是可变对象,Num类本身不是线程安全的。 如果在多线程环境下共享同一个 Num 实例并进行写操作, 必须由外部进行同步控制(如使用 synchronized 或 Lock)
 * <p>
 * 意外的副作用 (Unintended Side Effects) 当一个 {@code Num} 对象被多个引用持有,通过任何一个引用修改其状态,都会影响到所有其他引用。
 * <p>
 * 严禁</b>将 {@code Num} 对象用作 {@link java.util.HashSet} 的元素或 {@link java.util.HashMap} 的键。
 * 如果 {@code Num} 对象在放入集合后其值被修改,其 {@code hashCode()} 也会改变,导致集合无法再找到该对象。因为这些集合依赖对象的 {@code hashCode()} 和 {@code equals()}。
 */
@Slf4j
public final class Num {

    private BigDecimal value;

    private Num(BigDecimal value) {
        this.value = value;
    }

    // 创建默认值
    public static Num of(Number value) {
        Objects.requireNonNull(value);
        return new Num(NumberUtil.toBigDecimal(value));
    }

    // 创建默认值
    public static Num of(Num value) {
        return of(value.get());
    }

    // 创建默认值
    public static Num of(String value) {
        return of(new BigDecimal(value));
    }

    // 创建默认值等于0
    public static Num zero() {
        return of(BigDecimal.ZERO);
    }

    // 加
    public Num add(Number... toAdd) {
        for (Number item : toAdd) {
            value = NumberUtil.add(value, item);
        }
        return this;
    }

    // 加
    public Num add(Num... toAdd) {
        for (Num item : toAdd) {
            value = NumberUtil.add(value, item.get());
        }
        return this;
    }

    // 加
    public Num add(String... toAdd) {
        for (String item : toAdd) {
            value = NumberUtil.add(value, new BigDecimal(item));
        }
        return this;
    }


    // 减
    public Num sub(Number... toSubtract) {
        for (Number item : toSubtract) {
            value = NumberUtil.sub(value, item);
        }
        return this;
    }

    // 减
    public Num sub(Num... toSubtract) {
        for (Num item : toSubtract) {
            value = NumberUtil.sub(value, item.get());
        }
        return this;
    }

    // 减
    public Num sub(String... toSubtract) {
        for (String item : toSubtract) {
            value = NumberUtil.sub(value, new BigDecimal(item));
        }
        return this;
    }

    // 乘
    public Num mul(Number... toMultiply) {
        for (Number item : toMultiply) {
            value = NumberUtil.mul(value, item);
        }
        return this;
    }

    // 乘
    public Num mul(Num... toMultiply) {
        for (Num item : toMultiply) {
            value = NumberUtil.mul(value, item.get());
        }
        return this;
    }

    // 乘
    public Num mul(String... toMultiply) {
        for (String item : toMultiply) {
            value = NumberUtil.mul(value, new BigDecimal(item));
        }
        return this;
    }

    // 除
    public Num div(Number... toDivisor) {
        for (Number item : toDivisor) {
            value = NumberUtil.div(value, item);
        }
        return this;
    }

    // 除
    public Num div(Num... toDivisor) {
        for (Num item : toDivisor) {
            value = NumberUtil.div(value, item.get());
        }
        return this;
    }

    // 除
    public Num div(String... toDivisor) {
        for (String item : toDivisor) {
            value = NumberUtil.div(value, new BigDecimal(item));
        }
        return this;
    }


    // ============= 安全除法方法开始 =============

    /**
     * 安全除法 - 当除数为0时,返回0
     *
     * @param toDivisor 除数数组
     * @return Num实例
     */
    public Num divSafe(Number... toDivisor) {
        for (Number item : toDivisor) {
            if (item == null || Num.of(item).isZero()) {
                log.warn("安全除法检测到无效除数 - 当前值:{}, 除数:{} (除数为null或0,将返回0)", this.value, item);
                value = BigDecimal.ZERO;
                return this;
            }
            value = NumberUtil.div(value, item);
        }
        return this;
    }

    /**
     * 安全除法 - 当除数为0时,返回0
     *
     * @param toDivisor 除数数组
     * @return Num实例
     */
    public Num divSafe(Num... toDivisor) {
        for (Num item : toDivisor) {
            if (item == null || item.isZero()) {
                log.warn("安全除法检测到无效除数 - 当前值:{}, 除数:{} (除数为null或0,将返回0)", this.value, item);
                value = BigDecimal.ZERO;
                return this;
            }
            value = NumberUtil.div(value, item.get());
        }
        return this;
    }

    /**
     * 安全除法 - 当除数为0时,返回0
     *
     * @param toDivisor 除数数组
     * @return Num实例
     */
    public Num divSafe(String... toDivisor) {
        for (String item : toDivisor) {
            if (item == null || Num.of(item).isZero()) {
                log.warn("安全除法检测到无效除数 - 当前值:{}, 除数:'{}' (除数为null或0,将返回0)", this.value, item);
                value = BigDecimal.ZERO;
                return this;
            }
            value = NumberUtil.div(value, new BigDecimal(item));
        }
        return this;
    }

    // ============= 安全除法方法结束 =============


    /**
     * 创建当前对象的副本(复制当前值)。
     * 由于此类是可变的,通过副本可以保留当前状态,避免后续操作影响原始对象。
     *
     * @return 一个新的 Num 实例,其值与当前对象相同
     */
    public Num copy() {
        return Num.of(this.value);
    }


    // 获取结果
    public BigDecimal get() {
        return value;
    }

    // 获取结果 保留指定位小数
    public BigDecimal get(int newScale) {
        return get().setScale(newScale, RoundingMode.HALF_UP);
    }

    // 获取结果
    public double getDoubleValue() {
        return get().doubleValue();
    }

    // 获取结果 保留指定位小数
    public double getDoubleValue(int newScale) {
        return get(newScale).doubleValue();
    }

    // ============= 比较方法 =============

    /**
     * 是否为0(数值判断)
     */
    public boolean isZero() {
        return compareTo(BigDecimal.ZERO) == 0;
    }

    /**
     * 大于
     *
     * @param other 要比较的数值
     * @return 如果当前值大于参数数值,返回true;否则返回false
     */
    public boolean gt(Number other) {
        return compareTo(other) > 0;
    }

    /**
     * 大于等于
     *
     * @param other 要比较的数值
     * @return 如果当前值大于或等于参数数值,返回true;否则返回false
     */
    public boolean gte(Number other) {
        return compareTo(other) >= 0;
    }

    /**
     * 小于
     *
     * @param other 要比较的数值
     * @return 如果当前值小于参数数值,返回true;否则返回false
     */
    public boolean lt(Number other) {
        return compareTo(other) < 0;
    }

    /**
     * 小于等于
     *
     * @param other 要比较的数值
     * @return 如果当前值小于或等于参数数值,返回true;否则返回false
     */
    public boolean lte(Number other) {
        return compareTo(other) <= 0;
    }

    /**
     * 等于
     *
     * @param other 要比较的数值
     * @return 如果当前值等于参数数值,返回true;否则返回false
     */
    public boolean eq(Number other) {
        return compareTo(other) == 0;
    }

    /**
     * 与另一个 Number 比较,返回 -1, 0, 1
     */
    public int compareTo(Number other) {
        Objects.requireNonNull(other, "比较对象不能为null");
        return value.compareTo(NumberUtil.toBigDecimal(other));
    }

    @Override
    public String toString() {
        return value.toPlainString();
    }

}
相关推荐
CodeSheep35 分钟前
OpenJDK 全面禁止 AI 生成代码!
前端·后端·程序员
IT_陈寒1 小时前
为什么我的Vue组件总是莫名其妙重渲染?
前端·人工智能·后端
东风破_9 小时前
danci 2:创建的单词书到底存在哪里?从 Supabase 一路理解 ORM、Drizzle 和 RLS
数据库·后端·node.js
东风破_10 小时前
danci 项目(三):从 JSON 数据到 AI Coding,真实项目里的数据清洗、Prompt 和工程规范
前端·后端·node.js
东风破_10 小时前
danci 项目(一):从需求到架构,一个单词学习系统为什么会这样设计
前端·后端·node.js
jay神12 小时前
【计算机毕业设计】基于SpringBoot的程序教学辅助系统
java·前端·vue.js·spring boot·后端·毕业设计·课程设计
2601_9620738115 小时前
Spring全面详解(基础版)
java·后端·spring
IT_陈寒15 小时前
Vite打包时的静态资源坑,我帮你踩过了
前端·人工智能·后端
考虑考虑15 小时前
kubectl命令
运维·后端·自动化运维
第五页的你15 小时前
ElasticSearch结合本地消息表实现搜索功能
后端