2

我试图在 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 之间的数字?

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

4

11 回答 11

22

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

于 2009-11-20T19:04:56.047 回答
14

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

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

考虑您的源数据。0.7 真的比 0.69999999 正确得多吗?

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

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

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

于 2009-11-20T19:06:25.163 回答
8

您需要多少精度?您可能会考虑缩放值并以定点表示对其进行量化。

于 2009-11-20T19:03:02.567 回答
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 的数量级上,第二种方法会给你更高的精度。

于 2009-11-20T19:11:54.793 回答
2

你想用你的号码做的测试是不正确的。

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

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

一个解决方案是使用有理数(boost 有一个有理库),这对于 ermm、有理数总是准确的,或者通过将数字乘以 10 的幂来使用自制的 base-10 系统。

于 2009-11-20T19:13:32.763 回答
1

如果您只需要几位精度,则只需使用整数。如果您需要更好的精度,那么您将不得不寻找提供精度保证的不同库。

于 2009-11-20T19:08:57.863 回答
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;
于 2009-11-20T19:11:16.700 回答
1

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

让我们退后一步。.1 是什么意思?还是0.7?它们的意思是 1x10 -1和 7x10 -1。如果您使用二进制作为数字,而不是我们通常使用的以 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 的表示是 0.333,那么它的三个副本加起来就是 0.999,而不是 1。

有四种可能的解决方案。如果您关心的只是精确地表示像 .1 和 .7 这样的小数(例如,您不在乎 1/3 会遇到您提到的相同问题),那么您可以将您的数字表示为十进制,例如使用二进制编码的十进制,并操作它们。这是金融中的一种常见解决方案,其中许多操作都是用十进制定义的。这样做的缺点是您需要自己实现所有算术运算,没有计算机 FPU 的好处,或者找到一个十进制算术库。如前所述,这也对无法以十进制精确表示的分数没有帮助。

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

第三种解决方案,如果你关心的只是让你的数字加起来正好等于 1,那么对于你有n 个可能性的事件,只存储这些概率的n -1 的值,并将最后一个的概率计算为1减去其余概率的总和。

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

于 2009-11-20T19:52:52.237 回答
0

Depending on the requirements of your applications any one of several solutions could be best:

  1. You live with the inherent lack of precision and use floats or doubles. You cannot test either for equality and this implies that you cannot test the sum of your probabilities for equality with 1.0.

  2. As proposed before, you can use integers if you require a fixed precision. You represent 0.7 as 7, 0.1 as 1, 0.2 as 2 and they will add up perfectly to 10, i.e., 1.0. If you have to calculate with your probabilities, especially if you do division and multiplication, you need to round the results correctly. This will introduce an imprecision again.

  3. Represent your numbers as fractions with a pair of integers (1,2) = 1/2 = 0.5. Precise, more flexible than 2) but you don't want to calculate with those.

  4. You can go all the way and use a library that implements rational numbers (e.g. gmp). Precise, with arbitrary precision, you can calculate with it, but slow.

于 2009-11-20T21:01:17.060 回答
0

很抱歉,您的问题并没有一个简单的答案。

它属于一个名为“数值分析”的研究领域,可以处理这些类型的问题(这远远超出了确保您不检查两个浮点值之间的相等性)。通过研究领域,我的意思是有大量的书籍、期刊文章、课程等处理它。有些人在上面做博士论文。

我只能说,我很庆幸我不必过多地处理这些问题,因为问题和解决方案通常非常不直观。

您可能需要做什么来表示您正在处理的数字和计算,这在很大程度上取决于您正在执行的操作、这些操作的顺序以及您希望在这些操作中处理的值的范围.

于 2009-11-20T19:21:14.760 回答
-5

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

我确实觉得“0.7”在存储上像这样模糊起来有点有趣。

于 2009-11-20T19:15:12.843 回答