Глаза страшат, а руки делают

ЧАСТЬ ВТОРАЯ


27. Объект Number

  • создание объекта;
  • представление чисел;
  • свойства;
  • методы;
  • пример создания метода.

В этой части наших занятий мы познакомимся с объектом Number как с контейнером числового типа данных. Его собственно объектная сущность сегодня будет затронута очень поверхностно.

Так же, как объект String содержит строки текста, объект Number содержит числа. Так же, как и строки, числа, которые мы создаём, атоматически становятся экземплярами объекта.

Тип данных Number

Числа в JavaScript бывают двух видов: целые и с плавающей точкой (разделений на множество типов, как в других языках — integer, long, short, double — здесь нет). Числа с плавающей точкой имеют целую и дробную части, разделённые точкой (независимо от национальных настроек).

Эти два вида чисел не являются самостоятельными типами и не нуждаются в специальной конверсии между собой. Если, например, 32.5 мы умножим на 0.4, то сразу получим целое число 13, а не дробь 13.0, которую нужно конвертировать в целочисленное значение (как сплошь и рядом в других языках).

Создание объекта Number

Как и строку, число обычно создают как экземпляр объекта Number, простым назначением (в отличие от строки, кавычек не требуется).

var myNum = 21;

Но можно создать и новый объект с помощью конструктора:

var myNum = new Number;
myNum = 21;

В подавляющем большинстве случаев это излишне и даже вредно. Корректные примеры подобной работы с объектами мы рассмотрим в 4-й части.

Представление чисел

Экспоненциальная форма

Числа можно представлять как в обычной, так и в экспоненциальной форме, например:

2e6   // без пробелов!

Это означает: 2 × 106

Если мы выведем такое число через метод документа write(), то получим развёрнутое число в обычном представлении.

document.write(14e12);

Результат:

Основные системы счисления

Числа можно также представлять как в десятичной, так в шестнадцатеричной и восьмеричной системах.

Целые числа в десятичной форме не должны начинаться с нуля, потому что нуль является префиксом для недесятичных систем. Просто 0 — префикс для восьмеричных значений, а 0x — для шестнадцатеричных.

Например, 075 — это восьмеричное представление десятичного числа 61, а 0x75 — шестнадцатеричное представление десятичного числа 117.

Для восьмеричных значений используются цифры от 0 до 7, для шестнадцатеричных — цифро-буквенный ряд 0123456789ABCDEF. Буквы можно использовать в любом регистре.

В арифметических выражениях можно использовать любые формы чисел, но результат всегда будет представлен десятичным числом.

Представление чисел в других системах

Говоря об объекте String, мы касались метода toString(), который конвертирует любой объект в строку. При конвертации чисел в строки желательно указать как аргумент систему счисления.

Обратите внимание

Исходное число требуется заключить в скобки

(число).toString(система)

система — может принимать любые значения от 2 до 36.

(157).toString(2);  // двоичное представдение 157, равно 
(53).toString(27);  // экзотическое 27-ричное представдение 53, равно 

Но эти полученные выражения являются строками. Чтобы сделать их реальными числами в указанных системах счисления, нужно результат метода toString(система) конвертировать обратно в число. Делается это уже не методом, а функцией ядра Number(объект). О функциях ядра мы поговорим в одном из ближайших уроков, а пока обратите внимание на синтаксис, он похож на вызов функции:

var a = 1546;
var b = a.toString(2);
var c = Number(b);

Или сразу, «два в одном»:

var a = 1546;
var b = Number(a.toString(2));

Примечание

Если исходное число назначено на переменную, то её не надо брать в скобки при вызове метода toString().

Свойства объекта Number

Общие объектные свойства constructor и prototype мы затронем в уроке об объекте Object, а сейчас обратимся к специфическим свойствам объекта Number.

Эти свойства — только для чтения, то есть мы не можем их изменять.

MAX_VALUE

Максимальное число, доступное для обработки в JavaScript.

Посмотрим, что же это за число такое:

var e = Number.MAX_VALUE
document.write(e)

Результат:

1.7976931348623157e+308

Плюс в данном случае — не знак сложения, а положительная степень, то есть 1.7976931348623157 × 10308

Примечание

Обычно я демонстрирую результаты с помощью реально написанных скриптов. Но слишком много вычислений дробных чисел могут тормозить загрузку страницы, и большинство результатов в этом уроке написаны, так сказать, «от руки».

MIN_VALUE

А это — соответственно — минимальное значение. Исследуем его.

var f = Number.MIN_VALUE
document.write(f)

Результат:

5e-324

То есть 5 × 10-324

NaN

Нечисловое значение, с которым мы уже встречались.

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

NEGATIVE_INFINITY, POSITIVE_INFINITY

Когда-то считали примерно так: «один оленя, два оленя, много оленя».

JavaScript считает чуть более «продвинуто»: «один оленя, два оленя, три оленя, ... , 1.7976931348623157 × 10308 оленя, много оленя».

Это запредельное «много» выражается свойством POSITIVE_INFINITY.

При обратном процессе — делении единицы («одного оленя») на мелкие-мелкие кусочки — самым мелким считанным кусочком окажется 5 × 10-324 часть. Всё, что меньше, — это уже NEGATIVE_INFINITY.

Методы объекта Number

Примечание: так же, как и в методе toString(), во всех остальных методах исходное число требуется заключить в скобки, если оно представлено явно (а не в виде переменной).

toExponential()

Возвращает строку, представляющую число в экспоненциальном виде, с одной цифрой перед десятичной точкой.

Синтаксис:

число.toExponential(количествоЗнаков)

Аргумент количествоЗнаков задаёт точность округления после десятичной точки. Если аргумент опущен, количество цифр после десятичной точки равно количеству цифр, необходимых для представления значения.

Пример:

var myFullNumber = 4659872156831598853654127;
document.write(myFullNumber.toExponential(4))

Результат:

4.6599e+24

toFixed()

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

Синтаксис:

число.toFixed(количествоЗнаков)

Примеры:

var myDecimal1 = 46.59872156831598853654127;
var myDecimal2 = 46;
document.write(myDecimal1.toFixed(1))
document.write("<br>")
document.write(myDecimal2.toFixed(3))

Результаты:

Этот метод иногда бывает очень полезен. Например, громоздкую функцию из прошлого урока можно теперь представить так:

function anyRootPlus(x, y) {
var srcnum = Math.exp(Math.log(x) / y);
var result = (srcnum).toFixed(3);
return result;
}

А теперь вставим её в форму и протестируем:

    
  

Правда, теперь и целые числа будут с тремя нулями после точки. Но, зная поведение чисел и строк, умея работать с условными операторами и конверсией типов, не так уж сложно сделать необходимый числовой «дизайн».

toLocaleString()

Преобразует числовой объект в строковое значение с учётом национальных настроек для десятичных разделителей и разделителей между тысячами. За основу берётся национальная валюта, поэтому в русском варианте все, даже целые, числа представлены с двумя знаками после запятой (копейками):

var myDrob = 25.327;
var myMnogo = 25635120;
var myRoubl = 35;
/* сделаем покрупнее, чтобы лучше разглядеть запятые */
document.write("<h3>" + myDrob.toLocaleString() + "</h3>");
document.write("<h3>" + myMnogo.toLocaleString() + "</h3>");
document.write("<h3>" + myRoubl.toLocaleString() + "</h3>");

Результаты:

toPrecision()

Возвращает строку, представляющую число с заданным общим количеством значащих цифр.

Синтаксис:

число.toPrecision(количЦифр)

Аргумент:

количЦифр — количество цифр в отображаемой строке. Если заданное количество больше количества в исходном числе, отображаются десятичные нули.

document.write((354).toPrecision(8))

Результат:

Методы toPrecision() и toLocaleString() совместно не работают, для «русского дизайна» чисел любой точности потребуется написать собственную функцию. Написанную функцию можно сделать дополнительным методом объекта Number, нечто подобное мы уже делали с объектом Date(). Подробнее — в уроке об объекте Object.

toString()

Ну, этот метод мы уже подробно разобрали в начале урока.

Создаём собственный метод

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

Для этого нам потребуется довольно заковыристая функция, которая будет заниматься преобразованием строк и массивов, полученных из экземпляра объекта Number.

Объявим метод:

Number.prototype.toRussianString = toRussianString

Прежде чем приступим к построению функции, сформулируем задачи.

Сначала нам нужно представить число как строку и извлечь из него целую часть, дробную часть и точку-разделитель.

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

Начнём функцию с объявления нужных переменных.

function toRussianString(prec) {
/* переменные для преобразования строк */
var a = "", b = "", c, d, e;

Забегая вперёд, скажу, что десятичная часть в результате наших строковых пертурбаций утрачивает свою «дробную» сущность, и работать с ней предстоит как с ещё одним целым числом. Для работы с ней воспользуемся методом toPrecision(), и аргумент нашей функции (а заодно и нашего метода) задаёт значение именно для метода toPrecision(). Минимальный параметр этого метода — единица. А для нашей функции может понадобиться и ноль (при округлении до целого числа). И при этом правильное округление должно сработать ещё до того, как мы начнём «расчленять» объект. Поэтому сразу же при объявлении переменных обойдём этот подводный камень:

/* переменная, конвертирующая в строку
заданный экземпляр объекта */
if (prec == 0)
    var str = this.toFixed(0).toString(10);
else
    var str = this.toString(10);

Продолжаем объявление переменных.

/* переменная для возвращаемого значения */
var nr1;
/* переменные для частей «расчленёнки» */
var intpart, fractpaft, precpart, divider, dot = str.lastIndexOf(".");
/* счётчик */
var i;

Переменная dot находит позицию точки в строке указанного числа. По этой позиции отсекаем целую часть (intpart). Если число целое и точки нет, её позиция будет меньше нуля. В этом случае и разделитель (divider), и дробная часть (fractpart) должны быть пустыми строками. В противном случае на разделитель назначается запятая, а дробная часть отсекается для дальнейшей работы:

if (dot < 0)
    {intpart = str; fractpart = ""; divider = "";}
else
    {intpart = str.substring(0, dot);
    fractpart = str.substring(dot + 1, str.length);
    divider = ",";}

Работаем с целой частью. Пробелы нужны только в том случае, если в ней больше 3 символов:

if (intpart.length > 3)
{

Теперь разложим следующий «пасьянс» (всё это происходит внутри условного оператора):

Пройдём циклом по тройкам в обратном порядке.

Сначала соберём их в переменную a безо всяких добавлений, просто для того, чтобы вычислить длину образовавшейся подстроки. Ведь если общее количество цифр не делилось на 3, то слева остался хвостик из 1 или 2 цифр, и теперь мы можем выразить и его в виде подстроки, отняв от длины всей строки длину подстроки с «тройками» (переменная c). И поставим перед ним неразрывный пробел — &nbsp; (который потом будем убирать). Зачем? Поскольку группы из трёх цифр считывались в обратном порядке, этот начальный хвостик нужно поместить в конец ряда. То есть, если у нас было число, допустим, 36748521, нам нужно выстроить &nbsp;521&nbsp;748&nbsp;36, ставя перед каждой группой неразрывный пробел, чтобы был разделитель для массива (ведь нам же надо перевернуть их обратно, а это можно сделать с помощью метода массива reverse()).

В последней инструкции цикла мы расставим в тройках &nbsp; и запишем результат в переменную b.

for (i=intpart.length-3; i>=0; i-=3)
/* собираем тройки */
{a = a + intpart.substr(i, 3);
/* находим левый "хвостик" */
c = "&nbsp;" + intpart.substr(0, intpart.length-a.length);
/* расставляем в тройках разделители */
b = b + "&nbsp;" + intpart.substr(i, 3);}

При сложении строк b + c мы получаем строку, которую нужно преобразовать в массив, а затем этот массив перевернуть и снова конвертировать в строку (это всё записывается в переменную d).

d = (b+c).split("&nbsp;").reverse().toString().replace(/,/g, "&nbsp;");

Массив преобразуется в строку вместе с запятыми-разделителями, они нам не нужны. Поэтому в этой же инструкции удаляем их с помощью регулярного выражения /,/g, где /,/ — создание регулярного выражения для запятой, а g — «флажок», который показывает, что нужно заменить все образцы выражения (попросту говоря — все запятые), которые встретятся в строке. Заменяем их на всё те же неразрывные пробелы.

(Подробно о регулярных выражениях будет рассказано в последней части пособия.)

А теперь нужно кое-что ещё почистить (внутри того же условного оператора if (intpart.length > 3)).

Дело в том, что либо в начале, либо в конце нашей строки окажется лишний неразрывный пробел, состоящий из 6 символов: «&», «n», «b», «s», «p» и «;». Поэтому очистим мусор и запишем результат в переменную e:

if (d.substring(0, 1) == "&")
    e = d.substring(6, d.length-6);
else
    e = d.substring(0, d.length-6);

Вот теперь мы можем с чистой совестью закрыть весь условный оператор и написать альтернативный вариант для короткого числа, которое не нужно разбивать на «тройки».

}
else
e = intpart;

Итак, переменная e хранит целую часть числа, а мы займёмся десятичной.

Внимание! Когда мы работаем с дробной частью (с которой теперь нужно обращаться как с целой), мы должны помнить, что при prec == 0 функция «заартачится», поэтому сразу «забьём заглушку»:

if (prec != 0)
{

Теперь можно работать спокойно. Но есть ещё несколько «камешков», которые мы сейчас обойдём.

Во-первых, если дробная часть у нас, предположим, 41, а мы задаём округление до 3 знаков, то метод toPrecision, принимая нашу дробную часть за целое число, выведет 41.0, то есть надо убирать точку.

Во-вторых, если дробная часть исходного числа окажется больше 3 знаков, то метод toPrecision начнёт выдавать результат в экспоненциальной форме. С этим тоже придётся побороться.

Поэтому мы будем «чистить» результат через три переменных: precpart, precpart1 и precpart2.

Итак:

precpart = (Number(fractpart).toPrecision(prec)).toString(10)

Теперь «чистим»:

/* если дробная часть есть */
if (fractpart != "")
{
/* ищем и устраняем точку */
precpart1 = precpart.replace(".", "")
/* проаеряем, нет ли там экспоненты, */
var plus =  precpart1.lastIndexOf("e");
    /* и если она есть, */
    if (plus > 0)
    /* выдёргиваем с корнем, */
    precpart2 = precpart1.substring(0, plus);
    /* в противном случае */
    else
    /* ничего не меняем */
    precpart2 = precpart1
}
/* если дробной части нет, */
else
/* то выводим нули и опять же избавляемся от точки */
precpart2 = "," + precpart.replace(".", "")

Поскольку мы в начале указывали, что при отсутствии дробной части в исходном числе запятой нет, то на этот случай нам нужно её снова поставить.

}
else
{
/* то есть если всё-таки prec равна нулю,
просто выводим пустые строки */
precpart2 = "";
divider = "";
}

И финальный аккорд:

nr1 = e + divider + precpart2;
return nr1;
}

Вся функция целиком:

function toRussianString(prec) {
var a = "", b = "", c, d, e;
if (prec == 0)
    var str = this.toFixed(0).toString(10);
else
    var str = this.toString(10);
var nr1;
var intpart, fractpaft, precpart, divider, dot = str.lastIndexOf(".");
var i;
if (dot < 0)
    {intpart = str; fractpart = ""; divider = "";}
else
    {intpart = str.substring(0, dot);
    fractpart = str.substring(dot + 1, str.length);
    divider = ",";}
if (intpart.length > 3)
{
for (i=intpart.length-3; i>=0; i-=3)
{a = a + intpart.substr(i, 3);
c = "&nbsp;" + intpart.substr(0, intpart.length-a.length);
b = b + "&nbsp;" + intpart.substr(i, 3);}
d = (b+c).split("&nbsp;").reverse().toString().replace(/,/g, "&nbsp;");
    if (d.substring(0, 1) == "&")
        e = d.substring(6, d.length-6);
    else
        e = d.substring(0, d.length-6);
}
else
e = intpart;
if (prec != 0)
{
    precpart = (Number(fractpart).toPrecision(prec)).toString(10)
        if (fractpart != "")
        {
        precpart1 = precpart.replace(".", "")
        var plus =  precpart1.lastIndexOf("e");
            if (plus > 0)
            precpart2 = precpart1.substring(0, plus);
            else
            precpart2 = precpart1
        }
        else
        precpart2 = "," + precpart.replace(".", "")
}
else
{
precpart2 = "";
divider = "";
}
nr1 = e + divider + precpart2;
return nr1;
}

Теперь эту функцию можно вызывать как метод.

var myNumber = 2569843359.6583521
document.write(myNumber.toRussianString(3))

Результат:

Можете поместить конструктор метода и функцию в библиотеку — то есть в файл.js, который можно вызывать из кода web-страниц. По мере написания методов к разным объектам можно рассортировать методы по библиотечным файлам для этих объектов и пользоваться дополнительными методами.

Введите число в англо-американском формате
Введите количество знаков для округления

Домашнее задание

Из функции извлечения корня из прошлого урока сделайте метод для объекта Number.

Для особо въедливых: усовершенствуйте сегодняшний метод, чтобы он мог округлять ещё и до десятков, сотен и т.д. (при введении отрицательного параметра для аргумента), а также установите параметр, допустим, 0 для аргумента по умолчанию.


Итак, мы узнали:

что такое объект Number.

А также сделали:

дополнительный метод для него.


К следующему уроку >>


 010548