java增函数的单变量求解,最底层码农的不易谁能体会?谁心里苦谁知道啊。

这是一篇本来不应该存在的程序,但是有了世界之大,就有这一段程序,如果你同情我的遭遇,那么进来帮我改进一下,大家一起交流讨论一下吧,我谢谢大家了。

本来我哪有这个业务啊,这个单变量求解可是excel的功能,谁能想到会有领导让人实现这个功能的,你直接调用excel,让他去算不好嘛,偏偏我对象的领导,就想出了这么一个搜肠刮肚,都想不出来的馊主意,还好这个功能使用的场景比较单一,只是用在了一个增函数上,如果连这个函数,是增函数还是减函数,都不清楚的情况下,以我这考不上985的大脑,估计是想不出来了。

废话说完,直接上货,底下是全部源码,不用着急看,可以先看源码底下的解释。

public class GoalSeek {
    public static void main(String[] args) {
        double target = 6.875531;

        double right;
        double left;

        double x=Math.random();
        if(f(x) < target){
            left = x;
            do{
                x*=10;
            } while(f(x) < target);
            right = x;
        } else{
            right = x;
            do{
                x/=10;
            } while(f(x) > target);
            left = x;
        }

        double threshold = findThreshold(right-left);

        double result = 0;

        while(left<right){
            double mid = (right + left)/2.0;
            double temp = f(mid);
            System.out.println("right="+right+" left="+left);
            if(Math.abs(temp-target)<0.0000001){
                result = mid;
                break;
            }
            if(temp > target){
                right -= threshold;
                while(f(right)<target){
                    right += threshold;
                    threshold*=0.1;
                    right -= threshold;
                }
            } else if( temp < target ){
                left += threshold;
                while(f(left)>target){
                    left -= threshold;
                    threshold*=0.1;
                    left += threshold;
                }
            }

        }
        System.out.println(result);
    }


    private static double findThreshold(double max){
        BigDecimal bigDecimal = new BigDecimal(max);
        String[] nums = bigDecimal.setScale(7, RoundingMode.HALF_EVEN).toPlainString().split("\\.");
        if(Integer.parseInt(nums[0])>0){
            return Math.pow(10,nums[0].length()-1);
        }else{
            double result = Math.pow(0.1,(nums[1].length() - String.valueOf(Long.parseLong(nums[1])).length()));
            return result == 1?0.1:result;
        }
    }



    private static double f(double x){
        return x*x+3;
    }

因为是一个增函数,所以需要找到两个值,这两个值需要在目标值的两边,类似于下面这个图:

红点是目标点,绿色是right值,黄色是left值,用二分法一点一点逼近目标值。

1.寻找上下界

所以第一步就是要先找到左右这两个点,代码如下所示:

//1.先随机出一个数来
double x=Math.random();

//如果这个数经过函数计算小于目标值,说明这个数是left值,再继续找right值就可以
//寻找的方法就是将这个随机数扩大10倍(扩大几倍都行),一直到这个数经过函数计算之后大于target就可以
        if(f(x) < target){
            left = x;
            do{
                x*=10;
            } while(f(x) < target);
            right = x;
        } 
//如果这个数经过函数计算大于目标值,说明这个数是right值,再继续寻找left值
//寻找的方法和上面寻找right相反,将随机数缩小10倍,直到这个数经过函数计算之后小于target就可以
        else{
            right = x;
            do{
                x/=10;
            } while(f(x) > target);
            left = x;
        }

2.计算阈值

当使用二分法对值进行逼近的时候,left和right值的加减变得尤为重要,是每一步+1,还是+0.1,还是0.01?这个不好确定,所以就有了如下的方法:

private static double findThreshold(double max){
        BigDecimal bigDecimal = new BigDecimal(max);
        String[] nums = bigDecimal.setScale(7, RoundingMode.HALF_EVEN).toPlainString().split("\\.");
        if(Integer.parseInt(nums[0])>0){
            return Math.pow(10,nums[0].length()-1);
        }else{
            double result = Math.pow(0.1,(nums[1].length() - String.valueOf(Long.parseLong(nums[1])).length()));
            return result == 1?0.1:result;
        }
}

方法入参是right-left所得,这两个数的差值格式,有三种情况:

  • 1.1,这种情况就是小数点两边都有值,这个时候只需要小数点左边的整数值就可以,如果是1.0就得到1,如果是10.0就得到10,如果是100.0就得到100,以此类推。

  • 1.0,这种情况和上述情况一样,只不过这种情况是恰巧得到一个整数,小数点右边没有数

  • 0.1,这种情况就是小数点左边为0,需要找到右边最靠近小数点的非零数,比如0.123得到0.1,0.0123得到0.01,0.00000123得到0.000001.

上述方法是先将小数转换成String格式,通过小数点将字符串变为两个部分,nums[0]是整数部分,nums[1]是小数部分,这地方有个坑就是要防止double变成科学计数法,例如0.00009变成9E-5这种情况,PHP具体怎么变我不知道。

小数点左侧比较好处理,比如左侧是89,则返回10即可,10的1次方

小数点右侧不太好处理,比如0.0076,右边是0076,先将0076转换为Long型变成76,再转换成字符串,用0076的字符串长度减去76的字符串长度 ,得到目标值2,0.1的平方是0.01。这里有一个特殊情况就是0.1最后得到的结果是0,0.1的0次方等于1,所以当这种情况遇到结果是1的时候,返回0.1即可。

3.防止错过答案

有的时候会出现这样一种情况,例如y=x+3,这个函数,如果你的目标值是5.1,即y=5.1是你的目标值,当x的左右值在加减的时候,会让x错过正确的答案,比如正确答案是x=2.1,但是你的左右值随机出来的值是0.1和2.2,计算出来的阈值是1,第一次二分结束的时候会让右边的值编程1.2,直接就错过了2.1这个正确答案,之后再如何算都是徒劳的,甚至进入死循环!

这个时候就是源代码的第三步:

if(temp > target){
    right -= threshold;
    while(f(right)<target){
        right += threshold;
        threshold*=0.1;
        right -= threshold;
    }
} else if( temp < target ){
    left += threshold;
    while(f(left)>target){
        left -= threshold;
        threshold*=0.1;
        left += threshold;
    }
}

这里的思想就是right的函数值是一定要大于目标的,因为是增函数,所以如果right的函数值小于目标,说明阈值大了,那么就将right值恢复,然后将阈值变小,再进行计算,一直到得出结果,左侧同理。

总结

过程就是这么一个过程,作为最底层码农的我,不仅要服务于我的领导,还有我领导的领导,还有客户,还有对象的领导,还有对象领导的客户,你们看吧,我苦不苦,啥也不说了,我又双叒叕坏肚子了。最后我对象还和我说,她有新的任务,暂时先不看了,我... ...

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zcrazy胡说八道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值