java封装BigDecimal进行高精度计算

14 阅读4分钟
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();
    }

}