Целые числа и числа с плавающей запятой
Целочисленные значения и значения с плавающей запятой являются базовыми конструктивными блоками арифметики и вычислений. Встроенные представления таких значений называются числовыми примитивами, а представления целых чисел и чисел с плавающей запятой в виде непосредственных значений в коде называются числовыми литералами. Например, 1
— это целочисленный литерал, а 1.0
— литерал с плавающей запятой. Их двоичные представления в памяти в виде объектов являются числовыми примитивами.
Julia предоставляет широкий диапазон примитивных числовых типов, на основе которых определен полный набор арифметических и битовых операторов, а также стандартных математических функций. Они связаны непосредственно с числовыми типами и операциями, которые изначально поддерживаются на современных компьютерах, что позволяет Julia использовать все преимущества вычислительных ресурсов. Кроме того, Julia обеспечивает программную поддержку для арифметики произвольной точности, способной обрабатывать операции с числовыми значениями, которые не могут быть должным образом представлены в собственной аппаратной реализации. Однако это выполняется за счет относительно более низкой производительности.
Ниже приведены примитивные числовые типы Julia.
-
Целочисленные типы:
Тип | Со знаком? | Количество битов | Наименьшее значение | Наибольшее значение |
---|---|---|---|---|
✓ |
8 |
--2^7 |
2^7 — 1 |
|
8 |
0 |
2^8 — 1 |
||
✓ |
16 |
--2^15 |
2^15 — 1 |
|
16 |
0 |
2^16 — 1 |
||
✓ |
32 |
--2^31 |
2^31 — 1 |
|
32 |
0 |
2^32 — 1 |
||
✓ |
64 |
--2^63 |
2^63 — 1 |
|
64 |
0 |
2^64 — 1 |
||
✓ |
128 |
--2^127 |
2^127 — 1 |
|
128 |
0 |
2^128 — 1 |
||
Н/Д |
8 |
|
|
-
Типы с плавающей запятой:
Тип | Точность | Количество битов |
---|---|---|
16 |
||
32 |
||
64 |
Кроме того, на базе этих примитивных числовых типов создана полная поддержка для комплексных и рациональных чисел. Все числовые типы естественным образом взаимодействуют между собой без явного приведения благодаря гибкой, расширяемой пользователем системе продвижения типов.
Целые числа
Литеральные целые числа представлены стандартным образом.
julia> 1
1
julia> 1234
1234
julia-repl
Тип по умолчанию для целочисленного литерала зависит от того, какую архитектуру имеет целевая система — 32- или 64-разрядную:
# 32-разрядная система:
julia> typeof(1)
Int32
# 64-разрядная система:
julia> typeof(1)
Int64
julia-repl
Внутренняя переменная Julia Sys.WORD_SIZE
указывает, является ли целевая система 32-разрядной или 64-разрядной:
# 32-разрядная система:
julia> Sys.WORD_SIZE
32
# 64-разрядная система:
julia> Sys.WORD_SIZE
64
julia-repl
Julia также определяет типы Int
и UInt
, которые являются псевдонимами для системных собственных целочисленных типов со знаком и без знака, соответственно:
# 32-разрядная система:
julia> Int
Int32
julia> UInt
UInt32
# 64-разрядная система:
julia> Int
Int64
julia> UInt
UInt64
julia-repl
Большие целочисленные литералы, которые не могут быть представлены с использованием только 32 битов, но могут быть представлены в 64 битах, всегда создают 64-разрядные целые числа, независимо от типа системы:
# 32-или 64-разрядная система:
julia> typeof(3000000000)
Int64
julia-repl
Целые числа без знака вводятся и выводятся с использованием префикса 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-repl
В основе лежит следующее наблюдение: когда шестнадцатеричные литералы без знака используются для целочисленных значений, они обычно применяются для представления фиксированной числовой последовательности байтов, а не просто целочисленного значения.
Также поддерживаются двоичные и восьмеричные литералы.
julia> x = 0b10
0x02
julia> typeof(x)
UInt8
julia> x = 0o010
0x08
julia> typeof(x)
UInt8
julia> x = 0x00000000000000001111222233334444
0x00000000000000001111222233334444
julia> typeof(x)
UInt128
julia-repl
Как и для шестнадцатеричных литералов, двоичные и восьмеричные литералы создают целочисленные типы без знака. Размер элемента двоичных данных — это минимально необходимый размер, если первой цифрой литерала не является 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
julia-repl
Минимальные и максимальные представимые значения примитивных числовых типов, такие как целые числа, задаются функциями 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]
julia-repl
Значения, возвращаемые typemin
и typemax
, всегда имеют заданный тип аргумента. (В приведенном выше выражении используется несколько функций, с которыми еще предстоит ознакомиться, включая циклы for, строки и интерполяцию. Однако оно должно быть достаточно простым и понятным пользователям с определенным опытом программирования.)
Поведение при переполнении
В Julia превышение максимального представимого значения заданного типа приводит к появлению циклического переноса:
julia> x = typemax(Int64)
9223372036854775807
julia> x + 1
-9223372036854775808
julia> x + 1 == typemin(Int64)
true
julia-repl
Таким образом, арифметика с целыми числами Julia фактически является формой модулярной арифметики. Это свидетельствует об особенностях базовой арифметики целых чисел, реализованной на современных компьютерах. В приложениях, где возможно переполнение, необходима явная проверка на наличие циклического переноса, вызванного переполнением. В противном случае рекомендуется использовать тип BigInt
в арифметике произвольной точности.
Ниже приведены пример поведения переполнения и возможные способы его разрешения.
julia> 10^19
-8446744073709551616
julia> big(10)^19
10000000000000000000
julia-repl
Ошибки деления
Целочисленное деление (функция 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
julia-repl
Все приведенные выше результаты являются значениями Float64
. Литеральные значения Float32
можно указывать, вводя f
вместо e
:
julia> x = 0.5f0
0.5f0
julia> typeof(x)
Float32
julia> 2.5f-4
0.00025f0
julia-repl
Значения можно легко преобразовывать в тип Float32
:
julia> x = Float32(-1.5)
-1.5f0
julia> typeof(x)
Float32
julia-repl
Также допускаются шестнадцатеричные литералы с плавающей запятой, но только как значения Float64
, где p
предшествует показателю основания 2:
julia> 0x1p0
1.0
julia> 0x1.8p3
12.0
julia> x = 0x.4p-1
0.125
julia> typeof(x)
Float64
julia-repl
Числа с плавающей запятой половинной точности также поддерживаются (Float16
), но они реализованы программно (имеют компьютерный формат) и используют Float32
для вычислений.
julia> sizeof(Float16(4.))
2
julia> 2*Float16(4.)
Float16(8.0)
julia-repl
В качестве разделителя цифр можно использовать символ подчеркивания _
:
julia> 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010
(10000, 5.0e-9, 0xdeadbeef, 0xb2)
julia-repl
Нуль с плавающей запятой
Числа с плавающей запятой имеют два нуля — положительный и отрицательный. Они равны друг другу, но имеют разные двоичные представления, как можно увидеть с помощью функции bitstring
:
julia> 0.0 == -0.0
true
julia> bitstring(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"
julia> bitstring(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"
julia-repl
Особые значения с плавающей запятой
Существует три определенных стандартных значения с плавающей запятой, которые не соответствуют ни одной точке на прямой вещественных чисел:
Float16 |
Float32 |
Float64 |
Название | Описание |
---|---|---|---|---|
|
|
|
положительная бесконечность |
значение, больше чем все конечные значения с плавающей запятой |
|
|
|
отрицательная бесконечность |
значение, меньше чем все конечные значения с плавающей запятой |
|
|
|
не число |
значение, не |
Дальнейшее обсуждение упорядочения этих бесконечных значений с плавающей запятой относительно друг друга и других значений с плавающей запятой см. в разделе Числовые сравнения. Согласно стандарту 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
julia-repl
julia> (typemin(Float16),typemax(Float16))
(-Inf16, Inf16)
julia> (typemin(Float32),typemax(Float32))
(-Inf32, Inf32)
julia> (typemin(Float64),typemax(Float64))
(-Inf, Inf)
julia-repl
Машинный эпсилон
Большинство вещественных чисел не могут быть точно представлены числами с плавающей запятой, поэтому для выполнения многих задач важно знать расстояние между двумя смежными представимыми числами с плавающей запятой, которое часто называется машинным эпсилоном.
В Julia существует метод eps
, который позволяет получить расстояние между 1.0
и следующим большим представимым значением с плавающей запятой:
julia> eps(Float32)
1.1920929f-7
julia> eps(Float64)
2.220446049250313e-16
julia> eps() # то же, что и eps(Float64)
2.220446049250313e-16
julia-repl
Это значения 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
julia-repl
Расстояние между двумя соседними представимыми числами с плавающей запятой не является постоянным — оно меньше для меньших значений и больше для больших. Другими словами, представимые числа с плавающей запятой наиболее плотно расположены на прямой вещественных чисел вблизи нуля, а по мере удаления от нуля их разреженность увеличивается экспоненциально. По определению, 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"
julia-repl
В этом примере подчеркивается общий принцип, который состоит в том, что смежные представимые числа с плавающей запятой также имеют смежные двоичные целочисленные представления.
Режимы округления
Если у числа отсутствует точное представление с плавающей запятой, оно должно быть округлено до соответствующего представимого значения. Однако способ выполнения округления при необходимости может быть изменен в соответствии с режимами округления, представленными в стандарте 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
julia-repl
Однако продвижение типов между приведенными выше примитивными типами и 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
julia-repl
Точность по умолчанию (в количестве разрядов значащего числа) и режим округления операций 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-repl
Числовые литеральные коэффициенты
Чтобы сделать обычные числовые формулы и выражения более понятными, в Julia непосредственно перед переменной можно указать числовой литерал, подразумевающий умножение. При этом написание многочленных выражений становится намного чище:
julia> x = 3
3
julia> 2x^2 - 3x + 1
10
julia> 1.5x^2 - .5x + 1
13.0
julia-repl
Написание экспоненциальных функций приобретает более элегантный вид:
julia> 2^2x
64
julia-repl
Приоритет числовых литеральных коэффициентов немного ниже, чем у унарных операторов, например у оператора отрицания. Таким образом, -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
julia-repl
Приоритет числовых литеральных коэффициентов, используемых для неявного умножения, выше, чем у других двоичных операторов, таких как операторы умножения ( |
Кроме того, выражения в скобках могут использоваться в качестве коэффициентов для переменных, что подразумевает умножение выражения на переменную:
julia> (x-1)x
6
julia-repl
Однако для обозначения умножения нельзя использовать ни сопоставление двух выражений со скобками, ни размещение переменной перед выражением со скобками.
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
julia-repl
Оба выражения интерпретируются как применение функции: если сразу за любым выражением, не являющимся числовым литералом, следует скобка, это выражение интерпретируется как функция, применимая к значениям в скобках (подробнее о функциях см. в разделе Функции). Таким образом, в обоих случаях возникает ошибка, поскольку левое значение не является функцией.
Приведенные выше синтаксические усовершенствования значительно снижают визуальный шум, появляющийся при написании обычных математических формул. Обратите внимание, что между числовым литеральным коэффициентом и идентификатором или выражением в скобках, которое оно умножает, не должно быть пробелов.
Синтаксические конфликты
Синтаксис смежных литеральных коэффициентов может конфликтовать с некоторыми синтаксисами числовых литералов: шестнадцатеричных, восьмеричных и двоичных целочисленных литералов и инженерной нотацией для литералов с плавающей запятой. Ниже приведены некоторые ситуации, в которых возникают синтаксические конфликты.
-
Шестнадцатеричное целочисленное литеральное выражение
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 существуют функции, которые возвращают литеральные нуль и единицу, соответствующие заданному типу или типу заданной переменной.
Функция | Описание |
---|---|
Литеральный нуль типа |
|
Литеральная единица типа |
Эти функции полезны в числовых сравнениях и позволяют избежать затрат, связанных с ненужным преобразованием типов.
Примеры:
julia> zero(Float32)
0.0f0
julia> zero(1.0)
0.0
julia> one(Int32)
1
julia> one(BigFloat)
1.0
julia-repl