2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




На страницу 1, 2  След.
 
 Деление на 0
Сообщение23.07.2014, 13:19 
Аватара пользователя
Всем привет! Вопрос собственно о корректности математических действий. В частном случае это деление на ноль или достаточно близкое к нулю число, которое приводит к вылету программы. В Delphi строю логику на обработке исключений:
Используется синтаксис Delphi
procedure Proc1();
var a,b,c: Double;
begin
//...
   try
      a := b / c;
   excpet
      on EMathError do a := -1;
   end;
end;
 

Стоит отметить, что класс исключений EMathError хорош тем, что перехватывает все математические исключения: деление на 0, переполнение (логарифм нуля или числа достаточно близкого к нулю) и пр.
Возможно ли реализовать на С/С++ нечто подобное? Решение типа cath(...) не работает.
Используется синтаксис C++
void Proc1()
{
   double a, b, c;
   //...
   try
   {
      a = b / c;
   }
   catch (...)
   {
      a = -1.0;
   }
}
 

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 14:08 
Аватара пользователя
С double вы же можете просто проверить результат на INF и NaN. Некорректные математические действия приводят к ним.

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 14:38 
Аватара пользователя
http://en.cppreference.com/w/cpp/numeric/fenv в самом низу.

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 15:04 
Legioner93 в сообщении #889652 писал(а):
С double вы же можете просто проверить результат на INF и NaN.
А еще лучше проверить на корректность делитель:
код: [ скачать ] [ спрятать ]
Используется синтаксис C++
#include <iostream>
#include <stdexcept>

double divide(double x, double y)
{
    if (y == 0.0)
    {
        throw std::invalid_argument("Division by zero!");
    }
   
    return x / y;
}

double f(double x, double y)
{
    double q;
       
    try
    {
        q = divide(x, y);
    }
    catch (...)
    {
        q = -1.0;
    }
   
    return q;
}

int main()
{
    std::cout << f(10.0, 2.0) << std::endl;
    std::cout << f(10.0, 0.0) << std::endl;
}
Legioner93 в сообщении #889652 писал(а):
Некорректные математические действия приводят к ним.
По стандарту IEEE 754 деление на ноль $\text{---}$ вполне корректное математическое действие. :wink:

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 15:08 
Аватара пользователя
EtCetera в сообщении #889681 писал(а):
По стандарту IEEE 754 деление на ноль $\text{---}$ вполне корректное математическое действие.

По стандарту IEEE 754 деление на ноль может считаться как корректным, так и некорректным, в зависимости от настройки FPU.

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 15:27 
Munin в сообщении #889686 писал(а):
По стандарту IEEE 754 деление на ноль может считаться как корректным, так и некорректным, в зависимости от настройки FPU.
Да, фразу "по умолчанию" я забыл вставить.

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 15:28 
Аватара пользователя
EtCetera в сообщении #889681 писал(а):
Legioner93 в сообщении #889652 писал(а):
С double вы же можете просто проверить результат на INF и NaN.
А еще лучше проверить на корректность делитель

Ну, насчёт лучше вы бы так категорично не заявляли... Оверхед же. Всё зависит от ситуации.
NaN и INF достаточно удобные, чтобы их использовать как раз в подобных целях, хотя бы иногда.
Вот целочисленное деление на 0 - оно да, программу останавливает.
EtCetera в сообщении #889681 писал(а):
Legioner93 в сообщении #889652 писал(а):
Некорректные математические действия приводят к ним.
По стандарту IEEE 754 деление на ноль $\text{---}$ вполне корректное математическое действие. :wink:

Корректность я имел в виду с точки зрения математики.

-- Ср июл 23, 2014 16:30:01 --

EtCetera
Munin
Вы сейчас про какое деление-то? Если в int, так оно UB. А если в double, то что означает термин некорректность?

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 15:35 
Legioner93 в сообщении #889701 писал(а):
EtCetera в сообщении #889681 писал(а):
Legioner93 в сообщении #889652 писал(а):
С double вы же можете просто проверить результат на INF и NaN.
А еще лучше проверить на корректность делитель
Ну, насчёт лучше вы бы так категорично не заявляли... Оверхед же.
Если производить проверку до выполнения операции, то накладные расходы есть, а если после $\text{---}$ их уже нет?
Я уж не говорю о том, что операции с NaN'ами на многих процессорах отличаются катастрофической медлительностью.

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 15:51 
Аватара пользователя
EtCetera в сообщении #889706 писал(а):
Если производить проверку до выполнения операции, то накладные расходы есть, а если после $\text{---}$ их уже нет?
Я уж не говорю о том, что операции с NaN'ами на многих процессорах отличаются катастрофической медлительностью.

Давайте сразу определимся, о какой ситуации мы говорим?
О простом делении $a$ на $b$ прямо по месту (без вызова функции!), с использованием результата тут и сразу? Например для печати, или для чего-то другого, но не как промежуточный результат для последующих вычислений?
Тогда особой разницы нет.
Но я, отвечая вам, смотрел на приведенный код. И там вы завернули деление в функцию. При таком подходе разница есть. Напишу, если хотите.

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 16:00 
Аватара пользователя
Legioner93 в сообщении #889701 писал(а):
Вы сейчас про какое деление-то? Если в int, так оно UB.

По POSIX, деление на 0 в int вызывает тот же SIGFPE, что и fp исключения. Это где-то на StackOverflow было упомянуто.

EtCetera в сообщении #889706 писал(а):
Я уж не говорю о том, что операции с NaN'ами на многих процессорах отличаются катастрофической медлительностью.

Ничё себе. Это интересно. Они же, как раз, проще: garbage in, garbage out. Чего именно вы меряли, и на каких процессорах?

EtCetera в сообщении #889706 писал(а):
Если производить проверку до выполнения операции, то накладные расходы есть, а если после $\text{---}$ их уже нет?

Их может быть просто меньше. Допустим, у вас формула из 8 входных значений, тогда вы должны в одном случае проделать 15 проверок, а в другом - всего одну, в самом конце.

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 16:30 
Аватара пользователя
В данном случае речь идёт о вещественных числах. Насколько я понял FPU входит в CPU. И существует два режима работы FPU:
1. С генерацией исключений (который вроде как по-умолчанию);
2. Без генерации исключений с константами типа INF(1.0/0.0) и NaN(0.0/0.0);
Последний режим действительно работает быстрее, как сообщается в Embarcadero RAD Studio Help:
Цитата:
When using OpenGL to render 3D graphics, we recommend that you disable all floating-point exceptions for performance reasons. To do this, call Set8087CW(0x133f) in...

Set8087CW задаёт контрольное слово (control word) FPU, которое задаёт маску исключений FPU при работе с вещественными числами. В общем попробовал. Работает:
код: [ скачать ] [ спрятать ]
Используется синтаксис C++
#include <stdio.h>
#include <conio.h>
#include <System.hpp>
#include <Math.hpp>

void GetDouble(const char *text, double *pValue)
{
        printf(text);
        scanf("%lf", pValue);
        fflush(stdin);
}
//-----------------------------------------------------------------------------
int main()
{
        double a, b, c;

        Set8087CW(0x133F);

        //получаем данные
        GetDouble("a= ", &a);
        GetDouble("b= ", &b);

        //расчет
        c = a / b;

        //вывод данных
        if (IsInfinite(c))
                printf("Infinite result\n");
        else if (IsNan(c))
                printf("Not a number\n");
        else
                printf("c= %G\n", c);

        printf("Press any key to exit...");
        _getch();
        return 0;
}
//-----------------------------------------------------------------------------
 


-- Ср июл 23, 2014 17:08:10 --

Справедливости ради, стоит отметить, что под Windows многие компиляторы реализуют так называемую структурную обработку исключений (SEH - structure exception handling), которая хорошо описана у Назара и Рихтера. Поэтому на С++ ошибки можно обрабатывать с помощью SEH:
код: [ скачать ] [ спрятать ]
Используется синтаксис C++
#include <stdio.h>
#include <conio.h>
#include <windows.h>

void GetDouble(const char *text, double *pValue)
{
        printf(text);
        scanf("%lf", pValue);
        fflush(stdin);
}
//-----------------------------------------------------------------------------
int main()
{
        double a, b, c;
        bool calced = false;

        //получаем данные
        GetDouble("a= ", &a);
        GetDouble("b= ", &b);

        //расчет
        __try
        {
                c = a / b;
                calced = true;
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
                printf("MATH ERROR.\n");
        }

        //вывод данных
        if (calced)
                printf("c= %G\n", c);+

        printf("Press any key to exit...");
        _getch();
        return 0;
}
//-----------------------------------------------------------------------------
 

Таким образом есть два взаимоисключающих варианта обработки мат. ошибок:
1. Использование SEH;
2. Использование сравнения на INF, NaN (для этого надо настроить FPU, чтобы он не выбрасывал исключений).
Что лучше?

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 17:13 
Legioner93
Legioner93 в сообщении #889714 писал(а):
При таком подходе разница есть. Напишу, если хотите.
Пишите, конечно.

Munin
Munin в сообщении #889720 писал(а):
Они же, как раз, проще: garbage in, garbage out. Чего именно вы меряли, и на каких процессорах?
До последнего времени операции по работе с NaN'ами, бесконечностями и денормализованными числами процессоры одной небезызвестной фирмы на букву "i" выполняли в сотни-тысячи раз медленнее, чем операции с обычными нормализованными числами (некоторое время назад ситуация постепенно начала выправляться, но окончательно вопрос еще не решен). Забавно, что у процессоров другой небезызвестной фирмы на букву "a" таких проблем не наблюдалось.
Сходу найденные ссылки:
Munin в сообщении #889720 писал(а):
Допустим, у вас формула из 8 входных значений, тогда вы должны в одном случае проделать 15 проверок, а в другом - всего одну, в самом конце.
Возможно, однако с учетом вышеприведенной особенности работы с NaN'ами может выйти и медленнее.

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 17:40 
Аватара пользователя
EtCetera
В общем, при таком подходе типа "функции - чёрного ящика" уместно (как минимум, для некоторых сценариев) делать следующее:
1) Максимально освободить функцию от разнообразных проверок, насколько это позволяет сделать язык. C позволяет нам не думать о разнообразных математических действиях с переменными double, услужливо предоставляя inf-nan-механизм. Подал пользователь на вход некорректные числа - его проблема, вернём ему inf или nan.
При этом функция
а) не упадёт на некорректных значениях, в то же время
б) будет иметь максимальную скорость работы при корректных данных. По-моему, идеальное сочетание.

1') Многие вообще забивают на пункт а) (в других случаях). Это же всё-таки C. Но тут этот пункт выполняется - и очень хорошо.

2) А что же должен проверять сам юзер: аргументы или возвращаемое значение?
+ Возвращаемое значение проверять как-то более идейно что ли. Фактически это тот же код возврата для математических функций.
+ При большом количестве аргументов проверить одно-единственное значение на NaN всё-таки будет быстрее.
- В то же время, если функция принимает так много аргументов, то наверное и работает она не быстро. После выявления же некорректных аргументов мы её не будем запускать вообще.
- Проверяя аргументы, мы более прицельно можем идентифицировать ошибку входных данных (один-единственный NaN или INF не очень многословен)

 
 
 
 Re: Деление на 0
Сообщение23.07.2014, 19:02 
Аватара пользователя
EtCetera в сообщении #889745 писал(а):
До последнего времени операции по работе с NaN'ами, бесконечностями и денормализованными числами процессоры одной небезызвестной фирмы на букву "i" выполняли в сотни-тысячи раз медленнее, чем операции с обычными нормализованными числами

Простите, а какой смысл имеет валить в одну кучу первые две и третью разновидность аргументов?

 
 
 
 Re: Деление на 0
Сообщение24.07.2014, 00:00 
Legioner93
Legioner93 в сообщении #889746 писал(а):
При большом количестве аргументов проверить одно-единственное значение на NaN всё-таки будет быстрее.
Все зависит от того, насколько часто возвращаемое значение оказывается NaN'ом (по крайней мере, на Intel'овских процессорах). Если достаточно часто (в процентном отношении), то может оказаться и наоборот.

Munin
Munin в сообщении #889751 писал(а):
EtCetera в сообщении #889745 писал(а):
До последнего времени операции по работе с NaN'ами, бесконечностями и денормализованными числами процессоры одной небезызвестной фирмы на букву "i" выполняли в сотни-тысячи раз медленнее, чем операции с обычными нормализованными числами
Простите, а какой смысл имеет валить в одну кучу первые две и третью разновидность аргументов?
В какую кучу, простите?

 
 
 [ Сообщений: 16 ]  На страницу 1, 2  След.


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group