const IsNanError = TypeError('resulted in NaN'); const FailedToConvergeError = Error('failed to converge'); const InvalidInputsError = Error('invalid inputs'); export default async function GoalSeek(options) { const { fn, fnParams, percentTolerance, customToleranceFn, maxIterations, maxStep, goal, independentVariableIdx, } = options; if (typeof customToleranceFn !== 'function') { if (!percentTolerance) { throw InvalidInputsError; } } let g; let y; let oldY = 0; let oldGuess = fnParams[independentVariableIdx]; let newGuess; let res; let max; let min; const absoluteTolerance = ((percentTolerance || 0) / 100) * goal; async function getMin(minGuest) { let copyParams = [...fnParams] copyParams[independentVariableIdx] = minGuest res = await fn(...copyParams); if (res > goal) { minGuest -= oldGuess / 2; await getMin(minGuest); } return minGuest; } async function getMax(maxGuest) { let copyParams = [...fnParams] copyParams[independentVariableIdx] = maxGuest res = await fn(...copyParams); if (res < goal) { maxGuest += oldGuess / 2; await getMax(maxGuest); } return maxGuest; } // iterate through the guesses for (let i = 0; i < maxIterations; i++) { // define the root of the function as the error res = await fn(...fnParams); y = res - goal; if (isNaN(y)) throw IsNanError; // 判断是否满足条件 if (typeof customToleranceFn !== 'function') { if (Math.abs(y) <= Math.abs(absoluteTolerance)) return fnParams[independentVariableIdx]; } else { if (customToleranceFn(res)) return fnParams[independentVariableIdx]; } // 获取要改变的变量 oldGuess = fnParams[independentVariableIdx]; // newGuess = oldGuess + y; // if (Math.abs(newGuess - oldGuess) > maxStep) { // if (newGuess > oldGuess) { // newGuess = oldGuess + maxStep; // } else { // newGuess = oldGuess - maxStep; // } // } // 获取最大值和最小值 if (y > 0) { max = oldGuess; if (!min) { min = await getMin(oldGuess / 2); } } else { min = oldGuess; if (!max) { max = await getMax(oldGuess * 2); } } // if(oldY) { // if(y > oldY) { // } // } // oldY = y; // 利用二分法查询 newGuess = (min + max) / 2; fnParams[independentVariableIdx] = newGuess; } // done with iterations, and we failed to converge throw FailedToConvergeError; } // const fn = (x, y) => x / y; // const fnParams = [2037375, 15897178]; // const customToleranceFn = (x) => { // return x < 1; // }; // try { // const result = goalSeek({ // fn, // fnParams, // customToleranceFn, // maxIterations: 1000, // maxStep: 0.01, // goal: 0.15, // independentVariableIdx: 0, // }); // console.log(`result: ${result}`); // } catch (e) { // console.log("error", e); // }