Ruby/Подробнее о числах

Подробнее о числах

править

Изначально числа представлены тремя типами: два целых типа (классы Fixnum и Bignum) и один с плавающей запятой (класс Float). Возможно подключение дополнительных типов, например, комплексных и рациональных чисел, но пока ограничимся тремя.

Целые числа

править

Целые числа в Ruby не ограничены по величине, то есть могут хранить сколь угодно большие значения. Для обеспечения такого волшебного свойства было создано два класса. Один из них хранит числа меньше   (по модулю), а второй — всё, что больше. По сути, для больших чисел создаётся массив из маленьких, а раз массив не имеет ограничений по длине, то и число получается неограниченным по значению.

 

Если, например, написать

puts 54308428790203478762340052723346983453487023489987231275412390872348475 **
    54308428790203478762340052723346983453487023489987231275412390872348475

то интерпретатор начинает ругаться и выдаёт Infinity. Можно подумать, что он не может обработать такое большое число. Конечно же это не так, что становится ясно после прочтения выдаваемого интерпретатором предупреждения. Там написано, что показатель степени (то есть второе число) не может быть типа Bignum (чтобы не пришлось слишком много считать).

 

Как ни странно,   определяется как Bignum

(2**30).class  #=> Bignum

Однако, целое число, меньшее (по модулю)  определяется как Fixnum

((2**30)-1).class    #=> Fixnum
(-(2**30)+1).class  #=> Fixnum

Так-то!

Как только число типа Fixnum становится больше или равным  (по модулю), то оно преобразуется к классу Bignum. Если число типа Bignum становится меньше  , то оно преобразуется к типу Fixnum.

При записи целых чисел сначала указывается знак числа (знак + обычно не пишется). Далее идёт основание системы счисления, в которой задаётся число (если оно отлично от десятичной): 0 — для восьмеричной, 0x — для шестнадцатеричной, 0b — для двоичной. Затем идёт последовательность цифр, выражающих число в данной системе счисления. При записи чисел можно использовать символ подчёркивания, который игнорируется при обработке. Чтобы закрепить вышесказанное, посмотрим примеры целых чисел:

# тип Fixnum
123_456                 # подчёркивание игнорируется
-567                    # отрицательное число
0xbad                   # шестнадцатеричное число
0377                    # восьмеричное
-0b101010               # отрицательное двоичное
0b0101_0101             # подчёркивание игнорируется

# тип Bignum
123_456_789_123_456     # подчёркивание игнорируется
-123_456_789_123_456    # отрицательное
07777777777777777777    # восьмеричное большое

Как видно из примеров, маленькие целые (Fixnum) и больши́е целые (Bignum) отличаются только значением.

Числа с плавающей запятой

править

Числа с плавающей запятой задаются только в десятичной системе счисления, при этом для отделения дробной части используется символ . (точка). Для задания чисел с плавающей запятой может быть применена и экспоненциальная форма записи: два различных представления 0.1234e2 и 1234e-2 задают одно и то же число 12.34.

# тип Float
-12.34      # отрицательное число с плавающей запятой
0.1234е2    # экспоненциальная форма для числа 12.34
1234е-2     # экспоненциальная форма для числа 12.34

Следует упомянуть, что чи́сла с плавающей запятой имеют фиксированный диапазон значений в отличие от целых чисел. Этот недостаток легко устраняется подключением библиотеки mathn (подключаются рациональные и комплексные числа).

Семейный портрет чисел

править
 
Числовые типы данных

В отличие от большинства элементарных типов данных, числа обладают своей иерархией. Все числа в Ruby наследованы от класса Numeric (числовой). Поэтому, если хотите добавить новый метод ко всем числам, то нужно расширять именно этот класс. Далее идёт деление чисел: Integer (целое), Float (число с плавающей запятой) и Complex (комплексное). При желании можно добавить и Rational (рациональное), но на данном семейном портрете оно отсутствует.

От класса Integer наследуются два класса: Fixnum (фиксированное целое) и Bignum (большое целое). К первому относятся все числа, по модулю меньшие  , а ко второму — все остальные.

  • Fixnum автоматически становится Bignum по превышении   по модулю. И наоборот, падая ниже, Bignum преобразуется в Fixnum.
  • Из отрицательного числа можно получить корень, когда подключена библиотека mathn. Он будет типа Complex.
  • Как только число типа Complex лишается мнимой части, то оно становится либо Integer (Fixnum или Bignum), либо Float (в зависимости от типа действительной части). Если подключена библиотека mathn, может получиться число типа Rational.
  • Если в результате арифметических действий в числе типа Rational знаменатель приравнивается 1, то оно преобразуется к числу Integer.

Арифметические операции

править

Арифметические операции в Ruby обычны: сложение (+), вычитание (-), умножение (*), деление (/), получение остатка от деления (%), возведение в степень (**).

6 + 4     #=> 10
6 - 4     #=> 2
6 * 4     #=> 24
6 / 4     #=> 1
6 % 4     #=> 2
6 ** 4    #=> 1296

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

Порядок вычисления обычный. Для изменения приоритета применяются круглые скобки:

2 + 2 * 2      #=> 6
(2 + 2) * 2    #=> 8

Первое, что бросается в глаза, — результат арифметической операции двух целых чисел всегда будет целым. Особенно это видно при делении:

1/3    #=> 0
2/3    #=> 0
3/3    #=> 1

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

Одна вторая в Ruby ноль,
А три вторые — единица.
Запомнить надо эту соль,
Чтоб результату не дивиться.

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

6.0 + 4      #=> 10.0
6 - 4.0      #=> 2.0
6.0 * 4.0    #=> 24.0
6.0 / 4      #=> 1.5 (одно из чисел с плавающей запятой, значит результат с плавающей запятой)
6.0 % 4      #=> 2.0
6 ** 4.0     #=> 1296.0

Лучше проверить эти сведения самостоятельно.

Поразрядная арифметика

править
Знак операции Название
& Побитовое «и»
| Побитовое «или»
^ Побитовое «исключающее или»
<< Побитовый сдвиг влево
>> Побитовый сдвиг вправо
~ Побитовая инверсия

Операции побитовой арифметики заимствованы из языка Си. На этот раз без всяких экзотических особенностей.

6 & 4     #=> 4
6 | 4     #=> 6
6 ^ 4     #=> 2
6 << 4    #=> 96
6 >> 4    #=> 0 (чересчур намного сдвинули)
~4        #=> -5 (операция только над одним аргументом)

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

Операции с присваиванием

править

Часто можно встретить выражения вида:

number_one += number_two

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

number_one = number_one + number_two

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

string = "едем"
string += ", "
string *= 3
string               #=> "едем, едем, едем, "

array = [1, 2, 3]
array += [4, 5]
array                #=> [1, 2, 3, 4, 5]

При определении метода + метод += вы получаете в подарок. Это правило касается всех бинарных операций, обозначаемых значками.

Методы явного преобразования типов

править
Метод Операция
to_f Преобразовать в число с плавающей запятой
to_i Преобразовать в целое число
to_s Преобразовать в строку
to_a Преобразовать в массив (до версии 1.9+)

Методы преобразования типов в Ruby традиционно начинаются с приставки to_. Последующая буква — это сокращение от названия класса, в который происходит преобразование (fFloat — число с плавающей запятой, iInteger — целое, sString — строка, aArray — массив). Посмотрим их действие на примере:

7.to_f      #=> 7.0
7.9.to_i    #=> 7
7.to_s      #=> "7"
"7".to_a    #=> ["7"]

Случайное число

править

Часто требуется получить случайное число. Пример:

rand(100)    #=> 86
rand         #=> 0.599794231588021

В первом случае метод rand возвращает целое число в диапазоне от 0 до 99 (на единицу меньше 100). Во втором случае метод rand возвращает число с плавающей запятой в диапазоне от 0.0 до 1.0 включительно. Различие в результате обусловлено передаваемым параметром:

  • если передаётся параметр (в данном случае 100), то генерируется целое случайное число (в диапазоне 0..N-1, где N — передаваемый аргумент);
  • если параметр отсутствует, то генерируется число с плавающей запятой в диапазоне от 0.0 до 1.0.

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

srand 123
Array.new(5){ rand(100) }    #=> [66, 92, 98, 17, 83]

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

Если вызвать srand без параметра или не вызывать его вообще, то номер «случайной» последовательности выбирается случайным образом.

Хитрости

править

Задача: выдать целое число в двоичной системе счисления.

start_number = 1234
puts sprintf("%b", start_number)    # метод sprintf заимствован из Си
puts start_number.to_s(2)           # современный метод — означает «по основанию», 
                                    # аргументом может служить не только 8 и 16, но и 5, 30…
                                    # На самом деле, основание не может превышать 36, 
                                    # что вполне объяснимо — 10 цифр и 26 букв латинского алфавита.

Поменять порядок цифр данного числа на обратный:

start_number = 1234
puts start_number.to_s.reverse    # метод reverse переворачивает строку

Получить значение N-го двоичного разряда данного целого числа:

start_number, N = 1234, 5
puts start_number[N]

Поменять целочисленные значения двух переменных без использования третьей переменной:

number_one, number_two = 134, 234
number_one, number_two = number_two, number_one

Округлить число с плавающей запятой до двух разрядов:

float_integer = 3.1415926535
puts (float_integer * 100).to_i.to_f / 100
puts ((float_integer + 0.005) * 100).to_i / 100.0
puts sprintf("%.2f", float_integer).to_f             # полуСишный способ =)

На самом деле во второй строке оставляются два знака после запятой, а остальные просто отбрасываются безо всяких округлений, в то время как в третьей строке действительно происходит округление до двух знаков после запятой. Это легко проверить попытавшись округлить до трёх знаков после запятой:

float_integer = 3.1415926535
puts (float_integer * 1000).to_i.to_f / 1000            #=>3.141
puts ((float_integer + 0.0005) * 1000).to_i / 1000.0    #=>3.142
puts sprintf("%.3f", float_integer).to_f                #=>3.142

Но всё же лучше:

float_integer = 3.1415926535
float_integer.round 3                #=>3.142 (возможно, что round округляет только до целых)