Функция журнала c ++ использует точность с плавающей запятой

У меня возникает интересная ошибка сегмента в следующей функции, когда я даю ей число, очень близкое к 1.0. В частности, когда число будет округлено до 1,0 с точностью FLOATING POINT.

double get_random_element(double random_number)
{
    if (random_number <= 0.0 || random_number >= 1.0)
        throw std::runtime_error("Can't have a random number not on the range (0.0, 1.0)");
    return -log(-log(random_number));
}

Если random_number равен 1.0, тогда log (1.0) = 0.0, а логарифм нуля - это неопределенное вычисление, приводящее к ошибке сегмента. Однако я мог подумать, что проверка ошибок в первой строке предотвратила бы это. Ddebugging показывает, что число, очень близкое к 1, пройдет проверку ошибок, но все равно вернет 0 из функции журнала, что заставляет меня поверить, что функция журнала использует только одну точность с плавающей запятой.

мои включения заключаются в следующем, поэтому я могу только предположить, что использую журнал из math.h

#include <string>
#include <math.h>
#include <sstream>
#include <map>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>
#include <utility>

ОБНОВЛЕНИЕ: как уже указывалось, простое решение - просто использовать число с плавающей запятой в качестве аргумента, и если передано число, равное 1.0f, просто удалить std :: numeric_limits :: epsilon (), чтобы получить число, которое может быть безопасно перешел в двойное бревно.

Но вопрос, на который я хотел бы получить ответ, заключается в том, почему вызов двойного журнала числа, близкого, но не равного 1, терпит неудачу.

ОБНОВЛЕНИЕ 2: после воссоздания этой проблемы в тестовом проекте я думаю, что проблема на самом деле во входных данных. Если я пройду

double val = 1.0 - std::numerical_limits<double>::epsilon();

У меня нет проблем с функцией. Однако на самом деле я передаю

boost::mt19937 random_generator;
double val = (random_generator()+1)/4294967297.0;

где random_generator предназначен для возврата числа в диапазоне [0, 2 ^ 32 - 1] == [0,4294967295]. Поэтому я решил ввести максимально возможное возвращаемое значение

double val = (4294967295+1)/4294967297.0;

который быстро дал мне предупреждение о переполнении unsigned int и, конечно же, сгенерировал ноль. Я перекомпилирую следующее:

get_random_element((random_generator()+1.0)/4294967297.0);

и, надеюсь, это странное поведение разрешится.

ОБНОВЛЕНИЕ 3: я наконец нашел, что здесь происходит ... и, как обычно, все сводится к ошибке пользователя (я являюсь ошибкой). Был второй путь управления, ведущий к этому методу, который временно хранил значение типа double как число с плавающей запятой, а затем преобразовывал его обратно в значение double, в результате чего 0,999999999 округлялось до 1,0, а затем передавалось в функцию -log (-log (x)) и приводило к это упасть. Я до сих пор не понимаю, почему я проверяю

 if (random_number <= 0.0 || random_number >= 1.0) throw runtime_error(blah)

не поймали ошибочный ввод до того, как он был передан в функции журнала?


person Jamie Cook    schedule 15.05.2011    source источник
comment
Не нужно предполагать; используйте ::log и предварительно обработайте исходный код для проверки   -  person sehe    schedule 16.05.2011
comment
Вы уверены, что это ошибка сегмента?   -  person David Heffernan    schedule 16.05.2011
comment
@sehe, запускающий препроцессор, показывает импортированные функции журнала math.h @david не уверен, что это ошибка сегмента или просто неподдерживаемая операция ... но в любом случае это убивает хост-приложение :)   -  person Jamie Cook    schedule 16.05.2011
comment
@Jamie: Конечно, проблема в сравнении double и 1.0f?   -  person quamrana    schedule 16.05.2011
comment
Какой компилятор вы используете? Самое главное, какие параметры вы передали для операций с плавающей запятой (/ fp: ... с Visual Studio)? Вы пробовали 1.0 вместо 1.0f (ничего не должно менять)? Вы пробовали r + std::numeric_limits<double>::epsilon() > 1 вместо r >= 1?   -  person Alexandre C.    schedule 16.05.2011
comment
@quamrana: IIRC 1.0f будет преобразован в double first.   -  person Alexandre C.    schedule 16.05.2011
comment
Также ваша функция очень чувствительна к тому, что происходит вокруг 1. Если вы удалите несколько эпсилонов, когда вы приблизитесь к 1, результат резко изменится!   -  person Alexandre C.    schedule 16.05.2011
comment
@quamrana - это приятный улов, но это опечатка в вырезании / вставке в stackoverflow. @Alexandre функция должна быть очень чувствительной :) Это часть генерации случайного значения из распределения экстремальных значений   -  person Jamie Cook    schedule 16.05.2011
comment
@ Джейми: ага, это меня сильно огорчило. Между прочим, заголовок <random> в C ++ 0x предоставляет вам экстремальные отклонения значений (и правильную семантику с плавающей запятой).   -  person Alexandre C.    schedule 16.05.2011
comment
Семантика @alexandre с плавающей запятой: Точная (/ fp: точная), RE ‹random› дает EV отклоняется: или rly? Я должен это найти!   -  person Jamie Cook    schedule 16.05.2011


Ответы (1)


Думаю, у quamrana есть хороший момент (он тоже сразу привлек мое внимание). Однако мне удалось запустить этот фрагмент довольно долго:

#include <math.h>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real.hpp>

double get_random_element(double random_number)
{
    if (random_number <= 0 || random_number >= 1.0f)
        throw std::runtime_error("Can't have a random number not on the range (0.0, 1.0)");
    return -::log(-::log(random_number));
}

int main()
{
    boost::mt19937 rng; 
    boost::uniform_real<> random(std::numeric_limits<double>::epsilon(),1);
    for (;;)
    {
        double r = random(rng);
        double gre = get_random_element(r);
        std::cout << "r = " << r << ", gre = " << gre << std::endl;
    }
    return 0; // not reached
}

E.g.:

sehe@meerkat:/tmp$ ./t | grep '^r = 0.999999' 
r = 0.999999, gre = 14.4777
r = 0.999999, gre = 13.7012
r = 0.999999, gre = 14.0492
r = 0.999999, gre = 14.1161
[.... many many lines snipped ....]
r = 0.999999, gre = 14.3691
r = 0.999999, gre = 13.424
r = 0.999999, gre = 14.4822
r = 0.999999, gre = 14.286
r = 0.999999, gre = 14.4344
r = 0.999999, gre = 14.0572
r = 0.999999, gre = 14.0607
r = 0.999999, gre = 14.1126
r = 0.999999, gre = 13.575
r = 0.999999, gre = 13.4754
r = 0.999999, gre = 13.5486
r = 0.999999, gre = 14.1983
^C

real    18m14.005s
user    20m5.667s
sys 12m19.302s

Может быть, вы могли бы использовать что-то подобное в духе?

person sehe    schedule 15.05.2011
comment
Это заставляет меня думать, что проблема может быть в флагах компилятора. Попробуйте / fp: strict в MSVC (или эквивалент для вашего компилятора) и посмотрите, сохраняется ли проблема. - person Alexandre C.; 16.05.2011
comment
Сехе, я только что обновил вопрос ... мы используем твистер Мерсенна, как и вы, но я не совсем уверен, что uniform_real принимает в качестве аргументов ... это включающие или исключительные границы? - person Jamie Cook; 16.05.2011
comment
Простой поиск по документации: uniform_real models a random distribution. On each invocation, it returns a random floating-point value uniformly distributed in the range [min..max). Я удивлен, что вы сами напишете входное распределение, но при этом достаточно параноик, чтобы проверить этот факт :) - person sehe; 16.05.2011
comment
паранойя такая забавная: P Проблема, которую я вижу в приведенном выше коде, заключается в том, что он гарантированно завершится неудачей, если выполняется достаточно долго, потому что ваш rng на каком-то этапе должен сгенерировать 0, что приведет к журналу нуля и отказу. Но, возможно, для нас было бы разумнее использовать boost :: uniform_real ‹› random (std :: numerical_limits ‹double› :: epsilon (), 1); - person Jamie Cook; 16.05.2011
comment
Я обновляю пример кода, чтобы не запутать будущих посетителей. (Должен признать, я совершенно упустил из виду тот факт, что 0 тоже недопустимо :)) - person sehe; 16.05.2011