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

发布于 2024-11-07 09:23:28 字数 1967 浏览 0 评论 0原文

当我给下面的函数一个非常接近 1.0 的数字时,我遇到了一个有趣的段错误。特别是当数字以浮点精度舍入为 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,并且 log 为零是导致段错误的未定义计算。然而我本以为第一行的错误检查会阻止这种情况发生。 Ddebugging 显示非常接近 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;

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

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

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

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

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

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

I am having an interesting seg fault in the following function when I give it a number very close to 1.0. Specifically when the number would be rounded to 1.0 at FLOATING POINT precision.

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));
}

If random_number is 1.0 then log(1.0) = 0.0 and the log of zero is an undefined calculation leading to a seg fault. However I would have thought that the error checking on the first line would have prevented this from ever happening. Ddebugging shows that a number very close to 1 will pass through the error checking but return 0 from the log function anyway leading me to believe that the log function is using only single floating point precision.

my includes are as follows so i can only assume I'm using the log from 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>

UPDATE: As pointed out an easy solution is to just use a floating point number as the argument and if a number equal to 1.0f is passed in to just remove std::numeric_limits::epsilon() to give a number which can be safely passed into the double log.

But the question I'd like answered is why does calling double log of a number near but not equal to 1 fail.

UPDATE 2: After recreating this problem in a test project I think the problem is actually in the inputs. If I pass in

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

I have no problems with the function. However what I actually pass in is

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

where random_generator is designed to return a number on the range [0, 2^32 - 1] == [0,4294967295]. So I decided to punch in the largest possible return value

double val = (4294967295+1)/4294967297.0;

which quickly gave me a warning about unsigned int overflow and sure enough generated a zero. I am recompiling with the following:

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

and hopefully this strange behaviour will be resolved.

UPDATE 3: I have finally found what is going on here... and as usual it comes down to user error (myself being the error). There was a second control path leading to this method which temporarily stored the double value as a float and then converted it back to double leading to 0.999999999 being rounded to 1.0 and then passed into the -log(-log(x)) function and causing it to fall over. What I still don't understand is why my checking

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

didn't catch the erroneous input before it was passed into the log functions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

强辩 2024-11-14 09:23:28

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

也许你可以使用类似的东西?

I think quamrana has a good point (it immediately drew my attention too). However, I've been able to run this snippet for considerable length:

#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

Perhaps you could use something similar in vein?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文