«Лучший» способ зависит от:
Только после на эти соображения мы можем подумать о подходящем методе (ах) и скорости!
Согласно спецификации ECMAScript 262:
все числа (тип
Number
) в javascript представлены / сохранены в:
Формат «
IEEE 754 с плавающей запятой двойной точности (binary64) ».
Таким образом, целые числа
также представлены в
том же формате с плавающей запятой (как числа без дроби).
Примечание: большинство реализаций do используют более эффективные (для скорости и объема памяти) целочисленные типы внутренне , когда это возможно!
Поскольку этот формат хранит 1 знаковый бит, 11 экспонентных бит и первые 53 значащих бита («мантисса»), мы можем сказать, что: only Number
-значения между -2<sup>52</sup>
и +2<sup>52</sup>
могут иметь дробь.
Другими словами: все представимые положительные и отрицательные Number
-значения между 2<sup>52</sup>
до (почти) 2<sup>(2<sup>11</sup>/2=1024)</sup>
(в этот момент формат называет это в день Infinity
) уже являются целыми числами (внутренне округлены, поскольку не осталось битов для представления оставшихся дробных и / или младших значащих целых цифр).
И вот первая 'гоча':
Вы не можете управлять внутренним режимом округления Number
-результатов для встроенных преобразований Literal / String в float (режим округления: IEEE 754-2008 «округление до ближайшего, привязка к четному») и встроенная арифметика операции (режим округления: IEEE 754-2008 «округление до ближайшего»).
Например:
2<sup>52</sup>+0.25 = 4503599627370496.25
округляется и сохраняется как: 4503599627370496
2<sup>52</sup>+0.50 = 4503599627370496.50
округляется и сохраняется как: 4503599627370496
2<sup>52</sup>+0.75 = 4503599627370496.75
округляется и сохраняется как: 4503599627370497
2<sup>52</sup>+1.25 = 4503599627370497.25
округляется и хранится как: 4503599627370497
2<sup>52</sup>+1.50 = 4503599627370497.50
округляется и сохраняется как: 4503599627370498
2<sup>52</sup>+1.75 = 4503599627370497.75
округляется и сохраняется как: 4503599627370498
2<sup>52</sup>+2.50 = 4503599627370498.50
округляется и сохраняется как: 4503599627370498
2<sup>52</sup>+3.50 = 4503599627370499.50
округляется и сохраняется как: 4503599627370500
Чтобы контролировать округление, вашему Number
нужна дробная часть (и, по крайней мере, один бит для представления этого), иначе ceil / floor / trunc / near возвращает целое число, которое вы ввели в него.
Для правильного ceil / floor / trunc числа с числом до x значащих дробных десятичных цифр (ов), мы заботимся только о том, чтобы соответствующее младшее и старшее десятичное дробное значение все еще давало нам двоичное дробное значение после округления (поэтому не блокируется) или напоследок к следующему целому числу).
Так, например, если вы ожидаете «правильного» округления (для ceil / floor / trunc) до 1 значащего дробного десятичного знака (x.1 to x.9
), нам нужно как минимум 3 бита (не 4) для дайте нам a двоичное дробное значение:
0.1
ближе к 1/(2<sup>3</sup>=8)=0.125
, чем 0
, а 0.9
ближе к 1-1/(2<sup>3</sup>=8)=0.875
, чем 1
.
только до ±2<sup>(53-3=50)</sup>
будут все представляемые значения иметь ненулевую двоичную дробь не более чем первая значащая десятичная дробная цифра (значения x.1
до x.9
).
Для 2 десятичных знаков ±2<sup>(53-6=47)</sup>
, для 3 десятичных знаков ±2<sup>(53-9=44)</sup>
, для 4 знаков после запятой ±2<sup>(53-13=40)</sup>
, для 5 знаков после запятой ±2<sup>(53-16=37)</sup>
, для 6 знаков после запятой ±2<sup>(53-19=34)</sup>
, для 7 знаков после запятой ±2<sup>(53-23=30)</sup>
, для 8 знаков после запятой ±2<sup>(53-26=27)</sup>
, для 9 десятичных знаков ±2<sup>(53-29=24)</sup>
, для 10 знаков после запятой ±2<sup>(53-33=20)</sup>
, для 11 знаков после запятой ±2<sup>(53-36=17)</sup>
и т. Д.
A "Безопасное целое число" в javascript - это целое число:
- , который может быть точно представлен как число двойной точности IEEE-754, и
- чье представление IEEE-754 не может быть результатом округления любого другого целого числа для соответствия представлению IEEE-754
(даже если ±2<sup>53</sup>
(как точная степень 2) можно точно представить, это , а не безопасное целое число, поскольку оно также могло бы быть ±(2<sup>53</sup>+1)
, прежде чем оно было округлено, чтобы соответствовать максимум 53 старших разряда).
Это эффективно определяет подмножество (безопасно представимых) целых чисел между -2<sup>53</sup>
и +2<sup>53</sup>
:
- от:
-(2<sup>53</sup> - 1) = -9007199254740991
(включительно)
(константа предоставляется как статическое свойство Number.MIN_SAFE_INTEGER
начиная с ES6)
до: +(2<sup>53</sup> - 1) = +9007199254740991
(включительно)
(константа предоставляется как статическое свойство Number.MAX_SAFE_INTEGER
начиная с ES6)
Тривиальное polyfill для этих 2 новых констант ES6:
Number.MIN_SAFE_INTEGER || (Number.MIN_SAFE_INTEGER=
-(Number.MAX_SAFE_INTEGER=9007199254740991) //Math.pow(2,53)-1
);
Начиная с ES6, существует также бесплатный статический метод Number.isSafeInteger()
, который проверяет, является ли переданное значение типа Number
и является целым числом в безопасном целочисленном диапазоне (возвращая логическое значение true
или false
).
Примечание: также вернет false
для: NaN
, Infinity
и, очевидно, String
(даже если оно представляет число).
Polyfill пример :
Number.isSafeInteger || (Number.isSafeInteger = function(value){
return typeof value === 'number' &&
value === Math.floor(value) &&
value < 9007199254740992 &&
value > -9007199254740992;
});
ECMAScript 2015 / ES6 предоставляет новый статический метод Math.trunc()
усечь число с плавающей точкой до целого числа:
Возвращает неотъемлемую часть числа x, удаляя все дробные цифры. Если x уже является целым числом, результатом будет x.
Или, проще говоря ( MDN ):
В отличие от других трех математических методов: Math.floor()
, Math.ceil()
и Math.round()
, способ работы Math.trunc()
очень прост и понятен:
просто обрежьте точку и цифры за ней, независимо от того, является ли аргумент положительным числом или отрицательным числом.
Мы можем дополнительно объяснить (и заполнить) Math.trunc()
так:
Math.trunc || (Math.trunc = function(n){
return n < 0 ? Math.ceil(n) : Math.floor(n);
});
Обратите внимание, что полезная нагрузка указанного полифилла может потенциально быть лучше предварительно оптимизирована двигателем по сравнению с:
Math[n < 0 ? 'ceil' : 'floor'](n);
Использование : Math.trunc(/* Number or String */)
Ввод : (целое число или число с плавающей запятой) Number
(но с удовольствием попытается преобразовать строку в число)
Вывод : (целое число) Number
(но с радостью попытается преобразовать число в строку в контексте строки)
Диапазон : -2^52
до +2^52
(помимо этого мы должны ожидать, что 'ошибки округления' (и в какой-то момент научная / экспоненциальная запись) просты и просты, потому что наш Number
вход в IEEE 754 уже потерян дробная точность: поскольку числа от ±2^52
до ±2^53
уже внутренне округлены целые числа (например, 4503599627370509.5
внутренне уже представлены как 4503599627370510
) и за пределами ±2^53
целые числа также теряют точность ( полномочия 2)).
Преобразование с плавающей точкой в целое число путем вычитания Остаток (%
) отклонения на 1
:
Пример: result = n-n%1
(или n-=n%1
)
Это также должно усечь плавает. Так как оператор «Остаток» имеет более высокий приоритет , чем вычитание, мы фактически получаем: (n)-(n%1)
.
Для положительных чисел легко увидеть, что это минимальное значение: (2.5) - (0.5) = 2
,
для отрицательных чисел это означает значение: (-2.5) - (-0.5) = -2
(потому что --=+
так (-2.5) + (0.5) = -2
).
Поскольку вход и выход равны Number
, мы должны получить такой же полезный диапазон и выход по сравнению с ES6 Math.trunc()
(или это полифилл).
Примечание: жесткий я боюсь (не уверен), что могут быть различия: потому что мы делаем арифметику (которая внутренне использует режим округления "nearTiesEven" (он же Банковское округление)) на исходном номере ) и второе производное число (дробь), которое, по-видимому, вызывает сложные ошибки digital_representation и арифметического округления, что потенциально может возвращать число с плавающей запятой после всех ..
Преобразование с плавающей точкой в целое число (ab-) с использованием побитовых операций :
Это работает с помощью внутренне принудительного преобразования (с плавающей запятой) Number
(усечение и переполнение) в 32-разрядное целочисленное значение со знаком (дополнение к двум) с помощью побитовой операции на Number
(и результат преобразуется обратно в (с плавающей точкой) Number
, который содержит только целочисленное значение).
Опять же, ввод и вывод равен Number
(и снова тихое преобразование из String-input в Number и Number-output to String).
Более важный жест (и обычно забытый и не объясненный):
в зависимости от побитовой операции и знака числа , полезный диапазон будет ограничен между:
-2^31
до +2^31
(например, ~~num
или num|0
или num>>0
) ИЛИ 0
до +2^32
(num>>>0
).
Это должно быть дополнительно пояснено следующей таблицей поиска (содержащей все «критические» примеры):
n | n>>0 OR n<<0 OR | n>>>0 | n < 0 ? -(-n>>>0) : n>>>0
| n|0 OR n^0 OR ~~n | |
| OR n&0xffffffff | |
----------------------------+-------------------+-------------+---------------------------
+4294967298.5 = (+2^32)+2.5 | +2 | +2 | +2
+4294967297.5 = (+2^32)+1.5 | +1 | +1 | +1
+4294967296.5 = (+2^32)+0.5 | 0 | 0 | 0
+4294967296 = (+2^32) | 0 | 0 | 0
+4294967295.5 = (+2^32)-0.5 | -1 | +4294967295 | +4294967295
+4294967294.5 = (+2^32)-1.5 | -2 | +4294967294 | +4294967294
etc... | etc... | etc... | etc...
+2147483649.5 = (+2^31)+1.5 | -2147483647 | +2147483649 | +2147483649
+2147483648.5 = (+2^31)+0.5 | -2147483648 | +2147483648 | +2147483648
+2147483648 = (+2^31) | -2147483648 | +2147483648 | +2147483648
+2147483647.5 = (+2^31)-0.5 | +2147483647 | +2147483647 | +2147483647
+2147483646.5 = (+2^31)-1.5 | +2147483646 | +2147483646 | +2147483646
etc... | etc... | etc... | etc...
+1.5 | +1 | +1 | +1
+0.5 | 0 | 0 | 0
0 | 0 | 0 | 0
-0.5 | 0 | 0 | 0
-1.5 | -1 | +4294967295 | -1
etc... | etc... | etc... | etc...
-2147483646.5 = (-2^31)+1.5 | -2147483646 | +2147483650 | -2147483646
-2147483647.5 = (-2^31)+0.5 | -2147483647 | +2147483649 | -2147483647
-2147483648 = (-2^31) | -2147483648 | +2147483648 | -2147483648
-2147483648.5 = (-2^31)-0.5 | -2147483648 | +2147483648 | -2147483648
-2147483649.5 = (-2^31)-1.5 | +2147483647 | +2147483647 | -2147483649
-2147483650.5 = (-2^31)-2.5 | +2147483646 | +2147483646 | -2147483650
etc... | etc... | etc... | etc...
-4294967294.5 = (-2^32)+1.5 | +2 | +2 | -4294967294
-4294967295.5 = (-2^32)+0.5 | +1 | +1 | -4294967295
-4294967296 = (-2^32) | 0 | 0 | 0
-4294967296.5 = (-2^32)-0.5 | 0 | 0 | 0
-4294967297.5 = (-2^32)-1.5 | -1 | +4294967295 | -1
-4294967298.5 = (-2^32)-2.5 | -2 | +4294967294 | -2
Примечание 1: последний столбец имеет расширенный диапазон 0
до -4294967295
с использованием (n < 0 ? -(-n>>>0) : n>>>0)
.
Примечание 2: поразрядно вводит свои собственные издержки преобразования ( s ) (серьезность против Math
зависит от фактической реализации, поэтому побитовая может быть быстрее (часто в старых исторических браузерах)).
Очевидно, что если ваше число с плавающей запятой для начала было
String
,
parseInt(/*String*/, /*Radix*/)
будет правильным выбором для разбора его на целое число
Number
.
parseInt()
также будет
усекать (для положительных и отрицательных чисел).
Диапазон
снова ограничен плавающей точкой двойной точности IEEE 754, как объяснено выше для метода (ов)
Math
.
Наконец, если у вас есть String
и ожидаете String
в качестве выходных данных, вы также можете обрезать основную точку и дробь (что также дает вам больший точный диапазон усечения по сравнению с плавающей точкой двойной точности IEEE 754 (±2^52
))!
EXTRA:
Из вышеприведенной информации у вас теперь должно быть все, что вам нужно знать.
Если, например, вы хотите, чтобы округлился от нуля (он же округлился до бесконечности ), вы можете изменить Math.trunc()
polyfill, для пример :
Math.intToInf || (Math.intToInf = function(n){
return n < 0 ? Math.floor(n) : Math.ceil(n);
});