c,获得一个特殊的随机数

时间:2018-01-25 16:29:38

标签: c random

我有一个我需要加速的算法问题:)

我需要一个32位随机数,精确的10位设置为1.但同时,101(5 dec)和11(3 dec)等模式被认为是非法的。

现在MCU是8051(8位),我在Keil uVision中测试了所有这些。我的第一次尝试完成了,给出了解决方案

0x48891249
1001000100010010001001001001001   // correct, 10 bits 1, no 101 or 11

问题是它完成了97秒或1165570706 CPU周期,这太荒谬了!!!

这是我的代码

// returns 1 if number is not good. ie. contains at leats one 101 bit sequence
bool checkFive(unsigned long num)
{
    unsigned char tmp;

    do {

            tmp = (unsigned char)num;

        if(
            (tmp & 7) == 5 
        || (tmp & 3) == 3
        ) // illegal pattern 11 or 101
                return true; // found 

            num >>= 1;
    }while(num);

    return false;
}

void main(void) {


    unsigned long v,num; // count the number of bits set in v
    unsigned long c; // c accumulates the total bits set in v

    do {
            num = (unsigned long)rand() << 16 | rand(); 
            v = num;

            // count all 1 bits, Kernigen style
            for (c = 0; v; c++)
                    v &= v - 1; // clear the least significant bit set

    }while(c != 10 || checkFive(num));

  while(1);
}

一个聪明的头脑的大问题:) 可以做得更快吗?似乎我的方法很幼稚。

提前谢谢你,

哇,我给我留下了深刻的印象,感谢大家的建议。但是,在接受之前,我需要对它们进行测试。

现在有了第一个选项(查找)它只是不现实,将完成吹我整个8051微控制器的4K RAM :)正如你在图像中看到的那样,我在Code中测试了所有组合阻止,但有超过300的方式,直到5000索引...

enter image description here

我用来测试的代码

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>

//#define bool  bit
//#define   true    1
//#define false 0



// returns 1 if number is not good. ie. contains at leats one 101 bit sequence
bool checkFive(uint32_t num)
{
    uint8_t tmp;

    do {

            tmp = (unsigned char)num;

        if(
            (tmp & 7) == 5
        || (tmp & 3) == 3
        ) // illegal pattern 11 or 101
                return true; // found

            num >>= 1;
    }while(num);

    return false;
}

void main(void) {


    uint32_t v,num; // count the number of bits set in v
    uint32_t c, count=0; // c accumulates the total bits set in v

    //printf("Program started \n");

    num = 0;

    printf("Program started \n");

    for(num=0; num <= 0xFFFFFFFF; num++)
    {


        //do {



                //num = (uint32_t)rand() << 16 | rand();
                v = num;

                // count all 1 bits, Kernigen style
                for (c = 0; v; c++)
                        v &= v - 1; // clear the least significant bit set

        //}while(c != 10 || checkFive(num));

                if(c != 10 || checkFive(num))
                    continue;

                count++;

        printf("%d: %04X\n", count, num);
    }

    printf("Complete \n");
  while(1);
}

也许我可以重新制定问题:

我需要一个号码:

  • 精确(已知)1位数量,在我的示例中为10
  • 没有11或101种模式
  • 剩余的零可以是任何

所以不知怎的,只在里面乱掉1位。

或者,取一个0x00000000并在随机位置添加10个1位,但非法模式除外。

4 个答案:

答案 0 :(得分:7)

解决方案

给定一个例程r(n),它返回一个从0(包括)到n(不包括)的随机整数,并且均匀分布,问题中描述的值可以通过调用统一分布生成P(10, 4)其中P是:

static uint32_t P(int a, int b)
{
    if (a == 0 && b == 0)
        return 0;
    else
        return r(a+b) < a ? P(a-1, b) << 3 | 1 : P(a, b-1) << 1;
}

所需的随机数生成器可以是:

static int r(int a)
{
    int q;

    do
        q = rand() / ((RAND_MAX+1u)/a);
    while (a <= q);

    return q;
}

(除以(RAND_MAX+1u)/ado-while循环的目的是将rand的范围缩小到a的偶数倍,以便因非 - 消除了多个范围。)

P中的递归可以转换为迭代。这是省略的,因为没有必要说明算法。)

讨论

如果数字不能包含连续的比特11101,那么最接近的两个1比特可以相隔3比特,如1001。在32位中拟合10 1位然后需要至少28位,如1001001001001001001001001001中所示。因此,为了满足没有11101并且恰好有10 1位的约束,该值必须为1001001001001001001001001001且具有四个0位插入某些位置(包括可能的开头或结尾)。

选择这样的值相当于按某种顺序放置10个001实例和4个0实例。 1 有14个!订购14件商品的方式,但10件中的任何一件!重新排列10 001个实例的方法是相同的,4个中的任何一个!将0个实例重新排列的方法是相同的,因此不同选择的数量是14! / 10! / 4!,也称为从14中选择10件事的组合数。这是1,001。

要使用均匀分布执行此类选择,我们可以使用递归算法:

  • 选择概率分布等于可能排序中选项比例的第一个选项。
  • 递归选择其余选项。

在订购一个对象的 a 实例和第二个对象的 b 时, a /( a + b )潜在排序将从第一个对象开始, b /( a + b )将从第二个对象开始。因此,P例程的设计是:

  • 如果没有要按顺序放置的对象,则返回空位字符串。
  • 在[0, a + b )中选择一个随机整数。如果它小于 a (其概率 a /( a + b )),请插入位字符串001,然后递归以选择 a -1 001 b 0实例的实例的订单。
  • 否则,插入位字符串0,然后递归以选择 a 实例001 b -1实例的订单0

(因为,a为零后,只会生成0个实例,if (a == 0 && b == 0)中的P可能会更改为if (a == 0)。我将其保留为前一种形式,如果涉及其他字符串,则显示解决方案的一般形式。)

加成

这是一个列出所有值的程序(尽管不是按升序排列)。

#include <stdint.h>
#include <stdio.h>

static void P(uint32_t x, int a, int b)
{
    if (a == 0 && b == 0)
        printf("0x%x\n", x);
    else
    {
        if (0 < a) P(x << 3 | 1, a-1, b);
        if (0 < b) P(x << 1, a, b-1);
    }
}

int main(void)
{
    P(0, 10, 4);
}

脚注

1 这个公式意味着我们最终得到一个以001…而不是1…开头的字符串,但结果值(解释为二进制)是等价的,即使有在0之前插入的实例。因此,包含10 001和4 0的字符串与4 0插入1001001001001001001001001001的字符串一一对应。

答案 1 :(得分:5)

在有限数量的解决方案中满足您的标准的一种方法是利用在比特群中不再有四组000的事实。这也意味着值中可以有一组0000。知道了这一点,您可以使用127-31来为您的值增加值,然后继续添加随机位,检查每个添加的位是否满足您的35约束。 / p>

在为您的值添加随机位并满足约束时,总会有组合导致解决方案永远不能满足所有约束。为了防止这些情况,只需保持迭代计数并重置/重新启动值生成,如果迭代超过该值。在这里,如果要找到一个解决方案,它将在不到100次迭代中找到。并且通常在1-8次尝试中找到。对于您生成的每个值的含义,平均不超过800次迭代,这将远小于"97 Seconds or 1165570706 CPU cycles"(我没有计算周期,但回报几乎是瞬时的)

有很多方法可以解决这个问题,这只是一个在合理的时间内工作的方法:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>

#define BPOP   10
#define NBITS  32
#define LIMIT 100

/** rand_int for use with shuffle */
static int rand_int (int n)
{
    int limit = RAND_MAX - RAND_MAX % n, rnd;

    rnd = rand();
    for (; rnd >= limit; )
        rnd = rand();

    return rnd % n;
}

int main (void) {

    int pop = 0;
    unsigned v = 0, n = NBITS;
    size_t its = 1;

    srand (time (NULL));

    /* one of first 5 bits must be set */
    v |= 1u << (NBITS - 1 - rand_int (sizeof v + 1));
    pop++;      /* increment pop count */

    while (pop < BPOP) {        /* loop until pop count 10 */
        if (++its >= LIMIT) {   /* check iterations */
#ifdef DEBUG
            fprintf (stderr, "failed solution.\n");
#endif
            pop = its = 1;  /* reset for next iteration */
            v = 0;
            v |= 1u << (NBITS - 1 - rand_int (sizeof v + 1));
        }

        unsigned shift = rand_int (NBITS);  /* get random shift */

        if (v & (1u << shift))   /* if bit already set */
            continue;
        /* protect against 5 (101) */
        if ((shift + 2) < NBITS && v & (1u << (shift + 2)))
            continue;
        if ((int)(shift - 2) >= 0 && v & (1u << (shift - 2)))
            continue;
        /* protect against 3 (11) */
        if ((shift + 1) < NBITS && v & (1u << (shift + 1)))
            continue;
        if ((int)(shift - 1) >= 0 && v & (1u << (shift - 1)))
            continue;

        v |= 1u << shift;   /* add bit at shift */
        pop++;              /* increment pop count */
    }
    printf ("\nv : 0x%08x\n", v);   /* output value */

    while (n--) {   /* output binary confirmation */
        if (n+1 < NBITS && (n+1) % 4 == 0)
            putchar ('-');
        putchar ((v >> n & 1) ? '1' : '0');
    }
    putchar ('\n');
#ifdef DEBUG
    printf ("\nits: %zu\n", its);
#endif

    return 0;
}

注意:如果您打算在循环中生成多个随机解决方案,您可能需要更好的随机来源,如getrandom()或从/dev/urandom读取 - 特别是如果你从shell中循环调用可执行文件)

我还添加了一个DEBUG定义,您可以通过在编译器字符串中添加-DDEBUG选项来启用它,以查看失败解决方案的数量和最终的迭代次数。

示例使用/输出

连续8次运行的结果:

$ ./bin/randbits

v : 0x49124889
0100-1001-0001-0010-0100-1000-1000-1001

v : 0x49124492
0100-1001-0001-0010-0100-0100-1001-0010

v : 0x48492449
0100-1000-0100-1001-0010-0100-0100-1001

v : 0x91249092
1001-0001-0010-0100-1001-0000-1001-0010

v : 0x92488921
1001-0010-0100-1000-1000-1001-0010-0001

v : 0x89092489
1000-1001-0000-1001-0010-0100-1000-1001

v : 0x82491249
1000-0010-0100-1001-0001-0010-0100-1001

v : 0x92448922
1001-0010-0100-0100-1000-1001-0010-0010

答案 2 :(得分:3)

正如Eric在他的回答中提到的,由于每个1但必须至少有两个0位,所以基本上从28位模式1001001001001001001001001001开始。然后将剩余的四个0位置于此位模式中,并且有11个不同的位置来插入每个零。

这可以通过首先选择1到11的随机数来确定位置的位置来实现。然后你将目标位上方的所有位向左移动1.再重复3次,你就有了自己的价值。

这可以按如下方式完成:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>

void binprint(uint32_t n)
{
    int i;
    for (i=0;i<32;i++) {
        if ( n & (1u << (31 - i))) {
            putchar('1');
        } else {
            putchar('0');
        }
    }
}

// inserts a 0 bit into val after pos "1" bits are found
uint32_t insert(uint32_t val, int pos)
{
    int cnt = 0;
    uint32_t mask = 1u << 31;
    uint32_t upper, lower;

    while (cnt < pos) {
        if (val & mask) {              // look for a set bit and count if you find one
            cnt++;
        }
        mask >>= 1;
    }

    if (mask == (1u << 31)) {
        return val;                    // insert at the start: no change
    } else if (mask == 0) {
        return val << 1;               // insert at the end: shift the whole thing by 1
    } else {
        mask = (mask << 1) - 1;        // mask has all bits below the target set
        lower = val & mask;            // extract the lower portion
        upper = val & (~mask);         // extract the upper portion
        return (upper << 1) | lower;   // recombine with the upper portion shifted 1 bit
    }
}

int main()
{
    int i;
    uint32_t val = 01111111111;        // hey look, a good use of octal!

    srand(time(NULL));
    for (i=0;i<4;i++) {
        int p = rand() % 11;
        printf("p=%d\n", p);
        val = insert(val, p);
    }

    binprint(val);
    printf("\n");
    return 0;
}

两次运行的示例输出:

p=3
p=10
p=9
p=0
01001001000100100100100100100010

...

p=3
p=9
p=3
p=1
10001001000010010010010010010001

运行时间可以忽略不计。

答案 3 :(得分:2)

由于您不想要查找表,所以方法如下:

基本上你有这个数字,28位设置为0和1,你需要插入4x 0:

int special_rng_nolookup(void)
{
    int secret = 0b1001001001001001001001001001;
    int low_secret;
    int high_secret;
    unsigned int i = 28; // len of secret
    unsigned int rng;
    int mask = 0xffff // equivalent to all bits set in integer

    while (i < 32)
    {
        rng = __asm__ volatile(.    // Pseudo code
                    "rdrand"
                 );
        rng %= (i + 1);  // will generate a number between 0 and 28 where you will add a 0. Then between 0 and 29, 30, 31 for the 3 next loop.
        low_secret = secret & (mask >> (i - rng)); // locate where you will add your 0 and save the lower part of your number.
        high_secret = (secret ^ low_secret) << (!(!rng)); // remove the lower part to your int and shift to insert a 0 between the higher part and the lower part. edit : if rng was 0 you want to add it at the very beginning (left part) so no shift.
        secret = high_secret | low_secret; // put them together.
        ++i;
    }
    return secret;
}

因此您可以使用以下算法:

Logger::ERROR