Документация Engee

Целые числа и числа с плавающей запятой

Целочисленные значения и значения с плавающей запятой являются базовыми конструктивными блоками арифметики и вычислений. Встроенные представления таких значений называются числовыми примитивами, а представления целых чисел и чисел с плавающей запятой в виде непосредственных значений в коде называются числовыми литералами. Например, 1 — это целочисленный литерал, а 1.0 — литерал с плавающей запятой. Их двоичные представления в памяти в виде объектов являются числовыми примитивами.

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

Ниже приведены примитивные числовые типы Julia.

  • Целочисленные типы:

Тип Со знаком? Количество битов Наименьшее значение Наибольшее значение

Int8

8

--2^7

2^7 — 1

UInt8

8

0

2^8 — 1

Int16

16

--2^15

2^15 — 1

UInt16

16

0

2^16 — 1

Int32

32

--2^31

2^31 — 1

UInt32

32

0

2^32 — 1

Int64

64

--2^63

2^63 — 1

UInt64

64

0

2^64 — 1

Int128

128

--2^127

2^127 — 1

UInt128

128

0

2^128 — 1

Bool

Н/Д

8

false (0)

true (1)

  • Типы с плавающей запятой:

Тип Точность Количество битов

Float16

половинная

16

Float32

одинарная

32

Float64

двойная

64

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

Целые числа

Литеральные целые числа представлены стандартным образом.

julia> 1
1

julia> 1234
1234

Тип по умолчанию для целочисленного литерала зависит от того, какую архитектуру имеет целевая система — 32- или 64-разрядную:

# 32-разрядная система:
julia> typeof(1)
Int32

# 64-разрядная система:
julia> typeof(1)
Int64

Внутренняя переменная Julia Sys.WORD_SIZE указывает, является ли целевая система 32-разрядной или 64-разрядной:

# 32-разрядная система:
julia> Sys.WORD_SIZE
32

# 64-разрядная система:
julia> Sys.WORD_SIZE
64

Julia также определяет типы Int и UInt, которые являются псевдонимами для системных собственных целочисленных типов со знаком и без знака, соответственно:

# 32-разрядная система:
julia> Int
Int32
julia> UInt
UInt32

# 64-разрядная система:
julia> Int
Int64
julia> UInt
UInt64

Большие целочисленные литералы, которые не могут быть представлены с использованием только 32 битов, но могут быть представлены в 64 битах, всегда создают 64-разрядные целые числа, независимо от типа системы:

# 32-или 64-разрядная система:
julia> typeof(3000000000)
Int64

Целые числа без знака вводятся и выводятся с использованием префикса 0x и шестнадцатеричных (по основанию 16) цифр 0-9a-f (заглавные цифры A-F также подходят для ввода). Размер значения без знака определяется количеством используемых шестнадцатеричных цифр.

julia> x = 0x1
0x01

julia> typeof(x)
UInt8

julia> x = 0x123
0x0123

julia> typeof(x)
UInt16

julia> x = 0x1234567
0x01234567

julia> typeof(x)
UInt32

julia> x = 0x123456789abcdef
0x0123456789abcdef

julia> typeof(x)
UInt64

julia> x = 0x11112222333344445555666677778888
0x11112222333344445555666677778888

julia> typeof(x)
UInt128

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

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

julia> x = 0b10
0x02

julia> typeof(x)
UInt8

julia> x = 0o010
0x08

julia> typeof(x)
UInt8

julia> x = 0x00000000000000001111222233334444
0x00000000000000001111222233334444

julia> typeof(x)
UInt128

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

  • 0x1 и 0x12 являются литералами UInt8,

  • 0x123 и 0x1234 являются литералами UInt16,

  • 0x12345 и 0x12345678 являются литералами UInt32,

  • 0x123456789 и 0x1234567890adcdef являются литералами UInt64 и т. д.

Даже если присутствуют начальные нули, которые не влияют на значение, они учитываются при определении размера хранения литерала. Таким образом, 0x01 является UInt8, а 0x0001 — UInt16.

Так пользователи могут контролировать размер.

Литералы без знака (начинающиеся с 0x), которые кодируют целые числа, слишком большие для представления в виде значений UInt128, будут создавать вместо этого значения типа BigInt. Он не является типом без знака, но это единственный встроенный тип, достаточно большой для представления таких больших целочисленных значений.

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

julia> -0x2
0xfe

julia> -0x0002
0xfffe

Минимальные и максимальные представимые значения примитивных числовых типов, такие как целые числа, задаются функциями typemin и typemax:

julia> (typemin(Int32), typemax(Int32))
(-2147483648, 2147483647)

julia> for T in [Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128]
           println("$(lpad(T,7)): [$(typemin(T)),$(typemax(T))]")
       end
   Int8: [-128,127]
  Int16: [-32768,32767]
  Int32: [-2147483648,2147483647]
  Int64: [-9223372036854775808,9223372036854775807]
 Int128: [-170141183460469231731687303715884105728,170141183460469231731687303715884105727]
  UInt8: [0,255]
 UInt16: [0,65535]
 UInt32: [0,4294967295]
 UInt64: [0,18446744073709551615]
UInt128: [0,340282366920938463463374607431768211455]

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

Поведение при переполнении

В Julia превышение максимального представимого значения заданного типа приводит к появлению циклического переноса:

julia> x = typemax(Int64)
9223372036854775807

julia> x + 1
-9223372036854775808

julia> x + 1 == typemin(Int64)
true

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

Ниже приведены пример поведения переполнения и возможные способы его разрешения.

julia> 10^19
-8446744073709551616

julia> big(10)^19
10000000000000000000

Ошибки деления

Целочисленное деление (функция div) имеет два исключительных случая: деление на нуль и деление наименьшего отрицательного числа (typemin) на --1. В обоих случаях возникает DivideError. Функции вычисления остатка и модуля (rem и mod) вызывают DivideError, если их второй аргумент равен нулю.

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

Литеральные числа с плавающей запятой представлены в стандартных форматах с использованием при необходимости экспоненциальной нотации:

julia> 1.0
1.0

julia> 1.
1.0

julia> 0.5
0.5

julia> .5
0.5

julia> -1.23
-1.23

julia> 1e10
1.0e10

julia> 2.5e-4
0.00025

Все приведенные выше результаты являются значениями Float64. Литеральные значения Float32 можно указывать, вводя f вместо e:

julia> x = 0.5f0
0.5f0

julia> typeof(x)
Float32

julia> 2.5f-4
0.00025f0

Значения можно легко преобразовывать в тип Float32:

julia> x = Float32(-1.5)
-1.5f0

julia> typeof(x)
Float32

Также допускаются шестнадцатеричные литералы с плавающей запятой, но только как значения Float64, где p предшествует показателю основания 2:

julia> 0x1p0
1.0

julia> 0x1.8p3
12.0

julia> x = 0x.4p-1
0.125

julia> typeof(x)
Float64

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

julia> sizeof(Float16(4.))
2

julia> 2*Float16(4.)
Float16(8.0)

В качестве разделителя цифр можно использовать символ подчеркивания _:

julia> 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010
(10000, 5.0e-9, 0xdeadbeef, 0xb2)

Нуль с плавающей запятой

Числа с плавающей запятой имеют два нуля — положительный и отрицательный. Они равны друг другу, но имеют разные двоичные представления, как можно увидеть с помощью функции bitstring:

julia> 0.0 == -0.0
true

julia> bitstring(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"

julia> bitstring(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"

Особые значения с плавающей запятой

Существует три определенных стандартных значения с плавающей запятой, которые не соответствуют ни одной точке на прямой вещественных чисел:

Float16 Float32 Float64 Название Описание

Inf16

Inf32

Inf

положительная бесконечность

значение, больше чем все конечные значения с плавающей запятой

-Inf16

-Inf32

-Inf

отрицательная бесконечность

значение, меньше чем все конечные значения с плавающей запятой

NaN16

NaN32

NaN

не число

значение, не == никакому значению с плавающей запятой (включая его само)

Дальнейшее обсуждение упорядочения этих бесконечных значений с плавающей запятой относительно друг друга и других значений с плавающей запятой см. в разделе Числовые сравнения. Согласно стандарту IEEE 754 эти значения с плавающей запятой являются результатами определенных арифметических операций:

julia> 1/Inf
0.0

julia> 1/0
Inf

julia> -5/0
-Inf

julia> 0.000001/0
Inf

julia> 0/0
NaN

julia> 500 + Inf
Inf

julia> 500 - Inf
-Inf

julia> Inf + Inf
Inf

julia> Inf - Inf
NaN

julia> Inf * Inf
Inf

julia> Inf / Inf
NaN

julia> 0 * Inf
NaN

julia> NaN == NaN
false

julia> NaN != NaN
true

julia> NaN < NaN
false

julia> NaN > NaN
false

К типам с плавающей запятой также применяются функции typemin и typemax:

julia> (typemin(Float16),typemax(Float16))
(-Inf16, Inf16)

julia> (typemin(Float32),typemax(Float32))
(-Inf32, Inf32)

julia> (typemin(Float64),typemax(Float64))
(-Inf, Inf)

Машинный эпсилон

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

В Julia существует метод eps, который позволяет получить расстояние между 1.0 и следующим большим представимым значением с плавающей запятой:

julia> eps(Float32)
1.1920929f-7

julia> eps(Float64)
2.220446049250313e-16

julia> eps() # то же, что и eps(Float64)
2.220446049250313e-16

Это значения 2.0^-23 и 2.0^-52, как и Float32 и Float64, соответственно. Функция eps также может принимать в качестве аргумента значение с плавающей запятой и выдает абсолютную разницу между этим значением и следующим представимым значением с плавающей запятой. То есть eps(x) выдает значение того же типа, что и x, поэтому x + eps(x) является следующим представимым значением с плавающей точкой, большим, чем x:

julia> eps(1.0)
2.220446049250313e-16

julia> eps(1000.)
1.1368683772161603e-13

julia> eps(1e-27)
1.793662034335766e-43

julia> eps(0.0)
5.0e-324

Расстояние между двумя соседними представимыми числами с плавающей запятой не является постоянным — оно меньше для меньших значений и больше для больших. Другими словами, представимые числа с плавающей запятой наиболее плотно расположены на прямой вещественных чисел вблизи нуля, а по мере удаления от нуля их разреженность увеличивается экспоненциально. По определению, eps(1.0) аналогично eps(Float64), поскольку 1.0 является 64-разрядным значением с плавающей запятой.

Julia также предоставляет функции nextfloat и prevfloat, которые возвращают аргументу следующее наибольшее или наименьшее представимое число с плавающей запятой, соответственно:

julia> x = 1.25f0
1.25f0

julia> nextfloat(x)
1.2500001f0

julia> prevfloat(x)
1.2499999f0

julia> bitstring(prevfloat(x))
"00111111100111111111111111111111"

julia> bitstring(x)
"00111111101000000000000000000000"

julia> bitstring(nextfloat(x))
"00111111101000000000000000000001"

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

Режимы округления

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

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

Общие положения и справочные материалы

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

  • Подробным руководством по арифметике с плавающей запятой является стандарт IEEE 754-2008. Однако оно не доступно бесплатно в Интернете.

  • Краткое, но четкое изложение вариантов представления чисел с плавающей запятой можно найти в тематической статье Джона. Д. Кука (John D. Cook), а также в его статье с вводной информацией о некоторых проблемах, возникающих из-за отличия поведения этого представления от идеализированной абстракции вещественных чисел.

  • Также рекомендуется ознакомиться с серией публикаций блога Брюса Доусона (Bruce Dawson) о числах с плавающей запятой.

  • Интересное и подробное обсуждение чисел с плавающей запятой и вопросов, связанных с точностью вычислений , возникающих при работе с ними, представлено в документе Дэвида Голдберга (David Goldberg) What Every Computer Scientist Should Know About Floating-Point Arithmetic (Что каждый специалист в области информатики должен знать об арифметике с плавающей запятой).

  • Более подробную документацию, посвященную истории, обоснованию и проблемам с числами с плавающей запятой, а также обсуждение многих других тем в области числовых вычислений можно найти в сборнике публикаций от Уильяма Кахана (William Kahan), широко известного как «отец плавающей запятой». Особый интерес может представлять статья An Interview with the Old Man of Floating-Point (Интервью с отцом плавающей запятой).

Арифметика произвольной точности

Для выполнения вычислений с целыми числами произвольной точности и числами с плавающей запятой в Julia встроены библиотеки GNU Multiple Precision Arithmetic Library (GMP) и GNU MPFR Library, соответственно. Для целых чисел произвольной точности и чисел с плавающей запятой в Julia доступны типы BigInt и BigFloat, соответственно.

Для создания этих типов из примитивных числовых типов существуют конструкторы. Для построения типов из AbstractString можно использовать строковый литерал @big_str или parse. BigInt также можно вводить как целочисленные литералы, если они слишком велики для других встроенных целочисленных типов. Обратите внимание, что, поскольку в Base нет целочисленного типа произвольной точности без знака (в большинстве случаев достаточно BigInt), можно использовать шестнадцатеричные, восьмеричные и двоичные литералы (помимо десятичных).

После создания они применяются в арифметических операциях вместе со всеми другими числовыми типами благодаря механизму продвижения преобразования типов в Julia:

julia> BigInt(typemax(Int64)) + 1
9223372036854775808

julia> big"123456789012345678901234567890" + 1
123456789012345678901234567891

julia> parse(BigInt, "123456789012345678901234567890") + 1
123456789012345678901234567891

julia> string(big"2"^200, base=16)
"100000000000000000000000000000000000000000000000000"

julia> 0x100000000000000000000000000000000-1 == typemax(UInt128)
true

julia> 0x000000000000000000000000000000000
0

julia> typeof(ans)
BigInt

julia> big"1.23456789012345678901"
1.234567890123456789010000000000000000000000000000000000000000000000000000000004

julia> parse(BigFloat, "1.23456789012345678901")
1.234567890123456789010000000000000000000000000000000000000000000000000000000004

julia> BigFloat(2.0^66) / 3
2.459565876494606882133333333333333333333333333333333333333333333333333333333344e+19

julia> factorial(BigInt(40))
815915283247897734345611269596115894272000000000

Однако продвижение типов между приведенными выше примитивными типами и BigInt/BigFloat не является автоматическим и должно быть указано явным образом.

julia> x = typemin(Int64)
-9223372036854775808

julia> x = x - 1
9223372036854775807

julia> typeof(x)
Int64

julia> y = BigInt(typemin(Int64))
-9223372036854775808

julia> y = y - 1
-9223372036854775809

julia> typeof(y)
BigInt

Точность по умолчанию (в количестве разрядов значащего числа) и режим округления операций BigFloat можно изменить глобально, вызвав setprecision и setrounding. Эти изменения будут учитываться во всех дальнейших вычислениях. Или же точность или округление можно изменить только в рамках выполнения конкретного блока кода, используя те же функции с блоком do:

julia> setrounding(BigFloat, RoundUp) do
           BigFloat(1) + parse(BigFloat, "0.1")
       end
1.100000000000000000000000000000000000000000000000000000000000000000000000000003

julia> setrounding(BigFloat, RoundDown) do
           BigFloat(1) + parse(BigFloat, "0.1")
       end
1.099999999999999999999999999999999999999999999999999999999999999999999999999986

julia> setprecision(40) do
           BigFloat(1) + parse(BigFloat, "0.1")
       end
1.1000000000004

Числовые литеральные коэффициенты

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

julia> x = 3
3

julia> 2x^2 - 3x + 1
10

julia> 1.5x^2 - .5x + 1
13.0

Написание экспоненциальных функций приобретает более элегантный вид:

julia> 2^2x
64

Приоритет числовых литеральных коэффициентов немного ниже, чем у унарных операторов, например у оператора отрицания. Таким образом, -2x анализируется как (-2) * x, а √2x анализируется как (√2) * x. Однако в комбинации с экспонентой числовые литеральные коэффициенты анализируются аналогично унарным операторам. Например, 2^3x анализируется как 2^(3x), а 2x^3 анализируется как 2*(x^3).

Числовые литералы также работают как коэффициенты в выражениях с круглыми скобками:

julia> 2(x-1)^2 - 3(x-1) + 1
3

Приоритет числовых литеральных коэффициентов, используемых для неявного умножения, выше, чем у других двоичных операторов, таких как операторы умножения (*) и деления (/, \ и //). Это означает, например, что 1 / 2im равно -0.5im, а 6 // 2(2 + 1) равно 1 // 1.

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

julia> (x-1)x
6

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

julia> (x-1)(x+1)
ERROR: MethodError: objects of type Int64 are not callable

julia> x(x+1)
ERROR: MethodError: objects of type Int64 are not callable

Оба выражения интерпретируются как применение функции: если сразу за любым выражением, не являющимся числовым литералом, следует скобка, это выражение интерпретируется как функция, применимая к значениям в скобках (подробнее о функциях см. в разделе Функции). Таким образом, в обоих случаях возникает ошибка, поскольку левое значение не является функцией.

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

Синтаксические конфликты

Синтаксис смежных литеральных коэффициентов может конфликтовать с некоторыми синтаксисами числовых литералов: шестнадцатеричных, восьмеричных и двоичных целочисленных литералов и инженерной нотацией для литералов с плавающей запятой. Ниже приведены некоторые ситуации, в которых возникают синтаксические конфликты.

  • Шестнадцатеричное целочисленное литеральное выражение 0xff может интерпретироваться как числовой литерал 0, умноженный на переменную xff. Аналогичная неоднозначность возникает при использовании восьмеричных и двоичных литералов, таких как 0o777 или 0b01001010.

  • Литеральное выражение с плавающей запятой 1e10 может интерпретироваться как числовой литерал 1, умноженный на переменную e10. То же самое происходит с эквивалентной формой E.

  • 32-разрядное литеральное выражение с плавающей запятой 1.5f22 может интерпретироваться как числовой литерал 1.5, умноженный на переменную f22.

Во всех случаях неоднозначность разрешается в пользу интерпретации выражений как числовых литералов:

  • Выражения, начинающиеся с 0x/0o/0b, всегда являются шестнадцатеричными / восьмеричными / двоичными литералами.

  • Выражения, начинающиеся с числового литерала, за которым следует e или E, всегда являются литералами с плавающей запятой.

  • Выражения, начинающиеся с числового литерала, за которым следует f, всегда являются 32-разрядными литералами с плавающей запятой.

В отличие от E, которое по историческим причинам эквивалентно e в числовых литералах, F является просто другой буквой и не работает как f в числовых литералах. Следовательно, выражения, начинающиеся с числового литерала, за которым следует F, интерпретируются как числовой литерал, умноженный на переменную, что означает, что, например, 1.5F22 равно 1.5 * F22.

Литеральные нуль и единица

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

Функция Описание

zero(x)

Литеральный нуль типа x или тип переменной x

one(x)

Литеральная единица типа x или тип переменной x

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

Примеры:

julia> zero(Float32)
0.0f0

julia> zero(1.0)
0.0

julia> one(Int32)
1

julia> one(BigFloat)
1.0