Integers and Floating-Point Numbers
Целые числа и числа с плавающей запятой являются основными строительными блоками арифметики и вычислений. Встроенные представления таких значений называются числовыми примитивами, в то время как представления целых чисел и чисел с плавающей запятой в виде непосредственных значений в коде известны как числовые литералы. Например, 1
является целым литералом, в то время как 1.0
является литералом с плавающей запятой; их двоичные представления в памяти в виде объектов являются числовыми примитивами.
Julia предоставляет широкий спектр примитивных числовых типов, а также полный набор арифметических и побитовых операторов, а также стандартные математические функции, определенные для них. Эти типы и операции напрямую соответствуют числовым типам и операциям, которые нативно поддерживаются на современных компьютерах, что позволяет Julia в полной мере использовать вычислительные ресурсы. Кроме того, Julia предоставляет программную поддержку для Arbitrary Precision Arithmetic, которая может обрабатывать операции с числовыми значениями, которые не могут быть эффективно представлены в нативных аппаратных представлениях, но за счет относительно более медленной производительности.
Следующие примитивные числовые типы Julia:
- Целочисленные типы:
Type | Signed? | Number of bits | Smallest value | Largest value |
---|---|---|---|---|
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 | N/A | 8 | false (0) | true (1) |
- Типы с плавающей запятой:
Type | Precision | Number of bits |
---|---|---|
Float16 | half | 16 |
Float32 | single | 32 |
Float64 | double | 64 |
Кроме того, полная поддержка Complex and Rational Numbers построена на основе этих примитивных числовых типов. Все числовые типы естественно взаимодействуют друг с другом без явного приведения типов, благодаря гибкому, расширяемому type promotion system.
Integers
Целые литералы представлены стандартным образом:
julia> 1
1
julia> 1234
1234
Тип по умолчанию для целочисленного литерала зависит от того, имеет ли целевая система 32-битную архитектуру или 64-битную архитектуру:
# 32-bit system:
julia> typeof(1)
Int32
# 64-bit system:
julia> typeof(1)
Int64
Внутренняя переменная Julia Sys.WORD_SIZE
указывает, является ли целевая система 32-битной или 64-битной:
# 32-bit system:
julia> Sys.WORD_SIZE
32
# 64-bit system:
julia> Sys.WORD_SIZE
64
Julia также определяет типы Int
и UInt
, которые являются псевдонимами для знаковых и беззнаковых целочисленных типов системы соответственно:
# 32-bit system:
julia> Int
Int32
julia> UInt
UInt32
# 64-bit system:
julia> Int
Int64
julia> UInt
UInt64
Более крупные целочисленные литералы, которые не могут быть представлены с использованием только 32 бит, но могут быть представлены в 64 битах, всегда создают 64-битные целые числа, независимо от типа системы:
# 32-bit or 64-bit system:
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 loops, Strings и Interpolation, но должно быть достаточно легко для понимания пользователями с некоторым опытом программирования.)
Overflow behavior
В Julia превышение максимального представимого значения данного типа приводит к поведению обертывания:
julia> x = typemax(Int64)
9223372036854775807
julia> x + 1
-9223372036854775808
julia> x + 1 == typemin(Int64)
true
Арифметические операции с целочисленными типами Julia по своей сути выполняют modular arithmetic, отражая характеристики целочисленной арифметики на современном компьютерном оборудовании. В сценариях, где возможен переполнение, крайне важно явно проверять на эффекты обертывания, которые могут возникнуть в результате таких переполнений. Модуль Base.Checked
предоставляет набор арифметических операций с проверками на переполнение, которые вызывают ошибки, если происходит переполнение. Для случаев, когда переполнение не может быть допущено ни при каких обстоятельствах, рекомендуется использовать тип BigInt
, как подробно описано в Arbitrary Precision Arithmetic.
Пример поведения переполнения и способы его потенциального разрешения следующие:
julia> 10^19
-8446744073709551616
julia> big(10)^19
10000000000000000000
Division errors
Целочисленное деление (функция div
) имеет два исключительных случая: деление на ноль и деление на -1 наименьшего отрицательного числа (typemin
). Оба этих случая вызывают DivideError
. Функции остатка и модуля (rem
и mod
) вызывают 4d61726b646f776e2e436f64652822222c20224469766964654572726f722229_40726566
, когда их второй аргумент равен нулю.
Floating-Point Numbers
Литералы с плавающей запятой представлены в стандартных форматах, используя E-notation при необходимости:
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
можно ввести, заменив e
на f
:
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
перед двоичным экспонентом:
julia> 0x1p0
1.0
julia> 0x1.8p3
12.0
julia> x = 0x.4p-1
0.125
julia> typeof(x)
Float64
Числа с плавающей запятой половинной точности также поддерживаются (Float16
) на всех платформах, с использованием нативных инструкций на оборудовании, которое поддерживает этот формат чисел. В противном случае операции реализуются в программном обеспечении и используют Float32
для промежуточных вычислений. В качестве внутренней детали реализации это достигается за счет использования типа LLVM half
, который ведет себя аналогично тому, что флаг GCC -fexcess-precision=16
делает для кода C/C++.
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)
Floating-point zero
Числа с плавающей запятой имеют two zeros, положительный ноль и отрицательный ноль. Они равны друг другу, но имеют разные бинарные представления, как можно увидеть, используя функцию bitstring
:
julia> 0.0 == -0.0
true
julia> bitstring(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"
julia> bitstring(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"
Special floating-point values
Существует три заданных стандартных значений с плавающей запятой, которые не соответствуют ни одной точке на числовой прямой:
Float16 | Float32 | Float64 | Name | Description |
---|---|---|---|---|
Inf16 | Inf32 | Inf | positive infinity | a value greater than all finite floating-point values |
-Inf16 | -Inf32 | -Inf | negative infinity | a value less than all finite floating-point values |
NaN16 | NaN32 | NaN | not a number | a value not == to any floating-point value (including itself) |
Для дальнейшего обсуждения того, как эти конечные значения с плавающей запятой упорядочены относительно друг друга и других чисел с плавающей запятой, см. Numeric Comparisons. По IEEE 754 standard, эти значения с плавающей запятой являются результатами определенных арифметических операций:
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)
Machine epsilon
Большинство действительных чисел не могут быть точно представлены с помощью чисел с плавающей запятой, и поэтому для многих целей важно знать расстояние между двумя соседними представимыми числами с плавающей запятой, которое часто известно как machine epsilon.
Джулия предоставляет eps
, что дает расстояние между 1.0
и следующим большим представимым значением с плавающей запятой:
julia> eps(Float32)
1.1920929f-7
julia> eps(Float64)
2.220446049250313e-16
julia> eps() # same as 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"
Этот пример подчеркивает общий принцип, что соседние представимые числа с плавающей запятой также имеют соседние двоичные целочисленные представления.
Rounding modes
Если число не имеет точного представления с плавающей запятой, оно должно быть округлено до соответствующего представимого значения. Однако способ, которым это округление выполняется, может быть изменен при необходимости в соответствии с режимами округления, представленными в IEEE 754 standard.
Режим по умолчанию всегда используется RoundNearest
, который округляет до ближайшего представимого значения, при этом при равенстве округление происходит в сторону ближайшего значения с четким младшим значащим битом.
Background and References
Арифметика с плавающей запятой включает в себя множество тонкостей, которые могут удивить пользователей, не знакомых с низкоуровневыми деталями реализации. Тем не менее, эти тонкости подробно описаны в большинстве книг по научным вычислениям, а также в следующих источниках:
- Определяющее руководство по арифметике с плавающей запятой — это IEEE 754-2008 Standard; однако оно недоступно бесплатно в интернете.
- Для краткой, но ясной презентации того, как представляются числа с плавающей запятой, смотрите работу Джона Д. Кука article по этой теме, а также его introduction о некоторых проблемах, возникающих из-за того, как это представление отличается по поведению от идеализированной абстракции действительных чисел.
- Также рекомендуется работа Брюса Досона series of blog posts on floating-point numbers.
- Для отличного, глубокого обсуждения чисел с плавающей запятой и проблем числовой точности, с которыми сталкиваются при вычислениях с ними, см. статью Дэвида Голдберга What Every Computer Scientist Should Know About Floating-Point Arithmetic.
- Для еще более обширной документации по истории, обоснованию и проблемам с числами с плавающей запятой, а также обсуждению многих других тем в численных вычислениях, см. collected writings от William Kahan, обычно известного как "Отец чисел с плавающей запятой". Особый интерес может представлять An Interview with the Old Man of Floating-Point.
Arbitrary Precision Arithmetic
Чтобы разрешить вычисления с целыми числами произвольной точности и числами с плавающей запятой, Julia оборачивает GNU Multiple Precision Arithmetic Library (GMP) и GNU MPFR Library, соответственно. Типы BigInt
и BigFloat
доступны в Julia для целых чисел произвольной точности и чисел с плавающей запятой соответственно.
Конструкторы существуют для создания этих типов из примитивных числовых типов, и string literal @big_str
или parse
могут быть использованы для их создания из AbstractString
s. BigInt
s также могут быть введены как целочисленные литералы, когда они слишком велики для других встроенных целочисленных типов. Обратите внимание, что так как в Base
нет беззнакового целочисленного типа произвольной точности (BigInt
достаточно в большинстве случаев), можно использовать шестнадцатеричные, восьмеричные и двоичные литералы (в дополнение к десятичным литералам).
Как только они созданы, они участвуют в арифметических операциях со всеми другими числовыми типами благодаря type promotion and conversion mechanism:
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
Связь между setprecision
или setrounding
и @big_str
, макросом, используемым для big
строковых литералов (таких как big"0.3"
), может быть не интуитивно понятным, в результате того, что @big_str
является макросом. См. документацию 4d61726b646f776e2e436f64652822222c2022406269675f7374722229_40726566
для получения подробной информации.
Numeric Literal Coefficients
Чтобы сделать общие числовые формулы и выражения более понятными, 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
Обе конструкции интерпретируются как применение функции: любое выражение, которое не является числовым литералом, когда сразу за ним следует скобка, интерпретируется как функция, применяемая к значениям в скобках (см. Functions для получения дополнительной информации о функциях). Таким образом, в обоих этих случаях возникает ошибка, так как левое значение не является функцией.
Улучшения синтаксиса значительно уменьшают визуальный шум, возникающий при написании общих математических формул. Обратите внимание, что между числовым литералом коэффициента и идентификатором или выражением в скобках, которое он умножает, не должно быть пробелов.
Syntax Conflicts
Сопоставленный синтаксис буквенных коэффициентов может конфликтовать с некоторыми синтаксисами числовых литералов: шестнадцатеричными, восьмеричными и двоичными целыми литералами, а также инженерной нотацией для литералов с плавающей запятой. Вот некоторые ситуации, в которых возникают синтаксические конфликты:
- Шестнадцатеричное целочисленное литеральное выражение
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
.
Literal zero and one
Julia предоставляет функции, которые возвращают литералы 0 и 1, соответствующие указанному типу или типу данной переменной.
Function | Description |
---|---|
zero(x) | Literal zero of type x or type of variable x |
one(x) | Literal one of type x or type of variable x |
Эти функции полезны в Numeric Comparisons, чтобы избежать накладных расходов от ненужного type conversion.
Примеры:
julia> zero(Float32)
0.0f0
julia> zero(1.0)
0.0
julia> one(Int32)
1
julia> one(BigFloat)
1.0