Число

Число — тип данных, служащий для подсчёта количества тех или иных предметов, определения иных количественных характеристик, численного сравнения, нумерации. То же: number.

Числа можно рассматривать как термы и делать с ними всё то, что можно делать с термами вообще. Числа можно использовать при построении более сложных термов. Например, кортеж {age, 18} тоже является термом, но при этом состоит из атома age и числа 18.

Числа в Эрланге бывают двух основных типов:

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

1> 5 + 10.
15
2> 5 + 10.0. 
15.0
3> 10 / 2.
5.0

У умножения и деления приоритет, конечно, выше, чем у сложения и вычитания.

1> 5+7*2-10/0.1.
-81.0

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

1> (5+7)*(2-10)/0.1.
-960.0

В Эрланге действует точная целочисленная арифметика — сколько вам цифр в целом числе нужно, столько и получите. Попробуйте перемножить в оболочке два целых числа, в каждом из которых пятьдесят цифр. Можно поэтому не беспокоиться о том, что выбрали не тот тип для целого числа, в результате чего получится переполнение, приводящее к ошибке.

Если перед какой-нибудь числовой переменной поставить знак минус, значение переменной меняет знак (с плюса на минус или наоборот).

1> A = 777.
777
2> B = -A. 
-777

Обращаем внимание на пунктуацию. В вещественном числе после точки пробел недопустим.

1> 3.14. 
3.14
2> 3. 14. 
3
3> 14. 
14

Литерал бинарника выглядит как последовательность целых чисел (0..255), заключённая в двойные угловые скобки:

1> <<1,10,100,200>>.
<<1,10,100,200>>

Для сложных манипуляций с целыми числами можно использовать побитовые операторы.

Для “вытаскивания” чисел из строки можно использовать регулярные выражения.

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

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

Стандартные математические функции находятся в библиотечном модуле math.

Целочисленное деление

Допустим, у трёх братьев есть 100 лампочек. Их они хотят разделить по-братски, то есть чтобы всем досталось поровну. Если какие-то лампочки останутся, они их просто выкинут. В Эрланге им поможет оператор div.

1> 100 div 3. 
33

С отрицательными числами оператор работает полностью симметрично:

2> -100 div 3.
-33

Целый остаток от деления

“У нас было на троих 100 лампочек, — вспоминают братья. — Мы их поделили по-братски, а остаток выбросили. Сколько лампочек мы выбросили?” Тут им поможет оператор rem.

1> 100 rem 3. 
1
2> -100 rem 3.
-1

Числовые литералы

Обычное (десятеричное) представление целого:

1> A = 123.
123

Разделение по разрядам (для удобочитаемости):

2> B = 1_234_567.
1234567

Если по какой-то причине надо выделять не по три знака, а по два (считаем сотнями), так тоже можно. Компилятор просто опускает символы подчёркивания.

3> C = 1_23_45_67.
1234567

Бинарное представление выглядит так:

4> E = 2#100001001.
265

Шестнадцатеричное — так и так (регистр не важен):

5> F = 16#89abcd.
9022413
6> G = 16#89ABCD. 
9022413

И вообще основания могут быть произвольными: от 2 до 36 включительно. В литералах используются лишь цифры и буквы латинского алфавита, то есть возможно 36 знаков. С этим и связано верхнее ограничение — 36.

9> I = 7#2345. 
866
10> J = 17#cafe. 
62115
11> K = 21#cake. 
115976
12> L = 36#sugar. 
48450051

Научный формат вещественных чисел (включая порядок числа):

14> -3.14e10.
-3.14e10

Научный формат предполагает, что должна быть строчная, а не заглавная e. Но Эрланг поймёт и заглавную. Также научный формат предполагает, что первая цифра 1..9, а потом следует точка. Но Эрланг поймёт, если мы поставим точку иначе. Но выведет он число в правильном формате.

20> -314.0E8. 
-3.14e10
21> -0.314E11. 
-3.14e10

Генерация числовых последовательностей

Если нам нужно сгенерировать какую-то определённую последовательность чисел, в Эрланге для этого есть удобные инструменты.

Для генерации целых чисел в некотором диапазоне удобно применять lists:seq/2 и lists:seq/3. Третий аргумент — шаг перебора.

1> lists:seq(-10, 10).
[-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10]
2> lists:seq(-100, 100, 11). 
[-100,-89,-78,-67,-56,-45,-34,-23,-12,-1,10,21,32,43,54,65,
 76,87,98]

Вместе с lists:seq/* удобно применять генераторы и в целом обработчики списка. Допустим, нам надо сгенерировать последовательность значений (X+Y)/(X-Y), где X и Y находятся в некотором диапазоне. При этом случай, когда знаменатель равен нулю, надо отбросить.

1> [ (X+Y)/(X-Y) || X <- lists:seq(1,5), Y <- lists:seq(1,4), X /= Y ]. 
[-3.0,-2.0,-1.6666666666666667,3.0,-5.0,-3.0,2.0,5.0,-7.0,
 1.6666666666666667,3.0,7.0,1.5,2.3333333333333335,4.0,9.0]

Как видите, очень изящно смотрится.

Для генерации вещественных чисел нет (вроде бы) функции, аналогичной lists:seq, но это не очень большая проблема.

1> [ X/10 || X <- lists:seq(10, 50, 2) ].
[1.0,1.2,1.4,1.6,1.8,2.0,2.2,2.4,2.6,2.8,3.0,3.2,3.4,3.6,
 3.8,4.0,4.2,4.4,4.6,4.8,5.0]

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

#!/usr/bin/escript

main(_Arg) ->
    L = lists:reverse(gen([1.0], 5.0, 2/7)),
    io:format("~p~n", [L]).

gen([H|T], Limit, Step) when H+Step>Limit  -> [H|T];
gen([H|T], Limit, Step) -> gen([(H+Step)|[H|T]], Limit, Step). 

Запустив этот скрипт, получим числа в диапазоне от 1 до 5 с шагом 2/7:

[1.0,1.2857142857142856,1.5714285714285712,1.8571428571428568,
 2.1428571428571423,2.428571428571428,2.7142857142857135,2.999999999999999,
 3.2857142857142847,3.5714285714285703,3.857142857142856,4.1428571428571415,
 4.428571428571427,4.714285714285713,4.999999999999998]

От перестановки множителей…

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

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

С вещественными числами не так. Например, результат выражения 1/3 есть бесконечная череда цифр (как в десятичном, так и двоичном формате). Чтобы его представить точно, потребуется бесконечное количество битов. Поэтому надо не забывать про то, что вычисления с вещественными числами всегда неточны.

И из-за этого может получиться так, что от перестановки множителей произведение меняется.

1> 1.78*100*1.78*100. 
31684.000000000004
2> 1.78*100*100*1.78. 
31684.0

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

Вещественные числа можно сравнивать с помощью оператора ==, но это очень опасно и быстро приведёт к смысловой ошибке.

3> 3.14 == 3.14. 
true
4> 1.78*100*1.78*100 == 1.78*100*100*1.78. 
false

Сравнение вещественных чисел на равенство

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

1> X = 3.14.
3.14
2> Y = 3.1415.
3.1415
3> abs(X - Y) < 0.0001.
false

Более универсальным является сравнение вещественных чисел относительным способом (с помощью деления). Мы, например, решаем, что нас устраивает точность 99.99 %, её и придерживаемся.

1> X = 3.14.
3.14
2> Y = 3.1415.
3.1415
3> abs(1 - X/Y) < 0.0001. 
false

В каких-то случаях удобнее просто верно выбрать единицу измерения. Если мы измеряем время с точностью до миллисекунды, мы можем считать не секунды (ведь тогда придётся добавлять дробную часть), а микросекунды или наносекунды. И тогда можно использовать целые числа.

Чтобы в сравнение случайно не затесалось вещественное число, можно добавить проверку с помощью бифа is_integer/1.


© Алексей Карманов, 2024.