关于round_up宏的问题

时间:2009-06-18 05:35:49

标签: c macros

#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))

有了上面的宏,有人可以帮我理解“(s)-1”部分,为什么会这样?

以及像:

这样的宏
#define PAGE_ROUND_DOWN(x) (((ULONG_PTR)(x)) & (~(PAGE_SIZE-1)))
#define PAGE_ROUND_UP(x) ( (((ULONG_PTR)(x)) + PAGE_SIZE-1)  & (~(PAGE_SIZE-1)) ) 

我知道“(〜(PAGE_SIZE-1)))”部分会将最后五位归零,但除此之外我一无所知,特别是角色'&'操作员。

谢谢,

5 个答案:

答案 0 :(得分:16)

ROUND_UP宏依赖于整数除法来完成工作。它只有在两个参数都是整数时才有效。我假设N是要舍入的数字,S是应该舍入的区间。也就是说,ROUND_UP(12, 5)应该返回15,因为15是第一个大于12的区间。

想象一下,我们正在向下而不是向上。在这种情况下,宏将只是:

#define ROUND_DOWN(N,S) ((N / S) * S)

ROUND_DOWN(12,5)将返回10,因为整数除法中的(12/5)为2,而2 * 5为10.但我们没有做ROUND_DOWN,我们正在做ROUND_UP。因此,在我们进行整数除法之前,我们希望尽可能多地添加而不会失去准确性。如果我们添加了S,它几​​乎可以在所有情况下使用; ROUND_UP(11,5)将成为(((11 + 5)/ 5)* 5),并且由于整数除法中的16/5是3,我们得到15。

当我们传递一个已经四舍五入到指定倍数的数字时,就会出现问题。 ROUND_UP(10, 5)将返回15,这是错误的。因此,我们不添加S,而是添加S-1。这保证了我们永远不会不必要地将某些东西推到下一个“桶”。

PAGE_宏与二进制数学有关。为简单起见,我们假装我们正在处理8位值。我们假设PAGE_SIZE0b00100000。因此PAGE_SIZE-1 0b00011111。然后~(PAGE_SIZE-1)0b11100000

二进制&将排列两个二进制数,并在两个数字都为1的任何地方留一个。因此,如果x为0b01100111,则操作将如下所示:

  0b01100111  (x)
& 0b11100000  (~(PAGE_SIZE-1))
------------
  0b01100000

你会注意到该操作实际上只将最后5位清零。就这样。但正是这种操作需要向下舍入到最近的PAGE_SIZE区间。请注意,这只能起作用,因为PAGE_SIZE恰好是2的幂。这有点像说任何十进制数字,只需将最后两位数字归零就可以向下舍入到最近的100。它工作得很好,而且很容易做到,但是如果你试图将其舍入到最接近的76倍,则根本不起作用。

PAGE_ROUND_UP执行相同的操作,但它会在切断之前尽可能多地添加到页面中。这有点像我可以通过向任何数字添加99并且然后将最后两位数字归零来舍入到最接近的100的倍数。 (我们添加PAGE_SIZE-1的原因与上面添加S-1的原因相同。)

祝你虚拟内存好运!

答案 1 :(得分:4)

使用整数运算,除以总是向下舍入。要解决这个问题,如果原始数字可以被整除,则添加不会影响结果的最大可能数字。对于数字S,最大可能的数字是S-1。

舍入为2的幂是特殊的,因为您可以通过位操作来完成。 2的倍数将在底部位中具有零,4的倍数将在底部的两位中始终为零,等等.2的幂的二进制表示是单个位,后跟一串零;减1将清除该位,并将所有位设置为右。反转该值会在需要清除的位置创建一个带零的掩码。 &运算符将清除值中的这些位,从而将值四舍五入。将(PAGE_SIZE-1)添加到原始值的相同技巧会导致它向上舍入而不是向下。

答案 2 :(得分:1)

页面舍入宏假设`PAGE_SIZE是2的幂,例如:

0x0400    -- 1 KiB
0x0800    -- 2 KiB`
0x1000    -- 4 KiB

因此,PAGE_SIZE - 1的值都是一位:

0x03FF
0x07FF
0x0FFF

因此,如果整数是16位(而不是32位或64位 - 它可以节省一些输入),那么~(PAGE_SIZE-1)的值是:

0xFC00
0xFE00
0xF000

当你取x的值时(假设现实生活中令人难以置信,但对于阐述而言,ULONG_PTR是无符号的16位整数)0xBFAB ,那么

PAGE_SIZE         PAGE_ROUND_DN(0xBFAB)   PAGE_ROUND_UP(0xBFAB)

0x0400     -->    0xBC00                  0xC000
0x0800     -->    0xB800                  0xC000
0x1000     -->    0xB000                  0xC000

宏向下舍入到最接近页面大小的倍数。如果PAGE_SIZE == 0x20(或32),则最后五位将被清零。

答案 3 :(得分:1)

根据当前的标准草案(C99),这个宏并不完全正确,但请注意,对于N的负值,结果几乎肯定是不正确的。

公式:

#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))

利用整数除法向下舍入非负整数的事实,并使用S - 1部分强制它向上舍入。

然而,整数除法向零舍入(C99,第6.5.5节。乘法运算符,第6项)。对于否定的N,“向上”的正确方法是:“N / S”,仅此而已。

如果S也被允许为负值,它会更加复杂,但我们甚至不去那里......(请参阅:How can I ensure that a division of integers is always rounded up?更详细地讨论各种错误和一个错误或两个正确的解决方案)

答案 4 :(得分:0)

&这样做..好吧,让我们拿一些二进制数字。

(with 1000 being page size)
PAGE_ROUND_UP(01101b)=
01101b+1000b-1b & ~(1000b-1b) =
01101b+111b & ~(111b) =
01101b+111b & ...11000b = (the ... means 1's continuing for size of ULONG)
10100b & 11000b=
10000b

所以,正如你所看到的那样(希望如此)这可以通过将PAGE_SIZE添加到x然后进行AND运算来取消,这样它就会取消未设置的PAGE_SIZE的底部位

相关问题