Я всегда думал, что случайные числа лежат между нулем и единицей, без него1
, т.е. это числа из полуоткрытого интервала [0,1). Справки о на cppreference.com из std::generate_canonical
подтверждает это.
Однако когда я запускаю следующую программу:
#include <iostream>
#include <limits>
#include <random>
int main()
{
std::mt19937 rng;
std::seed_seq sequence{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
rng.seed(sequence);
rng.discard(12 * 629143 + 6);
float random = std::generate_canonical<float,
std::numeric_limits<float>::digits>(rng);
if (random == 1.0f)
{
std::cout << "Bug!\n";
}
return 0;
}
Это дает мне следующий результат:
Bug!
то есть он генерирует у меня идеал 1
, что вызывает проблемы в моей интеграции с MC. Это допустимое поведение или есть ошибка с моей стороны? Это дает тот же результат с G ++ 4.7.3
g++ -std=c++11 test.c && ./a.out
и лязг 3.3
clang++ -stdlib=libc++ -std=c++11 test.c && ./a.out
Если это правильное поведение, как я могу избежать 1
?
Изменить 1 : G ++ от git, похоже, страдает той же проблемой. Я на
commit baf369d7a57fb4d0d5897b02549c3517bb8800fd
Date: Mon Sep 1 08:26:51 2014 +0000
и компиляция с ~/temp/prefix/bin/c++ -std=c++11 -Wl,-rpath,/home/cschwan/temp/prefix/lib64 test.c && ./a.out
дает тот же результат, ldd
дает
linux-vdso.so.1 (0x00007fff39d0d000)
libstdc++.so.6 => /home/cschwan/temp/prefix/lib64/libstdc++.so.6 (0x00007f123d785000)
libm.so.6 => /lib64/libm.so.6 (0x000000317ea00000)
libgcc_s.so.1 => /home/cschwan/temp/prefix/lib64/libgcc_s.so.1 (0x00007f123d54e000)
libc.so.6 => /lib64/libc.so.6 (0x000000317e600000)
/lib64/ld-linux-x86-64.so.2 (0x000000317e200000)
Изменить 2 : я сообщил о поведении здесь: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176
Изменить 3 : команда clang, похоже, знает о проблеме: http://llvm.org/bugs/show_bug.cgi?id=18767
1.f == 1.f
во всех случаях (какие там все случаи? Я даже не видел никаких переменных1.f == 1.f
; здесь только один случай:,1.f == 1.f
и это всегдаtrue
). Пожалуйста, не распространяйте этот миф дальше. Сравнение с плавающей запятой всегда точное.abs(random - 1.f) < numeric_limits<float>::epsilon
проверяет, близок ли результат к 1.0 , что совершенно неверно в данном контексте: здесь есть числа, близкие к 1.0, которые являются действительными результатами, а именно все те, которые меньше 1.0.Ответы:
Проблема заключается в отображении из codomain of
std::mt19937
(std::uint_fast32_t
) вfloat
; алгоритм, описанный в стандарте, дает неправильные результаты (несовместимые с его описанием вывода алгоритма), когда происходит потеря точности, если текущий режим округления IEEE754 отличается от округления до отрицательной бесконечности (обратите внимание, что по умолчанию используется округление -в-ближайший).7549723-й вывод mt19937 с вашим семенем - 4294967257 (
0xffffffd9u
), который при округлении до 32-битного числа с плавающей запятой дает0x1p+32
, что равно максимальному значению mt19937, 4294967295 (0xffffffffu
), когда оно также округляется до 32-битного числа с плавающей запятой.Стандарт мог бы гарантировать правильное поведение, если бы он указывал, что при преобразовании вывода URNG в
RealType
ofgenerate_canonical
должно выполняться округление в сторону отрицательной бесконечности; в этом случае это даст правильный результат. Как QOI, было бы хорошо, если бы libstdc ++ внесла это изменение.После этого изменения
1.0
больше не будет создаваться; вместо этого граничные значения0x1.fffffep-N
для0 < N <= 8
будут генерироваться чаще (примерно2^(8 - N - 32)
наN
, в зависимости от фактического распределения MT19937).Я бы рекомендовал не использовать
float
withstd::generate_canonical
напрямую; скорее сгенерируйте число,double
а затем округлите до отрицательной бесконечности:Эта проблема также может возникнуть с
std::uniform_real_distribution<float>
; решение то же самое: специализировать распределениеdouble
и округлить результат до отрицательной бесконечностиfloat
.источник
sin(x)
, то на самом деле он хочет синуса (π / Math.PI), умноженного на x. Люди, поддерживающие Java, настаивают на том, что лучше иметь медленную рутинную математическую процедуру, чтобы сообщать, что синус Math.PI является разницей между π и Math.PI, чем сообщать значение, которое немного меньше, несмотря на то, что в 99% приложений он лучше бы ...std::uniform_real_distribution<float>
вследствие этого возникла та же проблема. (Чтобы люди, которые ищут uniform_real_distribution, увидели этот вопрос / ответ).generate_canonical
должно генерировать число в диапазоне[0,1)
, а мы говорим об ошибке, когда оно иногда генерирует 1.0, не будет ли округление до нуля столь же эффективным?По стандарту
1.0
не действует.источник
Я только что столкнулся с аналогичным вопросом
uniform_real_distribution
, и вот как я интерпретирую скупую формулировку Стандарта по этому поводу:Стандарт всегда определяет математические функции в терминах математики , а не в терминах чисел с плавающей запятой IEEE (потому что Стандарт по-прежнему делает вид, что плавающая точка не может означать плавающую точку IEEE). Итак, всякий раз, когда вы видите математические формулировки в Стандарте, речь идет о реальной математике , а не о IEEE.
Стандарт говорит, что оба
uniform_real_distribution<T>(0,1)(g)
иgenerate_canonical<T,1000>(g)
должны возвращать значения в полуоткрытом диапазоне [0,1). Но это математические значения. Когда вы берете действительное число в полуоткрытом диапазоне [0,1) и представляете его как число с плавающей запятой IEEE, ну, в значительной части времени оно будет округлено доT(1.0)
.Когда
T
равноfloat
(24 бита мантиссы), мы ожидаем увидетьuniform_real_distribution<float>(0,1)(g) == 1.0f
примерно 1 из 2 ^ 25 раз. Мои эксперименты с полным перебором с libc ++ подтверждают это ожидание.Пример вывода:
Когда
T
равноdouble
(53 бита мантиссы), мы ожидаем увидетьuniform_real_distribution<double>(0,1)(g) == 1.0
примерно 1 из 2 ^ 54 раз. У меня нет терпения проверить это ожидание. :)Насколько я понимаю, такое поведение нормально. Это может оскорбить наше чувство «полуоткрытого-rangeness» , что распределение требуя вернуть номера «меньше , чем 1,0» может в цифрах возвратных фактов, которые равны к
1.0
; но это два разных значения «1.0», понимаете? Первый - математическая 1.0; второй - это число с плавающей запятой одинарной точности IEEE1.0
. И нас десятилетиями учили не сравнивать числа с плавающей запятой на предмет точного равенства.В какой бы алгоритм вы ни вводили случайные числа, его не волнует, если оно иногда получается точно
1.0
. С числами с плавающей запятой вы ничего не можете сделать, кроме математических операций, и как только вы выполните некоторую математическую операцию, вашему коду придется иметь дело с округлением. Даже если бы вы могли обоснованно предположить этоgenerate_canonical<float,1000>(g) != 1.0f
, вы все равно не смогли бы этого предположитьgenerate_canonical<float,1000>(g) + 1.0f != 2.0f
- из-за округления. Вы просто не можете уйти от этого; Так зачем нам в этом единственном случае делать вид, что вы можете?источник
1.0f
но это просто неизбежно, когда вы приводите их к числам с плавающей запятой IEEE. Если вам нужны чисто математические результаты, используйте систему символьных вычислений; если вы пытаетесь использовать числа с плавающей запятой IEEE для представления чисел, находящихся в пределахeps
1, вы находитесь в состоянии греха.canonical - 1.0f
. Для каждого представимого поплавка[0, 1.0)
,x-1.0f
не равно нуль. Имея ровно 1.0f, вы можете получить деление на ноль вместо очень маленького делителя.