C++ log 函数使用浮点精度

c++ log function is using floating point precision

本文关键字:精度 log 函数 C++      更新时间:2023-10-16

当我给它一个非常接近 1.0 的数字时,我在以下函数中遇到了一个有趣的 seg 错误。特别是当数字在浮点精度下四舍五入为 1.0 时。

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,零对数是一个未定义的计算,导致 seg 错误。但是,我本以为第一行的错误检查会阻止这种情况发生。Ddebuging显示,非常接近1的数字将通过错误检查,但无论如何都会从log函数返回0,这让我相信log函数只使用单个浮点精度。

我的包含如下,所以我只能假设我正在使用 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;

这很快给了我一个关于无符号 int 溢出的警告,果然生成了一个零。我正在重新编译以下内容:

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

希望这种奇怪的行为能够得到解决。

更新3:我终于找到了这里发生的事情...像往常一样,它归结为用户错误(我自己是错误)。还有第二个控制路径通向此方法,该路径暂时将双精度值存储为浮点数,然后将其转换回双精度值,导致 0.9999999999 四舍五入为 1.0,然后传递到 -log(-log(x)) 函数并导致它倒下。我仍然不明白的是为什么我的检查

 if (random_number <= 0.0 || random_number >= 1.0) throw runtime_error(blah)
在将错误输入

传递到日志函数之前没有捕获错误输入?

我认为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
}

例如:

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

也许你可以在静脉中使用类似的东西?