В информатике и численном анализе единица на последнем месте или единица наименьшей точности ( ulp ) — это интервал между двумя последовательными числами с плавающей точкой , т. е. значение, которое представляет наименьшая значимая цифра (самая правая цифра), если она равна 1. Она используется как мера точности в числовых вычислениях. [1]
Наиболее распространенное определение: В системе счисления с точностью , если , то , [2] где — минимальный показатель степени нормальных чисел. В частности, для нормальных чисел , а для субнормальных .
Другое определение, предложенное Джоном Харрисоном, немного отличается: это расстояние между двумя ближайшими числами с плавающей точкой и (т.е. удовлетворяющими и ), предполагая, что диапазон экспоненты не ограничен сверху. [3] [4] Эти определения отличаются только знаковыми степенями основания. [2]
Спецификация IEEE 754 , которой следует все современное оборудование с плавающей точкой, требует, чтобы результат элементарной арифметической операции (сложение, вычитание, умножение, деление и квадратный корень с 1985 года и FMA с 2008 года) был правильно округлен , что подразумевает, что при округлении до ближайшего округленный результат находится в пределах 0,5 ulp от математически точного результата, используя определение Джона Харрисона; наоборот, это свойство подразумевает, что расстояние между округленным результатом и математически точным результатом минимизировано (но для промежуточных случаев ему удовлетворяют два последовательных числа с плавающей точкой). Авторитетные числовые библиотеки вычисляют основные трансцендентные функции с точностью от 0,5 до примерно 1 ulp. Лишь несколько библиотек вычисляют их с точностью до 0,5 ulp, эта проблема сложна из-за дилеммы Table-maker . [5]
Начиная с 2010-х годов, достижения в области математики с плавающей точкой позволили правильно округленным функциям быть почти такими же быстрыми в среднем, как эти более ранние, менее точные функции. Правильно округленная функция также была бы полностью воспроизводимой. Более ранней, промежуточной вехой были функции 0,501 ulp, [ необходимо разъяснение ], которые теоретически давали бы только одно неправильное округление из 1000 случайных входных данных с плавающей точкой. [6]
Пусть будет положительным числом с плавающей точкой и предположим, что активный режим округления — округление до ближайшего, связанного с четным , обозначается . Если , то . В противном случае или , в зависимости от значения младшей значащей цифры и показателя степени . Это демонстрируется в следующем коде Haskell , набранном в интерактивном режиме: [ необходима цитата ]
> до ( \ x -> x == x + 1 ) ( + 1 ) 0 :: Плавающий 1.6777216e7> это - 11.6777215e7> это + 11.6777216e7
Здесь мы начинаем с 0 в одинарной точности (binary32) и многократно добавляем 1 до тех пор, пока операция не перестанет изменять значение. Поскольку мантисса для числа одинарной точности содержит 24 бита, первое целое число, которое не может быть точно представлено, равно 2 24 +1, и это значение округляется до 2 24 в округлении до ближайшего, привязываясь к четному. Таким образом, результат равен 2 24 .
Следующий пример на Java аппроксимирует π как значение с плавающей точкой, находя два двойных значения, заключенных в скобки : .
// π с 20 десятичными цифрами BigDecimal π = new BigDecimal ( "3.14159265358979323846" ); // усечение до числа с плавающей точкой double p0 = π . doubleValue (); // -> 3.141592653589793 (hex: 0x1.921fb54442d18p1) // p0 меньше π, поэтому находим следующее число, представимое как double double p1 = Math . nextUp ( p0 ); // -> 3.1415926535897936 (hex: 0x1.921fb54442d19p1)
Тогда определяется как .
// ulp(π) — это разница между p1 и p0 BigDecimal ulp = new BigDecimal ( p1 ) .subtract ( new BigDecimal ( p0 )); // -> 4.44089209850062616169452667236328125E-16 // (это точно 2**(-51)) // тот же результат при использовании стандартной библиотечной функции double ulpMath = Math . ulp ( p0 ); // -> 4.440892098500626E-16 (hex: 0x1.0p-51)
Другой пример на языке Python , также набранный в интерактивном режиме:
>>> x = 1,0 >>> p = 0 >>> пока x != x + 1 : ... x = x * 2 ... p = p + 1 ... >>> x 9007199254740992,0 >>> p 53 >>> x + 2 + 1 9007199254740996,0
В этом случае мы начинаем с x = 1
и многократно удваиваем его до тех пор, пока x = x + 1
. Аналогично примеру 1, результатом будет 2 53 , поскольку формат с плавающей точкой двойной точности использует 53-битную мантиссу.
Библиотеки Boost C++ предоставляют функции boost::math::float_next
, boost::math::float_prior
, boost::math::nextafter
и boost::math::float_advance
для получения близких (и далеких) значений с плавающей точкой [7] и boost::math::float_distance(a, b)
для вычисления расстояния с плавающей точкой между двумя числами двойной точности. [8]
Библиотека языка C предоставляет функции для вычисления следующего числа с плавающей точкой в некотором заданном направлении: nextafterf
и nexttowardf
для float
, nextafter
и nexttoward
для double
, nextafterl
и nexttowardl
для long double
, объявленных в <math.h>
. Она также предоставляет макросы FLT_EPSILON
, DBL_EPSILON
, LDBL_EPSILON
, которые представляют положительную разность между 1,0 и следующим большим представимым числом в соответствующем типе (т. е. ulp единицы). [9]
Стандартная библиотека Java предоставляет функции Math.ulp(double)
и Math.ulp(float)
. Они были введены в Java 1.5.
Стандартная библиотека Swift предоставляет доступ к следующему числу с плавающей точкой в некотором заданном направлении через свойства экземпляра nextDown
и nextUp
. Она также предоставляет свойство экземпляра ulp
и свойство типа ulpOfOne
(которое соответствует макросам C, например FLT_EPSILON
[10] ) для типов с плавающей точкой Swift. [11]