Целые и числа с плавающей точкой
Целые и числа с плавающей точкой являются основными строительными блоками арифметики и вычислений. Встроенные представления таких значений называются числовыми примитивами, а представления целых и чисел с плавающей точкой в виде непосредственных значений в коде называются числовыми литералами. Например, 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 |
N/A | 8 |
false (0) |
true (1) |
- Типы с плавающей точкой:
| Тип | Точность | Количество битов |
|---|---|---|
Float16 |
полуточная | 16 |
Float32 |
одинарная | 32 |
Float64 |
двойная | 64 |
Кроме того, полная поддержка комплексных и рациональных чисел построена на основе этих примитивных числовых типов. Все числовые типы естественным образом взаимодействуют друг с другом без явного приведения типов благодаря гибкой, расширяемой пользователями системе повышения типа.
Целые числа
Литералы целых чисел представлены стандартным способом:
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> 0x1 0x01 julia> typeof(ans) UInt8 julia> 0x123 0x0123 julia> typeof(ans) UInt16 julia> 0x1234567 0x01234567 julia> typeof(ans) UInt32 julia> 0x123456789abcdef 0x0123456789abcdef julia> typeof(ans) UInt64 julia> 0x11112222333344445555666677778888 0x11112222333344445555666677778888 julia> typeof(ans) UInt128
Это поведение основано на наблюдении, что когда кто-то использует беззнаковые шестнадцатеричные литералы для целочисленных значений, он обычно использует их для представления фиксированной числовой последовательности байтов, а не просто целочисленного значения.
Вспомните, что переменная ans устанавливается в значение последнего вычисленного выражения в интерактивной сессии. Это не происходит, когда код Julia выполняется другими способами.
Также поддерживаются двоичные и восьмеричные литералы:
julia> 0b10 0x02 julia> typeof(ans) UInt8 julia> 0o010 0x08 julia> typeof(ans) UInt8 julia> 0x00000000000000001111222233334444 0x00000000000000001111222233334444 julia> typeof(ans) UInt128
Что касается шестнадцатеричных литералов, двоичные и восьмеричные литералы дают беззнаковые целочисленные типы. Размер двоичных данных — минимально необходимый размер, если ведущая цифра литерала не 0. В случае ведущих нулей размер определяется минимально необходимым размером для литерала, который имеет ту же длину, но ведущая цифра 1. Это позволяет пользователю контролировать размер. Значения, которые не могут быть сохранены в UInt128, не могут быть записаны в качестве таких литералов.
Двоичные, восьмеричные и шестнадцатеричные литералы могут быть знаковыми с помощью - непосредственно перед беззнаковым литералом. Они создают беззнаковое целое число того же размера, что и беззнаковый литерал, с обратным дополнением значения:
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 в арифметике с произвольной точностью рекомендуется вместо него.
Ошибки при делении
Деление целых чисел (функция 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> 0.5f0 0.5f0 julia> typeof(ans) Float32 julia> 2.5f-4 0.00025f0
Значения можно легко преобразовать в Float32:
julia> Float32(-1.5) -1.5f0 julia> typeof(ans) Float32
Шестнадцатеричные литералы с плавающей точкой также допустимы, но только как значения Float64 с p перед показателем в двоичной системе:
julia> 0x1p0 1.0 julia> 0x1.8p3 12.0 julia> 0x.4p-1 0.125 julia> typeof(ans) 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
Функции 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() # 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"
Этот пример демонстрирует общий принцип, что смежные представимые числа с плавающей точкой также имеют смежные двоичные целые представления.
Режимы округления
Если число не имеет точного представления с плавающей точкой, оно должно быть округлено до соответствующего представимого значения. Однако способ этого округления может быть изменён при необходимости в соответствии с режимами округления, представленными в стандарте IEEE 754.
Используемый по умолчанию режим всегда RoundNearest, который округляет до ближайшего представимого значения, а при равенстве округляет к ближайшему значению с чётным наименее значимым битом.
Обзор и ссылки
Арифметика с плавающей точкой предполагает множество тонкостей, которые могут удивить пользователей, незнакомых с деталями реализации на низком уровне. Однако эти тонкости подробно описаны в большинстве книг по научным вычислениям, а также в следующих ссылках:
- Определяющим руководством по арифметике с плавающей точкой является стандарт IEEE 754-2008; однако он не доступен для бесплатного онлайн-доступа.
- Для краткого, но ясного представления о том, как представлены числа с плавающей точкой, см. статью Джона Д. Кука на эту тему, а также его введение в некоторые проблемы, возникающие из-за того, как это представление отличается в поведении от идеализированной абстракции действительных чисел.
- Также рекомендуется серия блоговых постов Брюса Доусона о числах с плавающей точкой.
- Для отличного глубокого обсуждения чисел с плавающей точкой и проблем точности вычислений с ними см. статью Дэвида Голдберга What Every Computer Scientist Should Know About Floating-Point Arithmetic.
- Для ещё более подробного документации истории, обоснования и проблем с числами с плавающей точкой, а также обсуждения многих других тем в численном моделировании, см. собранные работы Уильяма Кахана, обычно известного как «отец арифметики с плавающей точкой». Особого интереса может заслуживать интервью со Стариком Арифметики с Плавающей Точкой.
Арифметика с произвольной точностью
Для выполнения вычислений с целыми числами и числами с плавающей точкой произвольной точности Julia использует GNU Multiple Precision Arithmetic Library (GMP) и GNU MPFR Library соответственно. Типы BigInt и BigFloat доступны в Julia для целых чисел и чисел с плавающей точкой произвольной точности соответственно.
Существуют конструкторы для создания этих типов из примитивных числовых типов, и parse может быть использован для их создания из AbstractString. После создания они участвуют в арифметических операциях со всеми другими числовыми типами благодаря механизму повышения и преобразования типов Julia преобразования и повышения типов:
julia> BigInt(typemax(Int64)) + 1 9223372036854775808 julia> parse(BigInt, "123456789012345678901234567890") + 1 123456789012345678901234567891 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. - Выражение числа с плавающей точкой
1e10может быть интерпретировано как числовой литерал1умноженный на переменнуюe10, и аналогично с эквивалентной формойE. - Выражение 32-разрядного числа с плавающей точкой
1.5f22может быть интерпретировано как числовой литерал1.5умноженный на переменнуюf22.
Во всех случаях неоднозначность разрешается в пользу интерпретации как числовых литералов:
- Выражения, начинающиеся с
0xвсегда являются шестнадцатеричными литералами. - Выражения, начинающиеся с числового литерала, за которым следуют
eилиEвсегда являются литералами чисел с плавающей точкой. - Выражения, начинающиеся с числового литерала, за которым следует
fвсегда являются литералами 32-разрядных чисел с плавающей точкой.
В отличие от E, что эквивалентно e в числовых литералах по историческим причинам, F — это просто другая буква и не ведет себя как f в числовых литералах. Следовательно, выражения, начинающиеся с числового литерала, за которым следует F, интерпретируются как числовой литерал, умноженный на переменную, что означает, например, что 1.5F22 равно 1.5 * F22.
Литерал ноль и единица
Julia предоставляет функции, возвращающие литералы 0 и 1, соответствующие указанному типу или типу заданной переменной.
| Функция | Описание |
|---|---|
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
© 2009–2019 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.0.4/manual/integers-and-floating-point-numbers/