如何解决运行 Do...while 计算直到满足条件 说明Endothermic_Dragon:斯科特·索耶:Endothermic_Dragon:斯科特·索耶:Endothermic_Dragon:斯科特·索耶:
我正在使用 Javascript 编写一个简单的 do...while 语句,该语句旨在查看给定输入 num
的小数的长度,如果小数的长度大于或等于 1,取 num
并将其添加到自身上,直到小数长度等于 0。目前,它适用于长度为 1 的小数,但如果大于,则停止。
预期输出,例如,当 num
为 8.75 时,应该是 35,而不是 17.5。
该系列需要 4 个步骤才能达到 35。
8.75
17.5
26.25
35
这是我的代码:
const num = 8.75;
var decimalLength = num.toString().split(".")[1].length;
let result = "";
let i = num;
do {
i = i + num;
result = result + num;
newLength = decimalLength;
} while (newLength < 0);
console.log(i);
解决方法
你可以使用一些花哨的数学来得到一个更明确的答案,而不是一遍又一遍地循环:
const num = 8.75;
var decimal = num % 1 //Decimal part
var decimalPlaces = num.toString().split(".")[1] ?.length || 0; //Get decimal places
//GCD function
const gcd = (x,y) => (!y ? x : gcd(y,x % y));
//GCD between decimal and 1,need to make it whole number to get multiplier,so multiply each with 10^[decimal places]
//Get what's remaining when divided by 10^[decimal places] as well,to see what to multiply by to get whole number
var multiplier = 10 ** decimalPlaces / gcd(10 ** decimalPlaces,decimal * 10 ** decimalPlaces)
//Use log change of base property to get value in power of 2
var outputExponent = Math.log(multiplier) / Math.log(2)
//Multiply number by multiplier
var outputValue = num * multiplier
console.log(outputExponent) //Power of 2 to multiply by to get whole number
console.log(outputValue) //The whole number itself
此版本使用递归函数和容差来处理浮点舍入错误。
const firstIntMultiple = (n,tol=1e-6,c = 1) =>
Math.abs(n * c - Math.floor(n * c)) < tol ? n * c : firstIntMultiple (n,tol,c + 1)
console .log (firstIntMultiple (8.75)) // 35/4
console .log (firstIntMultiple (8.333333333333334)) // 25/3
console .log (firstIntMultiple (14.058823529411764)) // 239/17
它通过乘以连续整数而不是连续加法来找到正确的版本,但这是相同的想法。
我们可以通过迭代方法轻松替换递归版本,这对于没有良好有理近似的数字可能很有用。 (现在,传递 Math.PI
会遇到递归限制。)
可能看起来像这样:
const firstIntMultiple = (n,tol=1e-6) => {
let c = 1;
while (Math.abs(n * c - Math.floor(n * c)) > tol) {
c += 1
}
return n * c
}
对于 Math.PI
,这将返回 4272943.0000005495
,即整数 1e-6
内的第一个 pi 倍数。您可以根据需要调整容差。
更新——一种完全不同的技术
另一种技术将利用 the fact,continued fractions 提供了一种直接的方法来找到一个数字的最佳有理近似值。我们可以使用它来找到 pi
的十个最佳有理近似,例如:
bestApprox (Math.PI,1,10) .map (({n,d}) => `${n}/${d}`)
// => ["3/1","22/7","333/106","355/113","103993/33102","104348/33215",// "208341/66317","312689/99532","833719/265381","1146408/364913"]
然后,使用这些近似值,我们可以找到第一个与我们的目标值相距很小的值。
代码可能如下所示:
const contFrac = (n,d = 1,count = Infinity) =>
count < 1 || d == 0
? []
: [Math .floor (n / d)] .concat (contFrac (d,n % d,count - 1))
const bestApprox = (n,c) =>
contFrac(n,d,c)
.reduce ((a,n,i) => [
...a,{
n: a [i] .n + n * a [i + 1] .n,d: a [i] .d + n * a [i + 1] .d
}
],[{n: 0,d: 1},{n: 1,d: 0}])
.slice (2)
const firstIntMultiple = (x,ε = 1e-6) =>
bestApprox (x,1)
.find (({n,d},i,a) => i == a.length - 1 || Math.abs (n / d - x) < ε)
.n
console .log (firstIntMultiple (8.75)) // 35/4
console .log (firstIntMultiple (8.333333333333334)) // 25/3
console .log (firstIntMultiple (14.058823529411764)) // 239/17
console .log (firstIntMultiple (Math.PI)) // 353/113
console .log (firstIntMultiple (Math.PI,1e-8)) // 103993/33102
我没有测试过性能,但这应该是相当不错的,特别是对于那些连分数早期包含大整数的数字。 (例如,pi
是 <3; 7,15,292,...>
292
意味着 3 + (1 / (7 + (1 / (15 + 1 / 1))))
或 355 / 113
是 pi
的极好近似值,事实上,它很好保留到小数点后六位。
我不知道这对 OP 有多大帮助,但它表明古老的数学课程有一天可能会派上用场!。 ;-)
更新 2 - 现在有更多解释!
此版本通过不检查测试值是否在原始值的 epsilon 范围内,而是检查测试值与原始值的比率,用小值解决了第二种方法中的问题值在 1
的 epsilon 内。它也有一些小的清理和默认 epsilon 的较小值:
const contFrac = (n,count = Infinity) =>
contFrac(n,count)
.reduce ((a,d: 0}])
.slice (2)
const isClose = (x,y,ε) =>
y == 0 ? x < ε : (x / y > 1 - ε && x / y < 1 + ε)
const firstIntMultiple = (x,ε = 1e-8) =>
bestApprox (x,a) => i == a.length - 1 || isClose (n / d,x,ε))
.n
console .log (firstIntMultiple (8.75)) // 35/4
console .log (firstIntMultiple (8.333333333333334)) // 25/3
console .log (firstIntMultiple (14.058823529411764)) // 239/17
console .log (firstIntMultiple (Math.PI)) // 103993/33102
console .log (firstIntMultiple (Math.PI,1e-6)) // 353/113
console .log (firstIntMultiple (13.000000221)) // 58823532/4524887
console .log (firstIntMultiple (1.0000003333)) // 3000301/3000300
console .log (firstIntMultiple (1234/987654321)) // 6/4802209
.as-console-wrapper {min-height: 100% !important; top: 0}
说明
这里的主要函数仍然是firstIntMultiple
,它相当简单,只是搜索bestApprox
的结果以获得足够接近我们目标数的有理近似值,然后返回该结果的分子. “足够接近”由isClose
决定,它检查两个数字的比率是否在1 - ε
和1 + ε
之间,其中ε
是一个可选参数,默认为{ {1}}。
所以问题是 1e-8
是如何工作的。为此,我们需要讨论 Continued Fractions。我不能在这里公正地对待他们,但希望我能描述足够多的人来感受他们。
这是一个重复的无限简单连分数:
bestApprox
这是一个连分数,因为我们将分数嵌套在其他分数的分母中。它是无限的,因为……嗯,因为它无限地继续——以一种明显的方式。这很简单,因为所有
分子是 1
1 + ---------------------------------
1
2 + ----------------------------
1
2 + ---------------------
1
2 + ---------------
1
2 + ---------
2 + ...
。
用一点代数不难证明这代表 1
的平方根。
这通常会用如下符号缩写:
2
这里所有的值都是整数,在第一个之后,都是正数。
这些有一些优点。所有有理数都具有作为连分数的有限表示,并且所有二次数都具有重复的无限模式。但更重要的是,这些连分数的前缀包含对一个数的最佳有理近似。 (证明不是特别难,应该是非数学家可以遵循的。但我不会在这里尝试。)
我的意思是这些数字:
<1; 2,2,...>
是对 <1;> //=> 1
<1; 2> //=> 3/2
<1; 2,2> //=> 7/5
<1; 2,2> //=> 17/12
<1; 2,2> //=> 41/29
...
的连续更好的近似,除了更高的分母,没有更好的近似可用。也就是说,例如,在大于 sqrt(2)
且小于 12
的分母中,没有比 29
更好的近似值。
因此,通过计算一个数的部分连分数,我们可以找到所有最好的近似值,并最终找到一个能得到我们正在寻找的倍数的近似值。
现在我们需要知道如何计算这些连分数,然后如何将它们的部分转换为有理数。幸运的是,两者都非常简单。
要找到连分数的元素,我们需要做的就是找到数字的下限,将其添加到我们的列表中,然后继续求余数的倒数。
如果我们从 sqrt(2)
开始,那么第一个元素将是 27/11
或 floor(27/11)
;余数为2
,其倒数为5/11
,下一位数为11/5
的底数,余数为2
,其倒数是 1/5
没有余数。所以5
可以写成27/11
。
如果我们从 <2; 2,5>
开始,那么我们的第一个元素将是 pi
,然后我们将继续使用 3
的倒数,即 0.14159265358979312
,并且下一个元素是 7.062513305931052
。取余数的倒数,我们得到7
,下一个元素是15.996594406684103
。余数的倒数是 15
,所以下一个元素是 1.0034172310150002
。然后余数的倒数变得更大,在 1
。我们可以继续得到如下结果:
292.63459087501246
没有明显的模式。但是 <3; 7,3,14,...>
的高值告诉我们,292
或 <3; 7,1>
是 355/113
的极好近似值。
函数 pi
按照描述执行此算法。
现在,要将部分转换为有理数,我们可以使用简单的递归。在 contFrac
中,第一个近似值是 <a_0; a_1,a_2,a_3,...>
,我们将其写为 a_0
。第二个是 a_0/1
或 a_0 + (1 / a_1)
。之后,我们可以通过这个简单的公式找到 (a_0 * a_1) / a_1
nd 值:(k + 2)
和 n_(k + 2) = a_(k + 2) * n_(k + 1) + n_k
。
所以对于 d_(k + 2) = a_(k + 2) * d_(k + 1) + d_k
,我们从 <3; 7,1>
开始,然后是 3/1
,然后我们的下一个值是 22/7
或 (15 * 22 + 3) / (15 * 7 + 1)
,然后是 333/106
或(1 * 333 + 22) / (1 * 106 + 7)
。代码使用了一个小技巧,将分子和分母扩展回两步,以便我们可以对每一步使用我们的递归,然后简单地将这两个值从最终结果中切掉。
我们有了。通过使用一个数的最佳有理近似,我们可以快速找到最小的整数,在很小的容差范围内,它是该数的倍数。
,这是所有答案的组合。这样做的好处是它对每种输入都有一个“后备”。如果有终止小数点,Endothermic_Dragon 的第一种方法就足够了。如果它是一个重复的小数,那么找到明确答案的新方法就足够了。如果不够,则连分数用作后备。
请注意,我最后包含连分数的唯一原因是它有时会导致小数点较小的错误。我只想“修复”这个方法,但我不明白它是如何工作的,所以我用它作为后备。
//At the top so it is viewable
console.log(findMultiple(54.46333333333333)) //16339/300
console.log(findMultiple(8.333333333333333)) //25/3
console.log(findMultiple(14.05882352941176)) //Irrational
console.log(findMultiple(94.03820382038203)) //Irrational
console.log(findMultiple(623.0549383482724)) //1009349/1620
console.log(findMultiple(Math.PI)) //Irrational
console.log(findMultiple(13.000000221)) //1829587379722249/140737488355328
console.log(findMultiple(1.0000003333)) //1125900282105063/1125899906842624
//Main control
function findMultiple(num,interpretLiteral = false,epsilon = 1e-6) {
if (num.toString().length < 17 || num % 1 == 0 || interpretLiteral) {
return EndothermicDragonFirstMethod(num) //Terminating decimal
}
var checkRepeatingNum = CheckRepeating(num)
if (checkRepeatingNum != false) {
return Math.round(EndothermicDragonSecondMethod(num,checkRepeatingNum) * num) //Repeating decimal
} else {
return ScottSauyetFirstMethod(num,epsilon) //Continued fraction
}
}
//Predifined functions
//GCD
function gcd(x,y){return !y ? x : gcd(y,x % y)};
//Check if digits repeat,if they do,return repeat period
function CheckRepeating(num) {
var totalSearchLength = (num % 1).toString().split('.')[1].slice(0,-1).length
var numTemp1 = (num % 1).toString().split('.')[1].slice(0,-1).split('').reverse().join('')
for (var i = 1; i < Math.floor(totalSearchLength / 3); i++) {
var numTemp2 = numTemp1.slice(0,3 * i)
var searchLength = i
bool = numTemp2.slice(0,searchLength) == numTemp2.slice(searchLength,2 * searchLength) && numTemp2.slice(0,searchLength) == numTemp2.slice(2 * searchLength,3 * searchLength)
if (bool) {
return searchLength
}
}
return false
}
//Terminating decimal
function EndothermicDragonFirstMethod(num) {
var decimal = num % 1;
var decimalPlaces = num.toString().split(".")[1]?.length || 0;
var multiplier = 10 ** decimalPlaces / gcd(10 ** decimalPlaces,decimal * (10 ** decimalPlaces));
return num * multiplier;
}
//Repeating decimal
function EndothermicDragonSecondMethod(num,repeatInterval) {
var numArray = num.toString().split('.')[1].slice(-repeatInterval).split('').reverse()
var restOfNum = num.toString().split('.')[1].slice(0,-repeatInterval * 3).split('').reverse()
var counter = 0;
var extraRepeat = 0;
restOfNum.every(el => {
if (el == numArray[counter]) {
extraRepeat++;
counter++;
if (counter == numArray.length) {
counter = 0
}
return true
}
return false
})
var repeatingPart = num.toString().split('.')[1].slice(-repeatInterval * 3 - extraRepeat,-repeatInterval * 2 - extraRepeat)
var notRepeatingPart = num.toString().split('.')[1].slice(0,-repeatInterval * 3 - extraRepeat)
var numerator = (parseInt(notRepeatingPart) * (parseInt("9".repeat(repeatingPart.length)))) + parseInt(repeatingPart)
var denominator = (parseInt("9".repeat(repeatingPart.length)) * (10 ** notRepeatingPart.length))
return denominator / gcd(numerator,denominator)
}
//Otherwise (irrational numbers or other)
function ScottSauyetFirstMethod(num,epsilon = 1e-6) {
const contFrac = (n,count = Infinity) =>
count < 1 || d == 0 ? [] : [Math.floor(n / d)].concat(contFrac(d,count - 1))
const bestApprox = (n,c) =>
contFrac(n,c)
.reduce((a,{
n: a[i].n + n * a[i + 1].n,d: a[i].d + n * a[i + 1].d
}
],[{
n: 0,d: 1
},{
n: 1,d: 0
}])
.slice(2)
const firstIntMultiple = (x,epsilon) =>
bestApprox(x,1)
.find(({
n,d
},a) => i == a.length - 1 || Math.abs(n / d - x) < epsilon)
.n
return firstIntMultiple(num,epsilon)
}
这会产生准确的答案,无论输入是什么(即使它是一个小数)!
,两个相互竞争的答案。哪个更好?在你测试之前没有办法知道。所以,这就是这个新答案试图做的。
这些“测试”(如果您愿意)运行每个函数 50 次,在清除控制台之前为每次迭代计时并记录时间的平均值。
8 十进制精度
Endothermic_Dragon:
const num = 3.14159265;
//Function start
const firstIntMultiple = () => {
var decimal = num % 1;
var decimalPlaces = num.toString().split(".")[1] ?.length || 0;
const gcd = (x,x % y));
var multiplier = 10 ** decimalPlaces / gcd(10 ** decimalPlaces,decimal * (10 ** decimalPlaces));
return num * multiplier;
}
//Function end
var times = []
for (var i = 0; i < 50; i++) {
var startTime = new Date()
console.log(firstIntMultiple().toString())
times.push((new Date() - startTime) / 1000)
}
console.clear()
console.log((times.reduce((a,b) => a + b,0) / times.length) + " seconds on average")
斯科特·索耶:
const num = 3.14159265;
//Function start
const firstIntMultiple = (tol = 1e-8) => {
let c = 1;
while (Math.abs(num * c - Math.floor(num * c)) > tol) {
c += 1
}
return num * c
}
//Function end
var times = []
for (var i = 0; i < 50; i++) {
var startTime = new Date()
console.log(firstIntMultiple().toString())
times.push((new Date() - startTime) / 1000)
}
console.clear()
console.log((times.reduce((a,0) / times.length) + " seconds on average")
10 十进制精度
Endothermic_Dragon:
const num = 3.1415926535;
//Function start
const firstIntMultiple = () => {
var decimal = num % 1;
var decimalPlaces = num.toString().split(".")[1] ?.length || 0;
const gcd = (x,0) / times.length) + " seconds on average")
斯科特·索耶:
const num = 3.1415926535;
//Function start
const firstIntMultiple = (tol = 1e-10) => {
let c = 1;
while (Math.abs(num * c - Math.floor(num * c)) > tol) {
c += 1
}
return num * c
}
//Function end
var times = []
for (var i = 0; i < 50; i++) {
var startTime = new Date()
console.log(firstIntMultiple().toString())
times.push((new Date() - startTime) / 1000)
}
console.clear()
console.log((times.reduce((a,0) / times.length) + " seconds on average")
请注意,由于内部机制略有不同,这些函数将返回略有不同的结果。 Endothermic_Dragon 的答案试图找到将返回精确整数的倍数,而 Scott Sauyet 的答案试图找到使数字在整数的指定容差范围内的乘数。
这些示例可以通过在每个示例上添加更多 pi 数字并降低对 Scott Sauyet 答案的容忍度来进一步扩展。
我最初打算在测试这个 (https://mikemcl.github.io/decimal.js/) 时使用 decimal.js
,但是,结果证明在 Scott 的方法上非常慢 - 计算每次迭代需要 5.1243 秒,而在 Endothermic_Dragon 的方法上每次迭代需要 0.0002 秒。请注意,这是在 w3schools 上通过 10 次迭代完成的。
所有这些分析可以得出两个结论:
- Endothermic_Dragon 的答案在终止小数方面具有可扩展性和准确性。
- Scott Sauyet 的回答耗费了一些时间,但提供了准确的答案。这在处理不终止的小数时特别有用,无论它们是无理数还是重复。
另外,这是一个额外的测试,因为为什么不呢。
15 十进制精度
Endothermic_Dragon:
const num = 3.141592653589793;
//Function start
const firstIntMultiple = () => {
var decimal = num % 1;
var decimalPlaces = num.toString().split(".")[1] ?.length || 0;
const gcd = (x,0) / times.length) + " seconds on average")
斯科特·索耶:
const num = 3.141592653589793;
//Function start
const firstIntMultiple = (tol = 1e-15) => {
let c = 1;
while (Math.abs(num * c - Math.floor(num * c)) > tol) {
c += 1
}
return num * c
}
//Function end
var times = []
for (var i = 0; i < 50; i++) {
var startTime = new Date()
console.log(firstIntMultiple().toString())
times.push((new Date() - startTime) / 1000)
}
console.clear()
console.log((times.reduce((a,0) / times.length) + " seconds on average")
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。