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;
}
}