C语言 - 随机生成数字 和 汉字

目录

  • 1.0 写在前面
  • 1 随机生成数字(整数 和 浮点数)
    • 1.1 rand() 简析
      • 本质
      • 使用注意
      • rand()取值范围:
    • 1.2 在 [ m, n ] 中随机取数(整 + 浮)
      • 1.2.1 注意:
      • 1.2.2 思路:
      • 1.2.3 实现代码:
      • 1.2.4 不足:超大区间会失去随机的均匀性
    • 1.3 在 [ m, n ] 中随机取数(整 + 浮) - 初步升级
      • 1.3.0 目标
      • 1.3.1 思路 ( 接 1.2.4 )
        • 生成整数:
        • 生成浮点数
      • 1.3.2 实现代码
    • 1.4 在 [ m, n ] 中随机取数(整 + 浮) - 升级最终版
      • 1.4.1 初步升级的不足(举例描述)
        • 漏洞举例
      • 1.4.2 修改 相邻两数的间隔
      • 1.4.3 代码
      • 1.4.4 不足
        • (1) 不完全均匀
        • (2) rand() 本身的限制
  • 2 随机生成汉字
    • 2.1 需了解的基础
    • 2.2 汉字行列号
    • 2.3 汉字 与 字母 的区分依据
      • 2.3.1 在 [ 16, 87 ] 和 [ 1, 94 ] 随机生成整数
    • 2.4 注意(很重要!)
      • 2.4.1 验证
    • 2.5 实现代码

1.0 写在前面

在区间 [ m, n ] 随机取值,首先应判断 n > m 是否成立!下面的代码没有判断,因为文章仅在 n > m 的情况下,从算法、技术方面实现随机取数

1 随机生成数字(整数 和 浮点数)

最简单的代码:

#include
using namespace std;

int main()
{
	srand(time(NULL));
	int i;
	
	i = rand();
	cout << "i = " << i << endl <<endl;
	
	system("pause"); 
	return 0;
}

1.1 rand() 简析

rand()详解:https://www.cnblogs.com/cyttina/p/3162208.html

本质

用递推公式,得到周期很长的数列 ( 开始的数定了,那数列的数就固定下来 ),至于 rand() 内部递推公式是什么,网上没找到。

递推开始的数就很重要了!
srand( 数字p ); 表示递推从带入 p 开始,意味着:无论程序重复运行几次,每次得到的数列都是一模一样的(易知)
srand(time(NULL)) ; 表示 开始的位置 是用系统的时间,而时间是每分每秒都在变化的,这样就得到了 “ 随机 ” 数列,即随机数,仔细想想依然不够随机…,这一点见 本文1.4.4(2)

使用注意

必须是主函数 中加上 srand(time(NULL)); 而不是在子函数的第一行! 这样 rand() 就是随机 整数
在子函数中想随机生成 整数,同样可以用 rand()
可参见 https://blog.csdn.net/qq_40893824/article/details/105066291

rand()取值范围:

rand() 整数的随机取值范围:[ 0, 32767 ],RAND_MAX = 32768的余数就是 [ 0, 32767 ]

1.2 在 [ m, n ] 中随机取数(整 + 浮)

适用范围 m 和 n 的区间长度 随机取值
仅整数 n – m < 32768 = 2^15 rand()%(n – m + 1) + m
整数、浮点数 n – m >= 32768 = 2^15 1.0 * rand() / 32767 * (n – m) + m 或
( (double)rand() / 32767 ) * (n - m) + m

1.2.1 注意:

1 在n – m >= 32768,随机取值 1.0 * rand() / 32767 * (n – m) + m 中 1.0 不可以省略!
或 ( (double)rand() / 32767 ) * (n - m) + m 中 double 不可省略!
若省略,则随机值 极大概率 会是左边界值 m,极小概率是右边界值 n
因为 rand() 是 [0, 32766] 时 rand() / 32767 = 0!
rand() 是32767 时,rand() / 32767 = 1

2 代码中需注意:
生成浮点数,使用了数据类型 long double,在用 printf() 输出时,输出格式是%-3.4Lf,L是大写不是小写!

float double long double
输入scanf() %-3.4f %-3.4lf %-3.4Lf
输出printf() %-3.4f %-3.4f %-3.4Lf

1.2.2 思路:

1 在区间 [ m, n ] 生成整数
1.1 区间范围 < 32768,rand()%(n – m + 1) + m
1.2 区间范围 >= 32768,1.0 * rand() / 32767 * (n – m) + m

2 在区间 [ m, n ] 生成浮点数:1.0 * rand() / 32767 * (n – m) + m

1.2.3 实现代码:

#include
using namespace std;

void creat()
{
	long long m, n, p, i, length;
	long double m1, n1, p1;
	
	cout << "1 区间取整数" << endl << endl;
	
	cout << "1.1 n - m < 32768" <<endl;
	m = -2113;
	n = 3567;
	cout << "m = " << m << endl;
	cout << "n = " << n << endl;
	cout << "在[" << m << ", " << n << "]中随机取整数:" << endl;;
	for(i = 0; i < 10; i++)
	{	
		p = rand()%(n - m + 1) + m;
		cout << "第" << setw(2) << i + 1 << " 次:p = " << p << endl;
	}
	cout << endl << endl; 
	
	cout << "1.2 n - m >= 32768" <<endl;
	m = -21133;
	n = 35673;
	cout << "m = " << m << endl;
	cout << "n = " << n << endl;
	cout << "在[" << m << ", " << n << "]中随机取整数:" << endl;
	for(i = 0; i < 10; i++)
	{	
		p = (1.0*rand()/32767)*(n - m) + m;
		cout << "第" << setw(2) << i + 1 << " 次:p = " << p << endl;
	}
	cout << endl << endl << endl;
	
	
	cout << "2 区间取浮点数" << endl << endl;
	
	cout << "rand()直接取" << endl; 
	m1 = -21131234.2123123;
	n1 = 35671234.2121231;
	cout << "m1 = " << m1 << endl;
	cout << "n1 = " << n1 << endl;
	cout << "在[" << m1 << ", " << n1 << "]中随机取浮点数:" << endl;;
	for(i = 0; i < 10; i++)
	{	
		p1 = ( (double)rand()/32767 )*(n1 - m1) + m1;
		cout << "第" << setw(2) << i + 1 << " 次:p1 = " ;
		printf("%-7.8Lf\n", p1);
	}
	cout << endl << endl << endl;
}

int main()
{
	srand(time(NULL));
	int i;
	
	i = rand();
	cout << "随机i = " << i << endl << endl << endl;
	
	creat(); 
	
	system("pause"); 
	return 0;
}

C语言 - 随机生成数字 和 汉字_第1张图片//C语言 - 随机生成数字 和 汉字_第2张图片

1.2.4 不足:超大区间会失去随机的均匀性

随机取整数 和 浮点数,当取值范围特别大时,会失去 随机的均匀性,即:区间中有些数是 不可能 被取到的!
原因?

再来研究随机取值的思路:
讨论范围:在区间 [ m, n ] 中 随机取值 p,n - m >= 32768,p = 1.0 * rand() / 32767 * (n – m) + m 看成 [ m, n ] 的随机值,p 可能的取值有哪些?

本质上,rand()随机在 [ 0, 32767 ] 中随机取数,有 32768 种情况,扩展到 [ m, n ] 其实仍然有32768种情况(易知)
[ 0, 32767 ] 的 323768 种情况是:0, 1, 2, …, 32766, 32767
[ 0, 32767 ] 和 [ m, n ] 的 323768 种情况比较:

情况编号 rand() 随机数p [ m, n ] 随机数p
1 0 m
2 1 m + (n - m) / 32767
3 2 m + 2 * (n - m) / 32767
4 3 m + 3*(n - m)/32767
32767 32766 m + 32766(n - m)/32767
32788 32767 m + 32767(n - m)/32767 = n

绝对不会取到的数的个数 = n - m + 1 - 32767 = n - m - 32768

当 n - m 超过 32768 不是很多时,未取到的数很少(姑且可忽略),其随机生成的数还可以看成随机数
但是当 n - m 远超 32768 时,随机的数就不能当成随机数了!
下面的1.3改进了这个不足!

1.3 在 [ m, n ] 中随机取数(整 + 浮) - 初步升级

1.3.0 目标

均匀性:每个整数都会被取到,且概率相同

1.3.1 思路 ( 接 1.2.4 )

生成整数:

在 [ m, n ] 中随机生成整数,区间长度 length = n - m
然后在 [ 0, length ] 中随机取数 p,最后再加上 m,这样就得到 [ m, n ] 的随机整数,怎么随机取整数 又不失 均匀性?

A length < 32768 时,返回 rand() ; 而不是返回 rand()%(length+1);
B 否则
r = rand() ;
p = ( 1.0 * r / 32767 ) * length ;
r 分 2 种情况
(1) 当取 0 - 32766,相邻两数的间隔是 length = length / 32767 , 递归 至 A,p += 递归返回的数
(2) 当取 32767,这个数就是边界 length,返回 p
代码在(1) 后,不管是不是 32767 都要 return p; 因为这时可以看成 p 已经是生成好的数了,需要返回。

这样可保证 [ 0, length ] 中每个 整数 都可能 被取到。
即,保证了 [ m, n ] 中每个 整数 都可能 被取到

注意:
1 长度 比 区间包含数字的个数 小 1
2 区间开闭
上面两点在编程时很容易混淆,不知道该除以长度还是数字的个数
闭区间 除以 长度,此时端点会被取到,容易思考;
若除以包含的数字个数,每次随机取数要额外考虑最后一段的取值范围,麻烦 又 容易出错

生成浮点数

1 [ m, n ] 随机生成浮点数,m 和 n 本身也是浮点数,区间 length = n - m 是浮点数
将 length 强制转为 long long 类型,再随机取整数,小数部分单独写个过程随机取
如何取整 或 强制取整 见文章:https://blog.csdn.net/qq_40893824/article/details/105922443

2 怎么取?在什么范围内随机取?
因为整数部分已经随机取了,而且 每个整数都有被取到的可能,那么现在的小数,它的范围应该在 [ 0, 1) 之间,注意开闭!
有一种情况不是 [ 0, 1) 间取值
举例这样描述这个情况:length = 12.345,整数就在随机在 [ 0, 12 ] 中取值,当取到12 时,我的小数部分就不能在 [ 0, 1) 间取值,因为可能会超过边界,比如小数取到 0.6,则12 + 0.6 = 12.6 > 12.345,越界了,应在 [ 0, 0.345 ] 中取浮点数

此时定义 r 是取到的整数
当 length - r < 1时,在 [ 0, length - r ] 中取值 (1.0 * rand()) / 32767 * ( length - r )
当 length - r >= 1时,在 [ 0, 1) 中取值 (1.0 * rand()) / 32768 ;

注意:
上面除的数 32767 和 32778 不一样
因为是 2 种情况:32767 对应闭区间,32768 对应开区间!
为什么 length - r >= 1时,在 [ 0, 1) 中取值 而不是 [ 0, 1 ] 中取值?
原因:若 区间边界1 可能被取到,那 此时的数就不是小数了,整数平均被取到的均匀性会破坏掉

1.3.2 实现代码

#include
#define min 1e-4 
using namespace std;

long long LLmyrand(long long length) 
{
	long long r, p;
	if(length < 32768)
		return rand()%(length+1);
	else
	{
		r = rand();
		p = (1.0*r/32767)*length;
		//cout << "粗略 p - m =  " << p << endl;
		if (r < 32767)
		{
			length /= 32767; // length被分了32767份, length/32767 就是每一份的长度; 
			//注意:
			// a 长度 比 区间包含数字 的个数小 1
			// b 区间的开闭
			// a、b 两点在编程时很容易混淆,不知道该除以长度还是数字的个数
			// 闭区间 除以 长度,此时端点会被取到,容易思考;若除以包含的数字个数,每次随机取数要额外考虑最后一段的取值范围,这样不好 
			p += LLmyrand(length);
		}
		return p;
	}
}

long double LDmyrand(long double length) 
{
	long long r;
	long double p, leng;
	r =  LLmyrand((long long)length); 
	//cout << "粗略 p - m =  " << p << endl;
	
	p = (long double)r ;
	leng = length - r;
	if(leng >= 1)
	{
		p += (1.0*rand())/32768;	
	}
	else
	{
		p += (1.0*rand())/32767*leng;
	}
	return p;
}

void creat()
{
	long long m, n, p, i, length;
	long double m1, n1, p1, length1;
	
	cout << "n - m > 32768 生成整数 - 改进" << endl << endl;
	m = -211123234232;
	n =  331123234234;
	cout << "m = " << m << endl;
	cout << "n = " << n << endl;
	cout << "在[" << m << ", " << n << "]中随机取整数:" << endl << endl;
	for(i = 0; i < 10; i++)
	{	
		length = n - m;
		p = LLmyrand(length) + m;
		//cout << "p - m =  " << p - m << endl;
		cout << "第" << setw(2) << i + 1 << " 次:p = " << p << endl << endl;
	}
	cout << endl << endl << endl; 
	
	
	cout << "n1 - m1 > 32768 生成浮点数 - 改进" << endl << endl;
	m1 = -1231212312.123123123;
	n1 =  1231567358.1212;
	cout << "m1 = " << setprecision(20) << m1 << endl;
	cout << "n1 = " << setprecision(20) << n1 << endl;
	cout << "在[" << setprecision(20) << m1 << ", " << n1 << "]中随机取浮点数:" << endl << endl;
	for(i = 0; i < 10; i++)
	{	
		length1 = n1 - m1;
		p1 = LDmyrand(length1) + m1;
		//cout << "p1 - m1 =  " << p1 - m1 << endl;
		cout << "第" << setw(2) << i + 1 << " 次:p1 = " << setprecision(20) <<p1 << endl << endl;
	}
	cout << endl << endl << endl; 
}

int main()
{
	srand(time(NULL));
	int i;
	i = rand();
	
	creat(); 
	
	system("pause"); 
	return 0;
}

C语言 - 随机生成数字 和 汉字_第3张图片 C语言 - 随机生成数字 和 汉字_第4张图片

1.4 在 [ m, n ] 中随机取数(整 + 浮) - 升级最终版

1.3 中,在整数区间的范围内随机取整数
在随机浮点数中,也用到了随机取整数的操作,弥补了随机的均匀性

1.4.1 初步升级的不足(举例描述)

1.3 中尝试 随机取数的均匀性(每个整数都会被取到,且概率相同)
再看 1.3 的思路:

在 [ m, n ] 中随机生成整数,区间长度 length = n - m
直接在 [ 0, length ] 中随机取数 p,最后再加上 m,这样就得到 [ m, n ] 的随机整数

A length < 32768 时,返回 rand()%length ;
B 否则
r = rand() ;
p = ( 1.0 * r / 32767 ) * length ;
r 共有 32768 种情况
(1) 当取 0 - 32766,相邻两数的间隔是 length = length / 32767 , 递归 至 A,p += 递归返回的数
(2) 当取 32767,这个数就是边界 length,返回 p

有漏洞的地方:

当取 0 - 32766,相邻两数的间隔是 length = length / 32767 , 递归 至 A,p += 递归返回的数

漏洞举例

简化模型:在 [ 3, 26 ] 中随机取整数,rand()的范围是 [ 0, 3 ],即RAND_MAX = 3,length = 26 - 3 = 23,在 [ 0, 23 ] 中随机取数
r = rand()

r p = ( 1.0 * r / 3 ) * 26 ;
0 0
1 7
2 15
3 23

0, 7, 15, 23 的间隔分别是:7, 8, 8

这说明 相邻两数的间隔不是固定的 length / 32767!

1.4.2 修改 相邻两数的间隔

length = (1.0*(r + 1)/32767)*length - (1.0*r/32767)*length; 

1.4.3 代码

代码太长,单独写成一篇文章:https://blog.csdn.net/qq_40893824/article/details/105928858

1.4.4 不足

(1) 仍未到达完全的均匀性
(2) rand() 本身的不足限制

(1) 不完全均匀

每个数会被取到(√),每个数被取到的概率相同(×

例子 - 简化模型:
在 [ 3, 26 ] 中随机取整数,rand()的范围是 [ 0, 3 ],即RAND_MAX = 3,length = 26 - 3 = 23,在 [ 0, 23 ] 中随机取数
第一次取值可能是:0, 7, 15, 23 (概率相同)
第二次取值概率:

第1次取值 概率 下一次取值每个数的概率
0 1/4 1/7
7 1/4 1/8
15 1/4 1/8

得:

数字 取值概率
0-6 ( 1/4 ) * ( 1/7 )
7 ( 1/4 ) * ( 1/8 )
15 ( 1/4 ) * ( 1/8 )
23 1/4

这样看来每个数都可能被取到,但概率不唯一,即仍不均匀
简单验证过程是否有问题,看概率和是否为 1
( 1/4 ) * ( 1/7 ) * 7 +
( 1/4 ) * ( 1/8 ) * 8 +
( 1/4 ) * ( 1/8 ) * 8 +
1/4 = 1

(2) rand() 本身的限制

可看看:https://blog.csdn.net/czc1997/article/details/78167705
在没有开始 rand() 时,rand 是随机的,一旦开始,其生成的数就是确定了
因为 rand() 本质是周期很长的数列(递推),给定递推开始的数,那么后面的数都是可以用公式表示出来

以上两点均暂时没想出解决的办法

2 随机生成汉字

2.1 需了解的基础

1个汉字存进 字符数组,貌似是占 1个字节单元,其实是 2个字节单元!汉字有个区位码表
高字节 对应 区码( = 行号)
低字节 对应 位码( = 列号)
区码 + 位码 = 区位码

区位码表(94 x 94 的表格):每个汉字 对应唯一 1个位置
内容见:https://www.doc88.com/p-9955966114086.html ( 无 04 - 15 分区内容)
和 https://wenku.baidu.com/view/b5340aef11661ed9ad51f01dc281e53a580251a2.html ( 含 04 - 09 分区内容 )

为什么不见 10 - 15分区内容?因为:
01 - 09 区 :特殊符号
10 - 15 区 :用户自定义符号(未编码,属于一级汉字范围)
16 - 55 区 :一级汉字(3755 = (55 - 16 + 1 - 1)*94 + 89),按拼音排序(55时,列号范围:[ 1, 89 ] !
56 - 87 区 :二级汉字(3008 = (87 - 56 + 1)*94),按部首/笔画排序
88 - 94 区 :用户自定义汉字(未编码)

所以汉字共 6763

2.2 汉字行列号

汉字的行号范围 [16, 87],列号范围 [1, 94]

2.3 汉字 与 字母 的区分依据

1 个汉字 占 2 个字节( = 16 bit),每 1 个字节 = 8 bit 位
而且每 1 个字节的最高位(符号位) 一定 是1,代表负数!
1 个字母 占 1 个字节,最高位 一定 是 0, 代表正数!

2.3.1 在 [ 16, 87 ] 和 [ 1, 94 ] 随机生成整数

	a = rand()%(87 - 16 + 1) + 16; //在[16, 87]随机生成整数
	b = rand()%num1 + 1; //在[1, 94]随机生成整数
	str[2*i] = 0 - a; // 别忘了是负数!
	str[2*i + 1] = 0 - (b); // 同理

2.4 注意(很重要!)

本次注意单独列出来,很重要

  1. 单个汉字,它的区位码 在C语言 或 计算机中 是负数,这个没错,但并不是无脑直接让符号位变成1!
    而是本身的区位码减去96(网络别处 我没看到谁提到过这个)
  2. 区码为 55 的时候,位码 范围是 [ 1, 89 ] … 而不是 [ 1, 94 ] 。这是在随机生成汉字的时候,总出现 bug 空格,发现的知识点

2.4.1 验证

1 汉字区位码 在计算机中存储
以汉字 ‘山’ 为例

汉字 ‘山’ 的 位码 = 41,位码 = 29

#include
#define num 100
using namespace std;

int main()
{
	char b0[num], b1[num];
	//
	cout << "汉字真正区位码 与 实际存储:" << endl << endl;
	
	char str[num] = "山";
	
	cout << "山: "  << endl;
	cout << "真正的 区码 = 41, 位码 = 29" << endl;
	
	itoa(str[0], b0, 10);
	itoa(str[1], b1, 10);
	cout << "计算机中 存储的区码 = "<< b0 << ", 位码 = " << b1 << endl;
	cout << "41 - (-55) = " << 41 + 55 << endl;
	cout << "29 - (-67) = " << 29 + 67 << endl << endl;;
	
	system("pause");
	return 0;
}
C语言 - 随机生成数字 和 汉字_第5张图片

2 区码是 55 的时候,位码 范围是 [ 1, 89 ]:

2.5 实现代码

#include
#define num 10000
#define num1 94
using namespace std;

void creat(char *str, FILE *fp)
{	
	char name[] = "./set.txt";
	fp = fopen(name, "w+");

	cout << "随机生成汉字" << endl;
	int length = rand()%num, i, a, b; 
	cout << "想生成 " << length << "个字" << endl;
	
	str = (char*)malloc((2*length + 1)*sizeof(char));
	
	for(i = 0; i < length; i++)
	{
		a = rand()%(87 - 16 + 1) + 16; //在[16, 87]随机生成整数
		if(a == 55)
			b = rand()%(89 - 1 + 1) + 1; //在[1, 89]随机生成整数
		else
			b = rand()%num1 + 1; //在[1, 94]随机生成整数
		str[2*i] = a - 96; // 别忘了是负数!
		str[2*i + 1] = b - 96; // 同理
		//printf("%d %d\n",str[2*i] + 96, str[2*i + 1] + 96);
	}
	str[2*length] = '\0';
	cout << "生成完毕!" << endl << endl;
	
	puts(str);
	fputs(str,fp);
	cout << endl << endl;
	fclose(fp);
}

int main()
{	
	srand(time(NULL));
	char str[] = "";
	int i;
	FILE *fp;
	
	for(i = 0; i < 1; i++)
		creat(str, fp);
	
	system("pause");
	return 0;
}
C语言 - 随机生成数字 和 汉字_第6张图片

你可能感兴趣的:(记录)