用C ++表示概率

时间:2009-11-20 18:59:12

标签: c++ probability rounding-error

我试图在C ++中表示一组简单的3个概率。例如:

a = 0.1  
b = 0.2  
c = 0.7

(据我所知,概率必须加起来为1)

我的问题是,当我尝试将C ++中的0.7表示为浮点数时,我最终会得到0.69999999,这在我稍后进行计算时无济于事。同样适用于0.8,0.80000001。

有没有更好的方法在C ++中表示0.0到1.0之间的数字?

请记住,这与数字如何存储在内存中有关,因此在对值进行测试时,我不关心它们的显示/打印方式。

11 个答案:

答案 0 :(得分:22)

这与C ++无关,而与浮点数如何在内存中表示有关。您永远不应该使用等于运算符来比较浮点值,请参阅此处以获得更好的方法:http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

答案 1 :(得分:14)

  

我的问题是,当我尝试   在C ++中表示0.7作为浮点数结束   高达0.69999999,这将无济于事   我以后做计算的时候。   同样适用于0.8,0.80000001。

真的有问题吗?如果您只需要更高的精度,请使用double而不是float。这应该可以让你获得大约15位数的精度,对于大多数工作来说已经足够了。

考虑您的源数据。 0.7是否真的比0.69999999更正确?

如果是这样,您可以使用有理数字库,例如:

http://www.boost.org/doc/libs/1_40_0/libs/rational/index.html

如果问题是概率根据定义加起来为1,则将它们存储为数字集合,省略最后一个。通过从1中减去其他值的总和来推断最后一个值。

答案 2 :(得分:8)

您需要多少精度?您可以考虑缩放值并以定点表示量化它们。

答案 3 :(得分:2)

如果你真的需要精确度,并且坚持使用有理数,我想你可以使用固定点算术。我之前没有这样做,所以我不推荐任何库。

或者,你可以在比较fp数字时设置一个阈值,但是你必须在一边或另一边犯错 - 比如说

bool fp_cmp(float a, float b) {
    return (a < b + epsilon);
}

请注意,在每次计算中会自动截断多余的精度,因此在算法中以多个不同的数量级运行时应注意。一个人为的例子来说明:

a = 15434355e10 + 22543634e10
b = a / 1e20 + 1.1534634
c = b * 1e20

c = b + 1.1534634e20

这两个结果将大不相同。使用第一种方法,前两个数字的许多精度将在除以1e20时丢失。假设您想要的最终值大约为1e20,则第二种方法将为您提供更高的精度。

答案 4 :(得分:2)

您想要对您的号码进行的测试不正确。

对于像0.1这样的数字,base-2数字系统中没有确切的浮点表示,因为它是一个infinte周期数。考虑三分之一,在base-3系统中可以完全表示为0.1,但在base-10系统中可以表示为0.333 ....

因此,使用浮点数0.1进行的任何测试都容易存在缺陷。

一个解决方案是使用有理数(boost有一个理性的lib),它总是精确地用于,ermm,有理数,或者通过将数字乘以10的幂来使用自制的base-10系统。

答案 5 :(得分:1)

这里的问题是浮点数存储在基数2中。您不能用基数为2的浮点数精确表示基数10中的小数。

让我们退后一步。 .1是什么意思?或.7?它们意味着1x10 -1 和7x10 -1 。如果您使用binary作为您的号码,而不是像往常一样使用基数10,.1表示1x2 -1 或1/2。 .11表示1x2 -1 + 1x2 -2 ,或1/2 + 1/4或3/4。

请注意,在此系统中,分母始终为2的幂。如果没有分数,则无法表示有限位数为2的幂。例如,.1(十进制)表示1/10,但二进制表示无限重复分数,0.000110011 ...(0011模式永远重复)。这类似于基数10,1/3是无限分数,0.3333 ......;基数10只能用分母代表数字,分母是2和5的幂的倍数。(另外,基数12和基数60实际上是非常方便的基数,因为12可以被2,3和4整除,并且60可以被2,3,4和5整除;但由于某种原因我们无论如何都使用十进制,我们在计算机中使用二进制。)

由于浮点数(或定点数)总是具有有限的数字位数,因此它们不能精确地表示这些无限重复分数。因此,它们要么截断或舍入值,要尽可能接近实际值,但不能完全等于实际值。一旦开始添加这些舍入值,就会开始出现更多错误。在十进制中,如果你的1/3的表示是.333,那么它的三个副本将加起来为.999,而不是1。

有四种可能的解决方案。如果你关心的只是完全代表.1和.7之类的小数部分(如你所知,你不在乎1/3将会出现同样的问题),那么你可以将你的数字表示为十进制数,例如使用binary coded decimal,并操纵那些。这是财务中的常见解决方案,其中许多操作以十进制的形式定义。这有一个缺点,你需要自己实现所有自己的算术运算,没有计算机的FPU的好处,或找到decimal arithmetic library。如上所述,这也无助于无法用十进制精确表示的分数。

另一种解决方案是使用分数来表示您的数字。如果您使用分数,使用bignums(任意大数字)作为分子和分母,则可以表示适合计算机内存的任何有理数。同样,缺点是算术会变慢,你需要自己实现算术或use an existing library。这将解决所有有理数的问题,但是如果最终计算出基于π或√2计算的概率,你仍然会遇到无法完全表示它们的相同问题,并且还需要使用一个后来的解决方案。

第三个解决方案,如果您只关心的是将您的数字精确地加到1,那么对于您有 n 可能性的事件,仅存储 n <的值/ em> -1这些概率,并计算最后一个的概率为1减去其余概率的总和。

第四个解决方案是在处理浮点数(或任何不精确的数字,例如用于表示无理数的分数)时始终需要记住的事情,并且绝不要比较两个数字的相等性。再次在基数10中,如果你加起来3个1/3的副本,你最终会得到.999。如果要将该数字与1进行比较,则必须进行比较以查看它是否足够接近1;检查差值的绝对值1-.999是否小于阈值,例如.01。

答案 6 :(得分:1)

如果您只需要几位数的精度,那么只需使用整数即可。如果您需要更高的精度,那么您将不得不寻找能够提供精确度保证的不同库。

答案 7 :(得分:1)

二进制计算机总是将十进制小数(除了.0和.5,.25,。75等)舍入到浮点不具有精确表示的值。这与C ++语言无关。除了在代码中从数字角度处理它之外,没有真正的方法。

至于实际产生你寻求的概率:

float pr[3] = {0.1, 0.2, 0.7};
float accPr[3];
float prev = 0.0;
int i = 0;

for (i = 0; i < 3; i++) {
    accPr[i] = prev + pr[i];
    prev = accPr[i];
}

float frand = rand() / (1 + RAND_MAX);
for (i = 0; i < 2; i++) {
    if (frand < accPr[i]) break;
}
return i;

答案 8 :(得分:0)

我很遗憾地说你的问题并不是一个简单的答案。

它属于一个名为"Numerical Analysis"的研究领域,它处理这些类型的问题(这远远超出了确保不检查2个浮点值之间的相等性)。在研究领域,我的意思是有大量的书籍,期刊文章,课程等处理它。有人在那里做博士论文。

我只能说,我很感谢我不必非常处理这些问题,因为问题和解决方案往往非常不直观。

您可能需要做的是如何处理表示您正在处理的数字和计算,这非常依赖于您正在执行的操作,这些操作的顺序以及您希望处理的值的范围在那些行动中。

答案 9 :(得分:0)

根据您的应用程序的要求,几种解决方案中的任何一种都是最佳解决方案:

  1. 你生活在内在缺乏精确性,并使用浮动或双打。您无法测试是否相等,这意味着您无法测试与1.0相等的概率总和。

  2. 如前所述,如果需要固定精度,可以使用整数。你将0.7表示为7,0.1表示为1,2表示为0.2,它们将完美地加起来为10,即1.0。如果你必须用你的概率计算,特别是如果你进行除法和乘法,你需要正确地舍入结果。这将再次引入不精确。

  3. 将数字表示为具有一对整数(1,2)= 1/2 = 0.5的分数。比2更精确,更灵活,但你不想用这些计算。

  4. 您可以一直使用实现有理数的库(例如gmp)。精确,任意精度,你可以用它计算,但速度很慢。

答案 10 :(得分:-5)

是的,如果您担心这些事情,我会根据您需要的数字(0-100)(0-1000)或任何固定大小进行缩放。在大多数情况下,它还可以实现更快的数学计算。回到过去的糟糕时期,我们将以整数形式定义整个cos / sine表和其他类似的bleh,以减少浮动模糊并提高计算速度。

我确实觉得有点有趣的是存储上的“0.7”模糊。