Число — тип данных, служащий для подсчёта количества тех или иных предметов, определения иных количественных характеристик, численного сравнения, нумерации. То же: 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.