Для меня ошибки с плавающей запятой - это, по сути, ошибки, которые в x86 приводят к исключению с плавающей запятой (при условии, что сопроцессор имеет это прерывание включенным).Особый случай - «неточное» исключение, т. Е. Когда результат не был точно представлен в формате с плавающей точкой (например, при делении 1 на 3).Новички, которых еще нет дома в мире с плавающей запятой, будут ожидать точных результатов и будут считать этот случай ошибкой.
На мой взгляд, существует несколько доступных стратегий.
- Ранние данныепроверка таким образом, что неверные значения идентифицируются и обрабатываются при входе в программное обеспечение.Это уменьшает потребность в тестировании во время самих операций с плавающей запятой, что должно повысить производительность.
- Поздняя проверка данных, так что неверные значения выявляются непосредственно перед их использованием в реальных операциях с плавающей запятой.Должно привести к снижению производительности.
- Отладка с включенными прерываниями исключения с плавающей запятой.Это, вероятно, самый быстрый способ получить более глубокое понимание проблем с плавающей запятой в процессе разработки.
, если назвать только несколько.
Когда я написал собственный движок баз данных более двадцатиНесколько лет назад, используя 80286 с сопроцессором 80287, я выбрал форму поздней проверки данных и использования примитивных операций x87.Поскольку операции с плавающей запятой были относительно медленными, я хотел избегать сравнения с плавающей запятой каждый раз, когда загружал значение (некоторые из которых могли вызвать исключения).Чтобы достичь этого, мои значения с плавающей запятой (двойной точности) представляли собой объединения с целыми числами без знака, так что я проверял бы значения с плавающей запятой, используя операции x86, прежде чем были вызваны операции x87.Это было громоздко, но целочисленные операции были быстрыми, и когда операции с плавающей запятой вступили в действие, рассматриваемое значение с плавающей запятой было бы готово в кеше.
Типичная последовательность C (деление двух матриц с плавающей запятой) выгляделачто-то вроде этого:
// calculate source and destination pointers
type1=npx_load(src1pointer);
if (type1!=UNKNOWN) /* x87 stack contains negative, zero or positive value */
{
type2=npx_load(src2pointer);
if (!(type2==POSITIVE_NOT_0 || type2==NEGATIVE))
{
if (type2==ZERO) npx_pop();
npx_pop(); /* remove src1 value from stack since there won't be a division */
type1=UNKNOWN;
}
else npx_divide();
}
if (type1==UNKNOWN) npx_load_0(); /* x86 stack is empty so load zero */
npx_store(dstpointer); /* store either zero (from prev statement) or quotient as result */
npx_load будет загружать значение в верхнюю часть стека x87, если оно допустимо.В противном случае вершина стека будет пустой.npx_pop просто удаляет значение, которое в данный момент находится на вершине x87.Кстати, «npx» является аббревиатурой от «Расширения числового процессора», как его иногда называли.
Выбранный метод был моим способом решения проблем с плавающей запятой, вытекающих из моего собственного разочаровывающего опыта при попытке получить решение сопроцессора.вести себя в приложении предсказуемо.
Конечно, это решение приводило к накладным расходам, но о чистом
*dstpointer = *src1pointer / *src2pointer;
не могло быть и речи, поскольку оно не содержало обработки ошибок.Дополнительные затраты на обработку ошибок были более чем компенсированы тем, как были подготовлены указатели на значения.Кроме того, случай 99% (оба значения действительны) довольно быстрый, так что если дополнительная обработка для других случаев медленнее, ну и что?