2016-02-19 2 views
1

В моем курсе по информатике мы изучаем числа с плавающей запятой и то, как они представлены в памяти. Я уже понимаю, как они представлены в памяти (мантисса/значимость, экспонента и его предвзятость и знаковый бит), и я понимаю, как добавляются и вычитаются поплавки друг от друга (денормализация и все эти забавные вещи). Однако, изучая некоторые вопросы изучения, я заметил то, что я не могу объяснить.Точность добавления плавающих чисел против умножения Float на Integer

Когда поплавок, который не может быть точно представлен, добавляется к нему несколько раз, ответ ниже, чем мы математически ожидаем, но когда тот же самый поплавок умножается на целое число, ответ получается точно до правильного числа ,

Вот пример из нашего исследования вопросов (пример написан на Java, и я редактировал его для простоты):

float max = 10.0f; /* Defined outside the function in the original code */ 
float min = 1.0f; /* Defined outside the function in the original code */ 
int count = 10; /* Passed to the function in the original code */ 
float width = (max - min)/count; 
float p = min + (width * count); 

В этом примере, мы сказали, что результат выходит точно 10.0. Однако, если мы посмотрим на эту проблему в виде суммы поплавков, мы получаем несколько иной результат:

float max = 10.0f; /* Defined outside the function in the original code */ 
float min = 1.0f; /* Defined outside the function in the original code */ 
int count = 10; /* Passed to the function in the original code */ 
float width = (max - min)/count; 

for(float p=min; p <= max; p += width){ 
    System.out.printf("%f%n", p); 
} 

Мы сказали, что окончательное значение p в этом тесте ~9.999999 с разницей -9.536743E-7 между последним значение p и значение max. С логической точки зрения (зная, как работают поплавки) это значение имеет смысл.

Однако, я не понимаю, почему мы получаем ровно 10,0 для первого примера. Математически, имеет смысл, что мы получим 10.0, но зная, как поплавки хранятся в памяти, для меня это не имеет смысла. Может ли кто-нибудь объяснить, почему мы получаем точное и точное значение, умножая неточное значение float на int?

EDIT: Чтобы уточнить, в исходных вопросах исследования некоторые значения передаются функции, а другие объявлены вне функции. Мои примеры кода - это сокращенные и упрощенные версии примеров вопросов исследования. Поскольку некоторые из значений передаются в функцию, а не явно определяются как константы, я считаю, что упрощение/оптимизация во время компиляции можно исключить.

+0

Поскольку компилятор уменьшил все это до постоянного значения. Попробуйте сделать каждый оператор функцией и вызвать один за другим. – Amit

+0

@ Признаюсь, мои извинения, я должен был это ясно понять в моем вопросе. Некоторые из значений, определенных в примерах, передаются как переменные в функцию, которая вычисляет конечный результат, поэтому маловероятно, что это будет оптимизация компилятора. Я пытался упростить код для этого сообщения, поэтому я определил значения в примерах. Я скоро сделаю редактирование, чтобы уточнить это. – SpencerD

+0

Если вы не собираетесь удивить меня своим редактированием, мой комментарий (если вы хотите, я отправлю его в качестве ответа) все равно будет держать. Компилятор оптимизирует все операторы до значения 'max', потому что все операторы выполняют вычисления взад и вперед. – Amit

ответ

1

Потому что 1.0 + ((10.0 - 1.0)/10.0) * 10.0 делает только 1 расчет с неточными значениями, таким образом, 1 ошибка округления, более точна, чем выполнение 10 добавлений представления float 0.9f. Я думаю, что это основной принцип, который должен преподаваться в этом примере.

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

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

Я думаю, чтобы понять, что происходит Я найду сообщение Кобина Д-ра Доббса по этой теме, это просветительское чтение, серия кулиминатов, показывающая, как такие языки, как perl, python &, вероятно, java make вычисления выглядят точными, достаточно точно.

Koenig's Dr Dobbs article on floating point

Even Simple Floating-Point Output Is Complicated

Не удивляйтесь, если неподвижная точка арифметика добавляется к процессорам 5-10 лет из, финансовые люди, как суммы, чтобы быть точным.

+0

Определенно полезный ответ, который может объяснить, что здесь происходит. Однако 'width' (значение' ~ 0.9') умножается на 10, а не 'min' (значение' 1.0'). Тем не менее, сообщение в блоге, с которым вы связались, оставило меня с интересной идеей. Когда 'width' добавляется к себе 10 раз, денормализация не происходит, потому что показатель ширины' 'очевидно один и тот же. Затем, когда этот результат добавляется к 'min', он достаточно велик, что денормализация там тоже не встречается. Таким образом, нет никакой потери точности, поэтому значение ширины «* достаточно точное *» считается точным. – SpencerD

+1

Компилятор может упростить выражение, как записано. у вас есть деление по счету, за которым следует многозадачность. Аналогично min + max - min, можно свести к 'float p = max;' Компиляторы в наши дни умны. – Rob11311

+0

Аппаратное обеспечение перемещает номера, чтобы масштабировать их, поскольку Амит указал, что умный компилятор может обнаружить, что вы умножаетесь на то же значение, которое вы разделили на. Компилятор НЕ хочет делать дорогостоящие конверсии, от 10 до 10.0f во время выполнения. Поэтому, чтобы проверить эту теорию, вам нужно вводить счетчик во время выполнения как float to. Он должен быть более точным, чем 10 дополнений, но он не может быть сведен к 'float p = max;' во время компиляции. И спасибо за тиканье ответа, вы должны спешить часто, чтобы войти первым, затем улучшить ответ, или вы найдете кого-то еще дубликатов, когда вы его пишете. – Rob11311

2

Во-первых, некоторые придирки:

Когда поплавок, который не может быть точно представлено

Там нет «поплавок, который не может быть точно представлено.» Все float s могут быть точно представлены как float s.

добавляются к себе несколько раз, ответ ниже, чем мы бы математически ожидать,

При добавлении номера к себе несколько раз, вы можете получить что-то высшего чем вы возможно. Я буду использовать C99 hexfloat notation. Рассмотрим f = 0x1.000006p+0f. Затем f+f = 0x1.000006p+1f, f+f+f = 0x1.800008p+1f, f+f+f+f = 0x1.000006p+2f, f+f+f+f+f = 0x1.400008p+2f, f+f+f+f+f+f = 0x1.80000ap+2f и f+f+f+f+f+f+f = 0x1.c0000cp+2f. Однако 7.0*f = 0x1.c0000a8p+2, который округляется до 0x1.c0000ap+2f, менее f+f+f+f+f+f+f.

, но когда этот же поплавок умножается на целое число, ответ выдается точно на правильное число.

7 * 0x1.000006p+0f не может быть представлен как IEEE float. Поэтому он округляется. В режиме округления округления от округления до ближайшего с привязкой к нужному, вы получите самый близкий поплавок к вашему точному результату, когда вы выполняете такую ​​арифметическую операцию, как эта.

Дело в том, что я не понимаю, почему мы получаем ровно 10,0 для первого примера. Математически, имеет смысл, что мы получим get 10.0, но, зная, как поплавки хранятся в памяти, это не имеет смысл для меня. Может ли кто-нибудь объяснить, почему мы получаем точное и точное значение , умножая неточное значение float на int?

Чтобы ответить на ваш вопрос, вы получите разные результаты, потому что выполняли разные операции. Это немного случайный случай, когда вы получили «правильный» ответ здесь.

Давайте переключаем номера вокруг. Если я вычислил 0x1.800002p+0f/3, я получаю 0x1.00000155555...p-1, который округляется до 0x1.000002p-1f. Когда я втройне, я получаю 0x1.800003p+0f, который раундов (с тех пор, как мы разорвали связь до уровня) до 0x1.800004p+0f. Это тот же результат, что и я, если бы вычислил f+f+f в float арифметике, где f = 0x1.000002p-1f.

Смежные вопросы