在循环中使用Math.pow()和Math.place()调用简化函数

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (83)

如何简化以下函数和重复调用Math.pow()?

function x(a, b, c) {
    rv = Math.floor(a * Math.pow(2, b));

    for (i = 1; i < c; i++) {
        rv += Math.floor(a * Math.pow(1.2, b + i));
    }

    return rv;
}
提问于
用户回答回答于

根据问题中的标记,我假设希望提高代码的性能。

以下是一些可以使用的技术,大致按照它们会给代码带来的性能提高进行排序,除了第一种,但我鼓励在代码中逐一实现它们,并查看它们如何影响代码的性能。

✏️基础:

首先,在注释中建议,应该使用局部变量而不是全局变量,因此请添加let在他们面前。

因为这样做可能是可以忽略的,但这并不是改变它的唯一原因:使用globals被认为是一种不好的做法,因为它们污染了全局名称空间并使你的代码更难维护

⛓️注释指数:

而不是执行Math.pow(1.2, b + i)在每次迭代中,都可以使用前面的迭代值计算当前的迭代值,因为乘法应该比幂运算快得多:

let exp = Math.pow(1.2, b);

for (let i = 1; i < c; ++i) {
    exp  *= 1.2;
    rv += Math.floor(a * exp);
}

按位非运算符的🤖地板(~):

如果a是正的,而你正在做的地板上的值总是< 2147483648,你可以用一个 NOT (~)而不是Math.floor(),如你所见:

console.log(`Math.floor(4.01)  =  ${ Math.floor(4.01) }`);
console.log(`Math.floor(4.99)  =  ${ Math.floor(4.99) }`);
console.log(`Math.floor(-4.01) = ${ Math.floor(-4.01) }`);
console.log(`Math.floor(-4.99) = ${ Math.floor(-4.99) }`);

console.log(`~~4.01  =  ${ ~~4.01 }`);
console.log(`~~4.99  =  ${ ~~4.99 }`);
console.log(`~~-4.01 = ${ ~~-4.01 }`);
console.log(`~~-4.99 = ${ ~~-4.99 }`);

console.log(`Math.floor(2147483647.99) = ${ Math.floor(2147483647.99) }`);
console.log(`Math.floor(2147483648.01) = ${ Math.floor(2147483648.01) }`);
console.log(`~~2147483647.99 = ${ ~~2147483647.99 }`);
console.log(`~~2147483648.01 = ${ ~~2147483648.01 }`);
.as-console-wrapper {
  max-height: 100vh !important;
}

但是,如果试图创建一个值>= 2147483648~~将换行并返回一个不正确的值,因为按位运算符处理32位整数,因此可以安全使用的最大值是231-1,或者2147483647

按位运算符的😺地板可供选择:

在这种情况下,你可以用Math.trunc()而不是Math.floor(),这有点快,正如在这里看到的

带左移位运算符的🤖Base2指数:

同样,如果b是那个整数s.t。1 <= b <= 30,可以使用左移(<<)而不是第一个Math.pow(2, b)2 << (b - 1):

for (let i = 0; i <= 31; ++i) {
  console.log(`Math.pow(2, ${ i }) === 2 << (${ i } - 1)? ${ Math.pow(2, i) === 2 << (i - 1) ? 'Yes' : 'No'}.` );
}
.as-console-wrapper {
  max-height: 100vh !important;
}

➰使用WITH循环:

注意到了吗,在应用回忆录技巧之后,我们没有使用i循环中的变量了吗?现在可以替换for带着while,这不会带来很大的收益,但仍然值得一提的是:

while (--c) {
    exp *= 1.2;
    rv += ~~(a * exp);
} 

💨最终结果:

总之,超高速代码如下所示:

function original(a, b, c) {
  rv = Math.floor(a * Math.pow(2, b));

  for (i = 1; i < c; i++) {
    rv += Math.floor(a * Math.pow(1.2, b+i));
  }

  return rv;
}

function faster(a, b, c) {
  let rv = ~~(a * (2 << (b - 1)));
  let exp = Math.pow(1.2, b);
  
  while (--c) {
    exp *= 1.2;
    rv += ~~(a * exp);
  } 

  return rv;
}

const expected = original(2, 2, 113);
const actual = faster(2, 2, 113);
const ok = expected === actual ;


if (ok) {
  // BEFORE:
  
  const t0b = performance.now();
  
  for (let i = 0; i < 100000; ++i) {
    original(2, 2, 113);
  }
  
  const tb = performance.now() - t0b;
  
  // AFTER:
  
  const t0a = performance.now();
  
  for (let i = 0; i < 100000; ++i) {
    faster(2, 2, 113);
  }
  
  const ta = performance.now() - t0a;
  
  console.log(` BEFORE = ${ tb }s`);
  console.log(`  AFTER = ${ ta }s`);
  console.log(`SPEEDUP = ${ Math.round(100 * tb / ta) / 100 } = ${ Math.round((1 - ta / tb) * 10000) / 100 }% faster!`);
  
} else {
  console.log(`Faster version = ${ actual } does not return the same as before = ${ expected }`);
}
.as-console-wrapper {
  max-height: 100vh !important;
}

📜循环展开:

值得一提的是,由于正在查找每个操作的结果,这种技术不会太快地加速代码,因此考虑到代码变得多么冗长,它可能不值得这么做。

可以阅读更多关于循环展开的内容。

但是,如果没有计算结果,则可以节省许多计算:

function faster (a, b, c) {
    let rv = a * (2 << (b - 1));
    let exp = Math.pow(1.2, b);

    const r = c % 4;

    if (r === 0) {
        exp *= 1.728;
        rv += a * a * a * exp;
    } else if (r === 1) {
        c += 3;
    } else if (r === 2) {
        exp *= 1.2;
        rv += a * exp;
        c += 2;
    } else if (r === 3) {
        exp *= 1.44;
        rv += a * a * exp;
        c += 1;
    }

    a = Math.pow(a, 4);
    c /= 4;

    while (--c) {
        exp *= 2.0736;
        rv += a * exp;
    }

    return rv;
}

正如所看到的,它的优点是可以将多个迭代的计算合并到一个单独的迭代中,而不是像在原始代码中所做的那样仅仅重复它们。仅为演示目的:

function original(a, b, c) {
  rv = Math.floor(a * Math.pow(2, b));

  for (i = 1; i < c; i++) {
    rv += Math.floor(a * Math.pow(1.2, b+i));
  }

  return rv;
}

function faster(a, b, c) {
  let rv = ~~(a * (2 << (b - 1)));
  let exp = Math.pow(1.2, b);
  
  const r = c % 4;
  
  if (r === 0) {
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
  } else if (r === 1) {
    c += 3;
  } else if (r === 2) {
    exp *= 1.2;
    rv += ~~(a * exp);
    c += 2;
  } else if (r === 3) {
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
    c += 1;
  }
  
  c /= 4;
  
  while (--c) {
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
  }

  return rv;
}

const expected = original(2, 2, 113);
const actual = faster(2, 2, 113);
const ok = expected === actual;

if (ok) {
  // BEFORE:
  
  const t0b = performance.now();
  
  for (let i = 0; i < 100000; ++i) {
    original(2, 2, 113);
  }
  
  const tb = performance.now() - t0b;
  
  // AFTER:
  
  const t0a = performance.now();
  
  for (let i = 0; i < 100000; ++i) {
    faster(2, 2, 113);
  }
  
  const ta = performance.now() - t0a;
  
  console.log(` BEFORE = ${ tb }s`);
  console.log(`  AFTER = ${ ta }s`);
  console.log(`SPEEDUP = ${ Math.round(100 * tb / ta) / 100 } = ${ Math.round((1 - ta / tb) * 10000) / 100 }% faster!`);
  
} else {
  console.log(`Faster version = ${ actual } does not return the same as before = ${ expected }`);
}
.as-console-wrapper {
  max-height: 100vh !important;
}
用户回答回答于

如果你担心为大范围计算所有1.2^(b+i),请考虑底层可能会在编译器优化中推断出以前的结果。然而,如果你想要明确地帮助他,你可以这样做

function x (a, b, c) {
  var rv = Math.floor(a * Math.pow(2, b))
  var multiplier = Math.pow(1.2, b + 1)
  for (i = 1; i < c; i++) {
    rv += Math.floor(a * multiplier);
    multiplier *= 1.2
  }
  return rv;
}

只是数学。

所属标签

可能回答问题的人

  • 天使的炫翼

    15 粉丝531 提问35 回答
  • 富有想象力的人

    2 粉丝0 提问26 回答
  • 旺仔小小鹿

    社区 · 运营 (已认证)

    48 粉丝0 提问26 回答
  • 发条丶魔灵1

    6 粉丝525 提问25 回答

扫码关注云+社区

领取腾讯云代金券