Предотвратить недополнение в делении с плавающей запятой в Python

Предположим, что и x, и y — очень маленькие числа, но я знаю, что истинное значение x / y разумно.

Как лучше всего вычислить x/y? В частности, вместо этого я делал np.exp(np.log(x) - np.log(y), но я не уверен, что это вообще что-то изменит?


person p-value    schedule 10.01.2018    source источник
comment
Можете ли вы привести пример двух чисел, где это не удастся? IEEE с плавающей запятой, как правило, очень надежен. Вы говорите, что x и y слишком малы для представления в IEEE с плавающей запятой?   -  person Mark Ransom    schedule 10.01.2018
comment
На самом деле, у меня нет конкретного неудачного примера --- я просто боялся, что x/y не получится, и делал np.exp(np.log(x) - np.log(y), но потом мне стало интересно, имеет ли это какое-то значение. Немного подредактирую вопрос.   -  person p-value    schedule 10.01.2018
comment
Я не могу представить сценарий, в котором использование log и exp дало бы лучший результат, чем прямое деление. Хотя известно, что иногда мне не хватало воображения.   -  person Mark Ransom    schedule 10.01.2018
comment
@MarkRansom Возможно, вы правы, но я тоже не уверен ...   -  person p-value    schedule 10.01.2018


Ответы (2)


Согласно документации Python, Python использует функции работы с плавающей запятой того оборудования, на котором он работает. На большинстве современных машин это арифметика IEEE-754 или что-то близкое к ней. В этой документации Python явно не указан режим округления, но вскользь упоминается, что результатом выборочного деления является ближайшее представимое значение, поэтому, предположительно, Python использует режим округления до ближайших связей до четности. («Округление до ближайшего» для краткости. Если два представимых значения одинаково близки в двоичном формате с плавающей запятой, создается одно с нулем в младшем бите его мантиссы.)

В арифметике IEEE-754 в режиме округления до ближайшего результатом деления является представимое значение, ближайшее к точному математическому значению. Поскольку вы говорите, что математическое значение x/y разумно, оно находится в нормальном диапазоне представимых значений (не ниже его, в субнормальном диапазоне, где страдает точность, и не выше него, где результаты округляются до бесконечности). В нормальном диапазоне результаты элементарных операций будут точными в пределах нормальной точности формата.

Однако, поскольку x и y являются «очень маленькими числами», мы можем быть обеспокоены тем, что они субнормальны и имеют потерю точности уже в них, прежде чем будет выполнено деление. В базовом 64-битном двоичном формате IEEE-754 числа меньше 2-1022 (около 2,22507•10-308) являются субнормальными. Если x и y меньше этого, то они уже потеряли точность, и ни один метод не может получить из них правильное частное, кроме как по чистой случайности. Логарифмирование для вычисления частного не поможет.

Если машина, на которой вы работаете, не использует IEEE-754, вполне вероятно, что прямое вычисление x/y даст лучший результат, чем np.exp(np.log(x)-np.log(y)). Первая представляет собой единственную операцию, вычисляющую базовую функцию аппаратного обеспечения, которая, вероятно, была разумно спроектирована. Последний представляет собой несколько операций, вычисляющих сложные функции в программном обеспечении, которые трудно сделать точными, используя обычные аппаратные операции.

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

Примечание

Нередким отклонением от IEEE-754 является обнуление ненормальных значений. Если у вас есть некоторые x и некоторые y, которые являются субнормальными, некоторые реализации могут сбросить их до нуля перед выполнением над ними операций. Однако это чаще встречается в коде SIMD, чем в обычном скалярном программировании. И, если бы это произошло, это в любом случае помешало бы вам оценить np.log(x) и np.log(y), так как субнормальные значения также были бы сброшены до нуля в них. Так что мы, вероятно, можем отбросить эту возможность.

person Eric Postpischil    schedule 10.01.2018
comment
Вы уверены, что логарифмирование не поможет? Хотя x и y денормальны, их логарифмы - нет, поэтому их использование может дать лучший результат, несмотря на более сложные вычисления. - person Rudy Velthuis; 10.01.2018
comment
@RudyVelthuis: Как это может помочь? Результат прямого деления уже является наилучшим из возможных (ближайшее представимое число к точному частному), если соблюдается семантика IEEE 754. - person Mark Dickinson; 10.01.2018
comment
Почему вы думаете, что логарифмы дают лучший результат? В IEEE-754 результатом деления x и y является представимое значение, наиболее близкое к математически точному значению. Не существует более подходящего значения. Поэтому невозможно вернуть лучший результат в формате с плавающей запятой. - person Eric Postpischil; 10.01.2018
comment
@MarkDickinson: я предполагаю, что значения x и y денормальны и, следовательно, довольно неточны. Но если они были вычислены с использованием логарифмов, логарифмы не денормальны. Вычитание по-прежнему должно иметь хорошую точность, а возведение в степень должно быть лучше, чем простое деление денормали. - person Rudy Velthuis; 10.01.2018
comment
@RudyVelthuis: извините, но вы, кажется, неправильно понимаете, как работают элементарные операции с плавающей запятой. Тот факт, что входные значения x и y для деления субнормальны, не повлияет на точность деления. Операция деления IEEE-754 возвращает представляемое значение, ближайшее к точному математическому результату. Период. Он не использует меньшую точность, потому что входные данные субнормальны. Он возвращает полную точность результата. Так указано в стандарте. Вот что он делает. - person Eric Postpischil; 10.01.2018
comment
@RudyVelthuis: Теперь x и y могут иметь меньшую точность, потому что они субнормальны. В предыдущих вычислениях могли возникнуть ошибки, и теперь x и y могут быть не такими, какими вы хотите их видеть. Но какими бы они ни были в настоящее время, это будет вкладом в отдел. Точно. И деление даст правильный результат. И если x и y имеют ошибки предыдущих операций, их логарифмирование не поможет. - person Eric Postpischil; 10.01.2018
comment
@Eric: да, это верно для неденормалов. И да, x/y даст наилучший результат, который может дать аппаратное обеспечение, даже для денормалей, но в любом случае это часто не очень хорошо. Скажем, x и y являются результатом чего-то вроде pow(10, -300) и pow(10, -308). Тогда логарифмы равны 300 и 308 соответственно. В результате должно получиться 1e8, и вы можете получить его довольно точно с pow(10, 308-300), но не обязательно очень точно с 1.0e-300/1.0e-308. Я не могу попробовать это сейчас, но это должно быть легко проверить. Но журналы должны обновляться сразу же, а не только тогда, когда x и y уже стали денормальными. - person Rudy Velthuis; 10.01.2018
comment
@Eric: нет, я, конечно, правильно понимаю, как работают операции FP. Важно то, что если вы знаете, что результаты могут стать денормальными, вы начинаете использовать журналы с самого начала и вычитаете их, когда требуется умножение реальных значений. Только в конце вы возвеличиваетесь. Это действительно зависит от того, как вы получили числа x и y и были ли журналы достаточно точными с самого начала. - person Rudy Velthuis; 10.01.2018
comment
@RudyVelthuis: если x является результатом pow(10, -300), а y является результатом pow(10, -308), то деление x на y даст представимый результат, ближайший к точному математическому результату x/ у. Лучшего результата не существует. Если вы жалуетесь, что x/y с плавающей запятой будет иметь какую-то ошибку, отличную от pow(10, 8), то это потому, что при их делении уже есть ошибки в x и y. Если в x и y уже есть ошибки, то логарифмирование, вычитание и возведение в степень эти ошибки не удалят. - person Eric Postpischil; 10.01.2018
comment
@RudyVelthuis: использование журналов с самого начала отличается от использования журналов для вычисления отношения x и y. - person Eric Postpischil; 10.01.2018
comment
Я бы хотел оставить это в открытом доступе, пока. Но ISTM, использующий журналы с самого начала, - это то, что делал OP. Логи уже денормализованных значений, конечно, не помогут. На самом деле, используя журналы с самого начала, вы могли бы легко вычислить значения, намного меньшие наименьшей денормальности, и при этом получить довольно хорошие результаты для x/y. - person Rudy Velthuis; 10.01.2018
comment
@RudyVelthuis См. ideone.com/jFS5P0 . Ошибка возникает из-за округления, а не из-за вычислений. Добавление двух дополнительных шагов округления не поможет. - person Sneftel; 10.01.2018
comment
Конечно, ошибка возникает из-за округления и из-за того, что денормали не имеют требуемой точности. Но pow(10, -308 - -316) точнее, чем 1.0e-308/1.0e-316. Таким образом, если вы знаете логарифмы с самого начала (здесь log10 — это -308 и -316), вы можете сохранять точность, как только узнаете, что можете перейти к денормалям. - person Rudy Velthuis; 10.01.2018
comment
Существует множество ситуаций, когда неявное отображение чисел в более правильное представление является хорошей идеей (в частности, иногда лучше хранить 1-x, чем x). Но это был не вопрос ОП. - person Sneftel; 10.01.2018
comment
@EricPostpischil Вау, большое спасибо за исчерпывающий ответ! На самом деле, x и y в моем случае составляют примерно 10E-10; Я не знал, что субнормальный предел снижается до 10E-308. И да, мне как раз было интересно, действительно ли взятие журналов еще больше испортит ситуацию, как вы указали. - person p-value; 10.01.2018

Деление, как и другие операции, указанные в IEEE-754, вычисляется с бесконечной точностью, а затем (с обычными правилами округления) округляется до ближайшего представимого числа с плавающей запятой. Результат вычисления x/y почти наверняка будет намного точнее, чем результат вычисления np.exp(np.log(x) - np.log(y) (и гарантированно не будет менее точен).

person Sneftel    schedule 10.01.2018
comment
Python использует базовое аппаратное обеспечение с плавающей запятой. На большинстве распространенных машин это может быть IEEE-754, но это не гарантируется. - person Eric Postpischil; 10.01.2018
comment
У меня есть небольшая проблема с бесконечной точностью. Точность обычно выше двойной, но не бесконечна. - person Rudy Velthuis; 10.01.2018
comment
@RudyVelthuis Нет, это бесконечно. Очевидно, что когда вы вычисляете что-то вроде 1/3, на самом деле невозможно иметь бесконечную последовательность 10101010101... перед округлением, но процессор должен найти способ вычисления окончательного значения, как если бы он это сделал. Все ошибки должны быть связаны с округлением, а не с вычислениями. FWIW, словосочетание бесконечной точности взято непосредственно из стандарта IEEE-754. - person Sneftel; 10.01.2018
comment
Процессор просто делает это, вычисляя несколько дополнительных битов точности (например, 64 бита значащей вместо 53), а затем округляя их до требуемой точности. Я не знаю, описывает ли это так IEEE-745, но на самом деле этого просто не происходит. - person Rudy Velthuis; 10.01.2018
comment
@RudyVelthuis Похоже, вы описываете форматы с повышенной точностью, но они не связаны (в конце концов, они тоже должны быть правильно округлены на основе результатов с бесконечной точностью). Стандартный метод вычисления для сложения/вычитания использует защитные биты и липкие биты, но (IIRC) всего 3. Чтобы ответить на ваш вопрос, если кто-то думает, что их процессор выполняет бесконечный объем работы для операции с плавающей запятой: вы правы, это не так. - person Sneftel; 10.01.2018
comment
@RudyVelthuis: IEEE-754 требует, чтобы реализация вычисляла результат, как если бы точный математический результат был вычислен с бесконечной точностью, а затем округлен до ближайшего представимого значения. Чтобы реализовать это на аппаратном уровне, разработчики рассчитывают, какое количество бит и какие данные им нужно использовать для получения требуемого результата. Если они используют фиксированную величину расширенной точности, они получают доказательства того, что используемой ими точности достаточно для получения требуемого результата. - person Eric Postpischil; 10.01.2018
comment
@RudyVelthuis: Чтобы продолжить, общая часть реализаций с плавающей запятой состоит в том, чтобы создать фиксированное количество битов плюс некоторую дополнительную информацию, которая не является дополнительным битом результата, но является своего рода другим указанием. Например, может быть фиксированное количество бит плюс остаток, который все еще необходимо учитывать. Изучение этого остатка помогает определить, ближе ли точный математический результат к тому или иному представимому значению. Аппаратное обеспечение делает это и возвращает результат, описанный стандартом. Результат такой же, как если бы были выполнены бесконечно точные математические вычисления. - person Eric Postpischil; 10.01.2018