【问题标题】:Question about round_up macro关于round_up宏的问题
【发布时间】:2023-04-09 08:43:01
【问题描述】:
#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)))”部分会将最后五位清零,但除此之外我一无所知,尤其是“&”运算符所扮演的角色。

谢谢,

【问题讨论】:

    标签: c macros


    【解决方案1】:

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

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

    #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_SIZE0b00100000PAGE_SIZE-1 因此是 0b00011111~(PAGE_SIZE-1)0b11100000

    二进制& 将排列两个二进制数,并在两个数都为 1 的地方留下 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 的原因相同。)

    祝你的虚拟内存好运!

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

        页面舍入宏假设 `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)时,最后五位才会清零。

        【讨论】:

          【解决方案4】:

          根据当前的草案标准 (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? 以更详细地讨论各种错误和一两个正确的解决方案)

          【讨论】:

            【解决方案5】:

            & 使它如此......好吧,让我们取一些二进制数。

            (页面大小为 1000) PAGE_ROUND_UP(01101b)= 01101b+1000b-1b & ~(1000b-1b) = 01101b+111b & ~(111b) = 01101b+111b & ...11000b = (... 表示 1 对于 ULONG 的大小继续) 10100b & 11000b= 10000b

            所以,正如你所看到的(希望如此)这通过将 PAGE_SIZE 添加到 x 然后进行 ANDing 来进行四舍五入,因此它会抵消 PAGE_SIZE 的未设置的底部位

            【讨论】:

              【解决方案6】:

              这是我用的:

              #define SIGN(x)            ((x)<0?-1:1)
              #define ROUND(num, place)  ((int)(((float)(num) / (float)(place)) + (SIGN(num)*0.5)) * (place))
              
              float A=456.456789
              B=ROUND(A, 50.0f) // 450.0
              C=ROUND(A, 0.001) // 456.457
              

              【讨论】:

              • 这似乎与所提出的问题无关。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-11-20
              • 1970-01-01
              • 1970-01-01
              • 2020-12-15
              • 1970-01-01
              • 2021-09-06
              • 1970-01-01
              相关资源
              最近更新 更多