使用二分法求解一元N次方程的近似值

package cn.pzh.test;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * 求解一元n次方程的方法
 * 售价=成本价格/[1-(利率*(1-让利)]*数量+成本价格/[1-(利率*(1-让利)]*数量+...
 * 除了让利,其余已知,通过公式,倒推让利
 * 首先计算出二分查找的二分点,然后将二分点带入公式计算出计算售价,最后判定计算售价与输入售价间的关系
 * @author pengzh
 *
 */
public class EquationResult {

    /**
     * 设置最大的让例 
     */
    public static double MAX_PROFIT = 1000000;

    /**
     * 计算产品的让利前准备
     * @param productList 产品信息
     * @param targetPrice 售价
     * @return
     */
    public static double calculateProfitByBuyAmount(List<ProductInfo> productList, double targetPrice){

        /*
         * 此处可对传入的数据进行检验
         *     对售价进行增减操作
         *     
         * 也可以对于产品进行一个校验,如是否有值,是否数据合法,等等
         */

        // 行项目里最大的毛利率 
        double maxRate = 0;
        // 遍历产品表,对于取得最大的利率
        for(ProductInfo product : productList){
            if(product.getRate()>maxRate){
                maxRate = product.getRate();
            }
        }
        // 开始计算一下让利数
        return calculateX(targetPrice, productList, maxRate, 2, 6);
    }

    /**
     * 计算让利
     * @param productList 参与计算的产品信息
     * @param targetPrice 售价
     * @param maxRate 最大的利率
     * @param errorRange 合计值的误差范围小数位数
     * @param numericPrecision 保留的小数位
     * @return
     */
    public static double calculateX(double targetPrice, List<ProductInfo> productList, 
            double maxRate, int errorRange, int numericPrecision){

        // 获取最小单位,如果numericPrecision为4,则为0.0001
        double unit = formatNumber(1/Math.pow(10, numericPrecision), numericPrecision);

        // 最小X的值:为了保证 分母大于0,我们搞根据最大的目标毛利率计算出最小X的值
        // 如果利率为0.3时:(-(1-0.3)/0.3) 约为 -2.3333...
        double minX = formatNumber(( -(1 - maxRate) / maxRate), numericPrecision);
        // 在计算出的最小的值上加上最小单位,防止四舍五入
        minX = minX + unit;
        // 让例为0 时的金额,不进行让利的进行
        // 售价=成本价格/(1-利率)*数量+成本价格/(1-利率)*数量+...
        //100/0.7*12+2/0.6*1
        double amount0 = getTotalRowAmountByProfit(productList, 0, errorRange);

        // 获取开始二分法处理的开始点和结束点
        double startX = 0;
        double endX = 0;

        // 金额的误差范围,校验成功证明让利为0
        if(checkAmount(targetPrice, amount0, errorRange)){
            return 0;
            // 如果输入售价大于让利为0的售价
        }else if(targetPrice > amount0){
            // 目标售价 比 折让比例为0时计算出来的金额还大
            startX = minX;
            endX = 0;
            // 如果输入售价小于让利为0的售价
        }else if(targetPrice < amount0){
            // 目标金额 比 折让比例为0时 计算出来的金额小,先快速定位出一个折让的区间
            // 0~1之间是概率最高的 所以先算1
            double amount1 = getTotalRowAmountByProfit(productList, 1, errorRange);
            if(checkAmount(targetPrice, amount1, errorRange)){
                return 1;
            }else if(targetPrice > amount1){
                // 让利在0和1之间
                startX = 0;
                endX = 1;
            }else{
                // 在1以后寻找有效的区间
                startX = 1;
                endX = 2;
                double amountEnd;
                while(true){
                    amountEnd =  getTotalRowAmountByProfit(productList, endX, errorRange);
                    if(targetPrice >=  amountEnd){
                        break;
                    }else{
                        // 判定是否大于最大的让利值
                        if(startX >= MAX_PROFIT){
                            System.out.println("超出计算的上限,未能正确计算!");
                            return 0;
                        }
                        startX = endX;
                        endX = Math.min(endX * 2,MAX_PROFIT);
                    }
                }
                // 验证结束的X是否恰好符合条件
                if(checkAmount(targetPrice, amountEnd, errorRange)){
                    return endX;
                }
            }
        }
        return calculateTry(targetPrice, productList, errorRange, numericPrecision, startX, endX);
    }

    /**
     * 二分法 带入数据尝试值
     * @param targetPrice 售价
     * @param productList 参与计算的产品信息
     * @param errorRange 合计值的误差范围小数位数
     * @param numericPrecision 保留的小数位
     * @param startX 开始值
     * @param endX 结束值
     * @return
     */
    public static double calculateTry(double targetPrice,List<ProductInfo> productList,
            int errorRange, int numericPrecision, double startX, double endX) {
        // 让利的最小位数
        double unit = 1/Math.pow(10,numericPrecision);

        while(true){
            // 让利等于开始值和结束值的中间点
            double profit = formatNumber((startX+endX)/2, numericPrecision);
            // 计算的让利计算 售价
            double amount = getTotalRowAmountByProfit(productList,profit,errorRange);
            if(checkAmount(targetPrice,amount,errorRange)){
                return profit;
            }else{
                // 验证计算精度是否达到上线
                double diff = formatNumber(endX-profit, numericPrecision);
                if(unit >= diff){
                    System.out.println("达到计算精度上线!");
                    return profit;
                }

                // 判定输入的售价和当前计算的售价的大小关系
                if(targetPrice > amount){
                    // 目标值比当前带入的 X 合计值大
                    endX = profit;
                }else{
                    // 目标值比当前带入的 X 合计值小
                    startX = profit;
                }

            }
        }
    }

    /**
     * 根据利率合计出所有行的值
     * @param productList 参与计算的产品信息
     * @param profit 让利
     * @param errorRange 合计值的误差范围小数位数
     * @return
     */
    public static double getTotalRowAmountByProfit(List<ProductInfo> productList, double profit, int errorRange){
        double result = 0;
        // 计算合计所有值,带入二分到的让利值
        for(ProductInfo product : productList){
            result += getRowAmountByProfit(product, profit, errorRange);
        }
        return result;
    }

    /**
     * 判定目标合计值和计算出让利的合计值 是否小于误差范围
     * @param targetPrice 目标合计值
     * @param amount 带入让利计算的合计值
     * @param errorRange 数值的误差范围
     * @return
     */
    public static boolean checkAmount(double targetPrice, double amount, int errorRange){
        // 金额的误差范围
        double amountUnit = formatNumber(1/Math.pow(10,errorRange), errorRange);
        // 计算的合计值和目标合计值的大小关系
        if(Math.abs(amount-targetPrice) <= amountUnit){
            return true;
        }else{
            return false;
        }
    }

    /**
     * 根据利率合计出单个行的值
     * @param product 产品信息
     * @param profit 让利
     * @param errorRange 数值的误差范围
     * @return
     */
    public static double getRowAmountByProfit(ProductInfo product, double profit, int errorRange){
        // 成本价格/[1-(利率*(1-让利)]*数量
        double result = product.getPrice() / (1 - (product.getRate() * (1 - profit))) * product.getSum();
        return formatNumber(result, errorRange);
    }

    /**
     * 金额格式化,保存2位小数,四舍五入
     * @param num
     * @return
     */
    private static double formatNumber(double num, int numericPrecision) {
         BigDecimal bg = new BigDecimal(num);
         double result = bg.setScale(numericPrecision, BigDecimal.ROUND_HALF_UP).doubleValue();
         return result;
    }

    public static void main(String[] args) {
        // 成本价格/[1-利率*(1-让利)]*数量
        List<ProductInfo> productList = new ArrayList<ProductInfo>();
        ProductInfo product = new ProductInfo();
        // 产品名
        product.setName("产品1");
        // 成本价格
        product.setPrice(100);
        // 数量
        product.setSum(12);
        // 利率
        product.setRate(0.3);
        productList.add(product);
        product = new ProductInfo();
        // 产品名
        product.setName("产品2");
        // 成本价格
        product.setPrice(2);
        // 数量
        product.setSum(133);
        // 利率
        product.setRate(0.4);
        productList.add(product);
        System.out.println(calculateProfitByBuyAmount(productList, 5000));
    }

}

实体类:

package cn.pzh.test;

public class ProductInfo {

    /**
     * 商品名
     */
    private String name;

    /**
     * 让利
     */
    private double profit;

    /**
     * 数量
     */
    private double sum;

    /**
     * 成本价格
     */
    private double price;

    /**
     * 比例
     */
    private double rate;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getProfit() {
        return profit;
    }

    public void setProfit(double profit) {
        this.profit = profit;
    }

    public double getSum() {
        return sum;
    }

    public void setSum(double sum) {
        this.sum = sum;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public double getRate() {
        return rate;
    }

    public void setRate(double rate) {
        this.rate = rate;
    }


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值