2.9999999999999999 >> .5? - PullRequest
       22

2.9999999999999999 >> .5?

13 голосов
/ 06 октября 2008

Я слышал, что вы можете сдвинуть число вправо на .5 вместо использования Math.floor (). Я решил проверить его пределы, чтобы убедиться, что это подходящая замена, поэтому я проверил следующие значения и получил следующие результаты в Google Chrome:


2.5 >> .5 == 2;
2.9999 >> .5 == 2;
2.999999999999999 >> .5 == 2;  // 15 9s
2.9999999999999999 >> .5 == 3;  // 16 9s

После некоторой путаницы я обнаружил, что максимально возможное значение двух, которое при смещении вправо на .5 даст 2, равно 2.9999999999999997779553950749686919152736663818359374999999¯ (с повторением 9) в Chrome и Firefox. Номер 2,99999999999999997779 в IE.

Мой вопрос: каково значение числа .0000000000000007779553950749686919152736663818359374? Это очень странное число, и оно действительно пробудило мое любопытство.

Я пытался найти ответ или, по крайней мере, какой-то шаблон, но я думаю, что моя проблема заключается в том, что я действительно не понимаю побитовую операцию. Я в принципе понимаю идею, но сдвиг битовой последовательности на .5 не имеет никакого смысла для меня. Любая помощь приветствуется.

Для записи странная последовательность цифр меняется на 2 ^ x. Максимально возможные значения следующих чисел, которые по-прежнему правильно обрезаются:

for 0: 0.9999999999999999444888487687421729788184165954589843749¯
for 1: 1.9999999999999999888977697537484345957636833190917968749¯
for 2-3: x+.99999999999999977795539507496869191527366638183593749¯
for 4-7: x+.9999999999999995559107901499373838305473327636718749¯
for 8-15: x+.999999999999999111821580299874767661094665527343749¯
...and so forth

Ответы [ 10 ]

63 голосов
/ 06 октября 2008

На самом деле, вы просто выполняете функцию floor () для первого операнда без каких-либо операций с плавающей запятой. Поскольку побитовые операции левого и правого сдвига имеют смысл только с целочисленными операндами, механизм JavaScript сначала преобразует два операнда в целые числа:

2.999999 >> 0.5

становится:

Math.floor(2.999999) >> Math.floor(0.5)

Что в свою очередь:

2 >> 0

Сдвиг на 0 бит означает «не делать сдвиг», и поэтому вы получите первый операнд, просто усеченный до целого числа.

Исходный код SpiderMonkey имеет:

switch (op) {
  case JSOP_LSH:
  case JSOP_RSH:
    if (!js_DoubleToECMAInt32(cx, d, &i)) // Same as Math.floor()
        return JS_FALSE;
    if (!js_DoubleToECMAInt32(cx, d2, &j)) // Same as Math.floor()
        return JS_FALSE;
    j &= 31;
    d = (op == JSOP_LSH) ? i << j : i >> j;
    break;

Вы видите «округление» с определенными числами из-за того факта, что механизм JavaScript не может обрабатывать десятичные цифры сверх определенной точности, и поэтому ваше число в итоге округляется до следующего целого числа. Попробуйте это в вашем браузере:

alert(2.999999999999999);

Вы получите 2.999999999999999. Теперь попробуйте добавить еще 9:

alert(2.9999999999999999);

Вы получите 3.

25 голосов
/ 06 октября 2008

Возможно, это единственная худшая идея, которую я когда-либо видел. Его единственная возможная цель для существующего - выиграть в состязательном кодовом конкурсе. Длинные числа, которые вы опубликовали, не имеют никакого значения - они являются артефактом базовой реализации с плавающей запятой, отфильтрованной по богам, сколько промежуточных слоев. Сдвиг битов на дробное число байтов безумен, и я удивлен, что это не вызывает исключения - но это Javascript, всегда готовый переопределить «безумный».

На вашем месте я бы никогда не использовал эту "функцию". Его единственное значение - возможная основная причина необычной ошибки. Используйте Math.floor() и пожалейте следующего программиста, который будет поддерживать код.


Подтверждение пары подозрений, которые у меня возникли при чтении вопроса:

  • Смещение вправо любого дробного числа x на любое дробное число y просто усекает x, давая тот же результат, что и Math.floor(), и полностью сбивает с толку читателя.
  • 2.999999999999999777955395074968691915 ... это просто наибольшее число, которое можно отличить от "3". Попробуйте оценить его самостоятельно - если вы добавите к нему что-либо, оно будет оценено как 3. Это артефакт реализации с плавающей точкой в ​​браузере и локальной системе.
6 голосов
/ 06 октября 2008

Если вы хотите углубиться, прочитайте «Что должен знать каждый ученый об арифметике с плавающей точкой»: http://docs.sun.com/source/806-3568/ncg_goldberg.html

5 голосов
/ 06 октября 2008

Попробуйте этот JavaScript: Оповещение (parseFloat ( "2,9999999999999997779553950749686919152736663818359374999999"));

Тогда попробуйте это: Оповещение (parseFloat ( "2,9999999999999997779553950749686919152736663818359375"));

То, что вы видите, является простой неточностью с плавающей запятой. Для получения дополнительной информации об этом см. Например: http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems.

Основная проблема заключается в том, что самое близкое значение, которое может получить значение с плавающей запятой к представлению второго числа, больше или равно 3, тогда как значение закрытия, с которым поплавок может получить первое число, строго меньше трех. 1008 *

Что касается того, почему смещение вправо на 0,5 делает что-либо вменяемым, кажется, что 0.5 само по себе заранее преобразуется в int (0). Затем исходный float (2.999 ...) преобразуется в int путем усечения, как обычно.

5 голосов
/ 06 октября 2008

Я не думаю, что ваш правильный сдвиг имеет значение. Вы просто превышаете разрешение константы с плавающей запятой двойной точности.

в Chrome:

var x = 2.999999999999999777955395074968691915273666381835937499999;
var y = 2.9999999999999997779553950749686919152736663818359375;

document.write("x=" + x);
document.write(" y=" + y);

Распечатывает: x = 2.9999999999999996 y = 3

3 голосов
/ 06 октября 2008

Оператор сдвига вправо работает только с целыми числами (обе стороны). Таким образом, смещение вправо на 0,5 бита должно быть точно эквивалентно смещению вправо на 0 бит. И перед операцией сдвига левая часть преобразуется в целое число, что аналогично Math.floor ().

2 голосов
/ 30 октября 2008

Я подозреваю, что преобразование 2.9999999999999997779553950749686919152736663818359374999999 чтобы это двоичное представление было бы поучительным. Это, вероятно, только 1 немного отличается от истины 3.

Хорошее предположение, но без сигары. Поскольку число FP двойной точности имеет 53 бита, последний номер FP перед 3 на самом деле (точное): 2.999999999999999555910790149937383830547332763671875

Но почему это 2.9999999999999997779553950749686919152736663818359375

(и это точно, а не 49999 ...!)

что на выше , чем последний отображаемый блок? Завершают. Процедура преобразования (строка в число) просто правильно запрограммирована для округления ввода до следующего числа с плавающей запятой.

2.999999999999999555910790149937383830547332763671875

....... (значения между, увеличение) -> округлить

2.9999999999999997779553950749686919152736663818359375

....... (значения между, увеличение) -> округлить до 3

3

Вход преобразования должен использовать полную точность. Если число ровно половина между эти два числа fp (то есть 2.9999999999999997779553950749686919152736663818359375) округление зависит от установленных флагов. Округление по умолчанию округлено до четного, что означает, что число будет округлено до следующего четного числа.

Теперь

3 = 11. (двоичный)

2.999 ... = 10.11111111111 ...... (двоичный код)

Все биты установлены, число всегда нечетное. Это означает, что точное половинное число будет округлено, так что вы получите странный период ..... 49999, потому что он должен быть меньше точной половины, чтобы ее можно было отличить от 3.

2 голосов
/ 10 октября 2008

Я подозреваю, что преобразование 2.9999999999999997779553950749686919152736663818359374999999 в его двоичное представление будет полезным. Это, вероятно, только на 1 бит отличается от истинного 3.

1 голос
/ 16 октября 2008

Следует отметить, что число ".0000000000000007779553950749686919152736663818359374" вполне возможно Epsilon , определяемое как "наименьшее число E такое, что (1 + E)> 1."

1 голос
/ 06 октября 2008

И чтобы добавить к ответу Джона, шансы того, что он будет более производительным, чем Math.floor, ничтожно малы.

Я не знаю, использует ли JavaScript числа с плавающей точкой или какую-то библиотеку с бесконечной точностью, но в любом случае вы получите ошибки округления при такой операции, даже если она довольно хорошо определена.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...