Ответ 1
Примечание.
Math.log2
фактически не изменилось с момента его внедрения в V8. Возможно, вы неправильно вспомнили или включили прокладку, которая произошло, чтобы получить правильный результат для этих особых случаев до Chrome включала собственную реализациюMath.log2
.Кроме того, кажется, что вы должны использовать
Math.ceil(x)
, а неMath.floor(x) + 1
.
Как я могу это решить?
Чтобы не полагаться на Math.log
или Math.log2
, быть точным среди различных реализаций JavaScript (используемый алгоритм - это реализация), вы можете использовать побитовые операторы, если у вас меньше 2 32 элементов в вашем двоичном дереве. Это, очевидно, не самый быстрый способ сделать это (это только O (n)), но это относительно простой пример:
function log2floor(x) {
// match the behaviour of Math.floor(Math.log2(x)), change it if you like
if (x === 0) return -Infinity;
for (var i = 0; i < 32; ++i) {
if (x >>> i === 1) return i;
}
}
console.log(log2floor(36) + 1); // 6
Как Math.log2
реализуется в настоящее время в разных браузерах?
Текущая реализация в Chrome неточна, поскольку они полагаются на умножение значения Math.log(x)
на Math.LOG2E
, что делает его восприимчивым к ошибке округления (source):
// ES6 draft 09-27-13, section 20.2.2.22.
function MathLog2(x) {
return MathLog(x) * 1.442695040888963407; // log2(x) = log(x)/log(2).
}
Если вы используете Firefox, он либо использует встроенную функцию log2
(если присутствует), либо если нет (например, в Windows), использует аналогичную реализацию в Chrome (источник).
Единственное отличие состоит в том, что вместо умножения они делятся на log(2)
:
#if !HAVE_LOG2
double log2(double x)
{
return log(x) / M_LN2;
}
#endif
double
js::math_log2_impl(MathCache *cache, double x)
{
return cache->lookup(log2, x, MathCache::Log2);
}
double
js::math_log2_uncached(double x)
{
return log2(x);
}
bool
js::math_log2(JSContext *cx, unsigned argc, Value *vp)
{
return math_function<math_log2_impl>(cx, argc, vp);
}
Весь другой код - это просто кешировать результаты в таблице, которую Chrome не делает. Это не влияет на точность функции Firefox Math.log2
.
Умножение или деление: какая разница?
Чтобы проверить разницу между делением на Math.LN2
и умножением на Math.LOG2E
, мы можем использовать следующий тест:
function log2d(x) { return Math.log(x) / Math.LN2; }
function log2m(x) { return Math.log(x) * Math.LOG2E; }
var pow = Math.pow;
// 2^1024 rounds to Infinity
for (var i = 0; i < 1024; ++i) {
var resultD = log2d(pow(2, i));
var resultM = log2m(pow(2, i));
if (resultD !== i) console.log('log2d: expected ' + i + ', actual ' + resultD);
if (resultM !== i) console.log('log2m: expected ' + i + ', actual ' + resultM);
}
Обратите внимание, что независимо от того, какую функцию вы используете, у них все еще есть ошибки с плавающей запятой для определенных значений 1. Так получилось, что представление с плавающей запятой log(2)
меньше фактического значения, что приводит к значению выше фактического значения (тогда как log2(e)
ниже). Это означает, что использование log(2)
округляется до правильного значения для этих особых случаев.
1:log(pow(2, 29)) / log(2) === 29.000000000000004