Неопределенность типа данных с плавающей запятой

Я делаю численный анализ математического программного обеспечения, которое я разработал. Я хочу определить, какова неопределенность моего результата. Будучи f() моим методом и x входным значением, я хочу идентифицировать y своего результата как f(x) +/- y. Мой метод f() имеет несколько операций между float переменными. Чтобы изучить распространение ошибки в f(), мне нужно применить формулы Статистического распространения неопределенности, а для этого мне нужно знать неопределенность переменной float.

Я понимаю архитектуру переменной float, как указано в стандарте IEEE 754, и ошибку округления, преобразующую десятичное значение в float, присущую последнему.

Насколько я понял из литературы, макрос FLT_EPSILON в http://www.cplusplus.com/reference/cfloat/ определяет мое значение y, но этот быстрый тест доказывает, что это неверно:

float f1 = 1.234567f;
float f2 = 1.234567f + 1.192092896e-7f;
float f3 = 1.234567f + 1.192092895e-7f;

printf("Inicial:\t%f\n", f1);
printf("Inicial:\t%f\n", f2);
printf("Inicial:\t%f\n\n", f3);

Выход:

Inicial:  1.234567
Inicial:  1.234567
Inicial:  1.234567

Когда ожидаемый результат должен быть:

Inicial:  1.234567
Inicial:  1.234568 <---
Inicial:  1.234567

В чем я не прав? Разве значение float для x + FLT_EPSILON и x - FLT_EPSILON не должно быть одинаковым?

РЕДАКТИРОВАТЬ: Мой вопрос заключается в том, что R значение float x, каково значение y, что x + y || x - y равно тому же значению R float?


person Pedro Pereira    schedule 23.03.2018    source источник
comment
увеличить точность отображения printf, например: %.8f   -  person Jean-François Fabre    schedule 23.03.2018
comment
Смотрите мое редактирование, пожалуйста   -  person Pedro Pereira    schedule 23.03.2018
comment
Или что когда значения float передаются в printf(), они автоматически преобразуются в double.   -  person Jonathan Leffler    schedule 23.03.2018
comment
@Джонатан Леффлер. Почему это имеет значение? Добавление происходит первым.   -  person Mad Physicist    schedule 23.03.2018
comment
@MadPhysicist: Помимо того факта, что iPhone удалось исказить «Note» в «Ore» (я не удосужился посмотреть; брюзжа!), на каком-то уровне это не имеет значения. На другом уровне это преобразование важно, потому что оно означает, что вы можете использовать точность с плавающей запятой размером double для просмотра значений, переданных в printf() — может иметь смысл использовать printf("%16.12f\n", f2);, чтобы увидеть больше полной точности значения как double (потому что это double, когда он обрабатывается printf().   -  person Jonathan Leffler    schedule 23.03.2018
comment
@JonathanLeffler: количество цифр, которое может правильно создать реализация C, является функцией (качества) реализации C, а не конкретным типом с плавающей запятой. В различных местах стандарт C определяет преобразование в десятичное число, стандарт рекомендует правильно округлять результаты до DECIMAL_DIG цифр, а DECIMAL_DIG является функцией самого широкого поддерживаемого реализацией типа с плавающей запятой. Таким образом, double, например, должно быть конвертируемым с числом цифр, достаточным для различения long double, даже если оно передается printf только как double.   -  person Eric Postpischil    schedule 23.03.2018


Ответы (3)


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

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

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

Размер шага зависит от величины чисел. Для двоичных чисел с плавающей запятой оно удваивается в 2, затем в 4, затем в 8 и так далее. Ниже 1 оно уменьшается вдвое, а при ½, ¼ и так далее.

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

Две цифры, использованные в вашем примере кода, 1.192092897e-7f и 1.192092896e-7f, настолько близки друг к другу, что преобразуются в одно и то же значение float, 2−23. Именно поэтому нет никакой разницы в ваших f2 и f3.

Между f1 и f2 есть разница, но вы не напечатали достаточно цифр, чтобы отобразить ее.

Вы спрашиваете: «Разве значение float для x + FLT_EPSILON и x - FLT_EPSILON не должно быть одинаковым?», но ваш код не содержит x - FLT_EPSILON.

Re: «Мой вопрос заключается в том, что R значение с плавающей запятой x, каково значение y, которое x + y || x - y равно тому же значению R с плавающей запятой?» Это тривиально удовлетворяется y = 0. Вы хотели спросить, какое наибольшее значение y удовлетворяет условию? Это немного сложно.

Размер шага для числа x называется ULP числа x, который мы можем рассматривать как функцию ULP(x). ULP означает единицу наименьшей точности. Это разрядное значение наименьшей цифры в представлении x с плавающей запятой. Это не константа; это функция x.

Для большинства значений, представляемых в формате с плавающей запятой, наибольшее y, удовлетворяющее вашему условию, равно ½ ULP(x) наименьшей цифры в представлении x с плавающей запятой. четно, а если цифра нечетная, то чуть меньше ½ ULP(x). Это усложнение возникает из-за того, что результаты арифметики округляются до ближайшего представимого значения, и в случае равенства выбирается значение с четным младшим разрядом. Таким образом, добавление ½ ULP(x) к x даст ничью, которая будет округлена до x, если младшая цифра четная, но не будет округлить до x, если младшая цифра нечетная.

Однако для x, находящихся на границе изменения ULP, наибольшее y, удовлетворяющее вашему условию, равно ¼ ULP(x). Это связано с тем, что чуть ниже x (по величине) изменяется размер шага, а следующее число меньше x равно половине x'. размер шага s вместо обычного полного размера шага. Таким образом, вы можете пройти только половину пути к этому значению, прежде чем изменить результат вычитания, поэтому максимальное значение y равно ¼ ULP(x).

person Eric Postpischil    schedule 23.03.2018
comment
В float.h: #define FLT_EPSILON 1.192092896e-7f. "Smallest such that 1.0+FLT_EPSILON !=1.0" Принимая это во внимание, 1.0+1.192092895e-7f==1.0 должно быть правдой. Также я только что заметил, что значения с плавающей запятой, которые я добавлял, неверны, они должны быть 1.192092896e-7f и 1.192092895e-7f соответственно. - person Pedro Pereira; 23.03.2018
comment
Как я вижу, спасибо за объяснение. Но какова функция ULP? - person Pedro Pereira; 23.03.2018
comment
@PedroPereira: Во-первых, 1.192092895e-7f не меньше FLT_EPSILON и не меньше 1.192092897e-7f. В исходном коде строка «1.192092895e-7f» является литералом, представляющим значение с плавающей запятой. Значение, которое оно представляет, не является математическим числом 1,192092895*10^-7, а является результатом преобразования этого числа в формат float. В этом преобразовании число округляется до ближайшего представимого значения. Поскольку оно очень близко к ближайшему представимому значению, равному 1,1920928955078125*10^-7, оно округляется до этого значения, равного FLT_EPSILON. - person Eric Postpischil; 23.03.2018
comment
@PedroPereira: Во-вторых, «наименьший такой, что 1,0+FLT_EPSILON !=1,0» не является правильным описанием FLT_EPSILON, и я не знаю, откуда взялась эта цитата. Правильное описание FLT_EPSILON — это «разница между 1 и наименьшим значением, большим 1, которое может быть представлено в данном типе с плавающей запятой», причем в данном случае типом с плавающей запятой является float. Это исходит из стандарта C. Это подразумевает, что 1 + FLT_EPSILON != 1, но это не самое маленькое такое значение, которое выполняет это, потому что вы можете иметь x = 0,75 * FLT_EPSILON, а затем 1+x != 1 из-за округления. - person Eric Postpischil; 23.03.2018
comment
@PedroPereira: верно то, что если x1 является наименьшим значением, большим 1, которое получается из 1+x для любого x, то x1-x равно FLT_EPSILON. - person Eric Postpischil; 23.03.2018
comment
@PedroPereira: для базовых 32-битных двоичных чисел с плавающей запятой IEEE-754 ULP(x) больше 2^(floor(log2(|x|)-23)) и 2^-149 при условии, что x меньше 2^128. - person Eric Postpischil; 23.03.2018
comment
Спасибо за ваше время, я думаю, что понял, что вы мне говорите. Поэтому я не могу знать наибольшее значение, которое я могу добавить к числу, которое не изменит его, потому что функция ULP не является линейной. Верно? Имея это в виду, как я могу изучать численный анализ своих методов, если я не могу указать число на неопределенность значений типа float? - person Pedro Pereira; 23.03.2018
comment
Я чувствую, что я на шаг (или несколько) назад, чем начальная точка - person Pedro Pereira; 23.03.2018
comment
@PedroPereira: численный анализ является предметом значительного объема. По ней есть учебники и научные статьи. Общего ответа нет. Вам придется подробно описать свое программное обеспечение, чтобы кто-нибудь мог проанализировать его числовые свойства. Иногда люди выполняют статистическую оценку ошибки, слегка изменяя входные данные и наблюдая, насколько сильно различаются результаты, и/или сравнивая их с результатами, рассчитанными с большей точностью. Они не являются доказательствами, но могут дать представление о природе ошибок в конкретных алгоритмах. - person Eric Postpischil; 23.03.2018
comment
Это был метод, который я использовал для измерения ошибки, но мой профессор-диссертант (я не знаю термина на английском языке) хочет, чтобы я был как можно более тщательным с научной точки зрения. Короче говоря, мне нужно сделать этот анализ, чтобы показать ему, какой метод лучше для численного анализа, если практический подход или теоретический. - person Pedro Pereira; 23.03.2018

Float — это 32 битное число с плавающей запятой одинарной точности IEEE 754: 1 бит для знака, 8 бит для показателя степени и 23* для значения, т. е. число с плавающей запятой имеет точность 7 десятичных цифр.

Увеличьте количество напечатанных цифр на printf, чтобы увидеть больше, но после 7 цифр это просто шум:

#include <stdio.h>

int main(void) {

 float f1 = 1.234567f;
 float f2 = 1.234567f + 1.192092897e-7f;
 float f3 = 1.234567f + 1.192092896e-7f;

 printf("Inicial:\t%.16f\n", f1);
 printf("Inicial:\t%.16f\n", f2);
 printf("Inicial:\t%.16f\n\n", f3);

 return 0;
}

Выход:

Inicial:        1.2345670461654663                                                                                                           
Inicial:        1.2345671653747559                                                                                                           
Inicial:        1.2345671653747559 
person sg7    schedule 23.03.2018
comment
Мой вопрос не связан с проблемой printf, это не входит в объем ответа, который я ищу. - person Pedro Pereira; 23.03.2018

float f1 = 1.234567f;
float f2 = f1 + 1.192092897e-7f;
float f3 = f1 + 1.192092896e-7f;

printf("Inicial:\t%.20f\n", f1);
printf("Inicial:\t%.20f\n", f2);
printf("Inicial:\t%.20f\n\n", f3);

Вывод:

Inicial:        1.23456704616546630000
Inicial:        1.23456716537475590000
Inicial:        1.23456716537475590000

Нет, ваше ожидание неверно
В первом вызове printf вы печатаете переменную f1 без какого-либо эффекта, которая представляет собой просто 1.234567f.

person Beyondo    schedule 23.03.2018