题目描述
小蓝最近在研究一种浮点数的表示方法:R 格式。对于一个大于 0 的浮点数 d,可以用 R 格式的整数来表示。给定一个转换参数 n,将浮点数转换为 R 格式整数的做法是:
- 将浮点数乘以 2n。
- 四舍五入到最接近的整数。
输入格式
一行一个整数 n 和一个浮点数 d。
输出格式
一行一个整数表示 d 用 R 格式表示出的值。
cs
输入
2 3.14
输出
13
说明/提示
样例 1 解释
3.14×2
2
=12.56,四舍五入后为 13。
数据规模与约定
用 t 表示将 d 视为字符串时的长度。
对于 50% 的数据,保证 n≤10,t≤15。
对于全部的测试数据,保证 1≤n≤1000,1≤t≤1024,保证 d 是小数,即包含小数点。
cs
#include <stdio.h>
#include <string.h>
#define MAX_LEN 200010 // 数组开大一点
int n, a[MAX_LEN], p, l; // p用于记录小数点位置,l为字符串长度
char q[MAX_LEN];
// 乘法函数:所有位乘以2
void multiply() {//只表示进行一次*2
// 按位乘2
for (int i = 1; i <= l; i++) {
a[i] *= 2;//数组中的每一个元素都*2
}
// 处理进位
for (int i = 1; i <= l; i++) {
a[i + 1] += a[i] / 10;
a[i] %= 10;
}
// 如果最高位的前一位进位后不是0就要增加长度
if (a[l + 1] > 0) {
l++;
}
}
int main() {
// 输入
scanf("%d %s", &n, q);
// 反转字符串
int len = strlen(q);
for (int i = 0; i < len / 2; i++) {
char temp = q[i];
q[i] = q[len - 1 - i];
q[len - 1 - i] = temp;
}
// 查找小数点位置
p = -1;
for (int i = 0; i < len; i++) {
if (q[i] == '.') {
p = i;
break;
}
}
if (p == -1) {
printf("错误:输入中没有小数点!\n");
return 1;
}
// 删除小数点
for (int i = p; i < len - 1; i++) {
q[i] = q[i + 1];
}
len--; // 长度减1
// 字符串转整数数组
l = len;
for (int i = 0; i < l; i++) {
a[i + 1] = q[i] - '0';
}
// n次乘2
for (int i = 1; i <= n; i++) {
multiply();
}
// 四舍五入:小数部分第一位>=5则进位
if (a[p] >= 5) {
a[p + 1]++;
}
// 处理进位
for (int i = p + 1; i <= l; i++) {
a[i + 1] += a[i] / 10;
a[i] %= 10;
}
// 再次检查最高位
if (a[l + 1] > 0) {
l++;
}
// 输出结果(因为前面逆序了所以这里倒着输出)
for (int i = l; i > p; i--) {
printf("%d", a[i]);
}
printf("\n");
return 0;
}
解释上述代码:
从主函数开始:
**步骤1:**将浮点数储存在q数组中。
步骤2:
为什么要反转字符串?(这是我一开始没有想到的)
cs
原始:"3.14"
反转后:"41.3"
-
人类书写:高位在左,低位在右(123:百位1,十位2,个位3)
-
数组计算:低位在左更方便(a[1]=3个位, a[2]=2十位, a[3]=1百位)
-
反转后,个位在数组开头,便于进位处理
步骤3:定位小数点
cs
反转后:"41.3"
小数点位置 p = 2(0-based索引)
步骤4:删除小数点
cs
删除前:"41.3"
删除后:"413"
长度 len = 3
步骤5:转换成数组
cs
数组 a(从1开始存储):
a[1] = '4' - '0' = 4 // 原个位
a[2] = '1' - '0' = 1 // 原十位
a[3] = '3' - '0' = 3 // 原十分位
长度 l = 3
重要关系:
-
p=2是原小数点的位置 -
删除小数点后,
a[p+1] = a[3] = 3是原小数部分第一位
为什么要减去'0'呢?
因为我们是将数字当作字符来处理的,所以字符数字对应的是ASCII码值,并不是数字。字符'0'的ASCII码值是48,所以只有每个字符数字减去字符'0'的ASCII码值才是真正的数字。
|------|--------|----------|----------------|
| 字符数字 | ASCII码 | '4' = 52 | '4' * 2 = 104 |
| 整数数字 | 实际数值 | 4 | 4 * 2 = 8 |
步骤6:执行n次乘2
cs
初始:a = [4, 1, 3] // 表示 3.14(反转后)
第1次乘2:
每位乘2: [8, 2, 6]
进位处理: [8, 2, 6](无进位)
第2次乘2:
每位乘2: [16, 4, 12]
进位处理:
a[3]=12 → a[4]+=1, a[3]=2 → [16, 4, 2, 1]
a[2]=4 → 无进位
a[1]=16 → a[2]+=1, a[1]=6 → [6, 5, 2, 1]
a[2]=5 → 无进位
最终:a = [6, 5, 2, 1],l=4
进位处理详细解析:
十进制数进位规则:逢十进一(例如:某一位是9,9+1=10,个位归0,十位进1)
当某一位 aᵢ ≥ 10 时,需要:
-
将 aᵢ / 10 加到更高位 aᵢ₊₁
-
将 aᵢ % 10 留在当前位
为什么不能直接将3.14*2^n得到的结果,12.56,逢5进1呢?
cs
#include<stdio.h>
#include<math.h>
int main()
{
int n;
double d;
scanf("%d%lf",&n,&d);
double a=d*pow(2,n);
int e=(int)a;
double b=a-e;
int c=(int)(b*10);
if(c>=5){
e=e+1;
}
printf("%d",e);
return 0;
}
明确最后计算得到的结果都要看小数点后的第一位,如果>=5则需要进位,这里的问题主要出在如果直接一次全*pow(2,n)会导致最后的结果不是12.56.具体问题看下面:
首先问题在于浮点数的精度限制:
cs
// 示例1:0.1 的二进制表示
double d = 0.1;
// 实际存储可能是:0.099999999999999999...
// 当你计算 d * pow(2, n) 时,误差会被放大
// 示例2:12.56 的情况
double d = 12.56;
// 实际存储可能是:12.559999999999999...
// (int)(b * 10) 可能得到 5,也可能得到 4
再就是我的方法受double精度限制,不能处理特别大的数
double最大约1.7e308,但只有约15-16位有效数字
高精度方法可以处理任意大的数
只要数组足够大,可以处理几千位的数字
而且一次就乘2的n次方,如果n特别大的时候,pow(2,n)可能溢出或精度损失。
步骤7:四舍五入,进位处理
特别要注意明确小数点被删除后,数组的哪一个下标表示小数点后的第一位。
反转后删除小数点的 "413" 实际表示 3.14:
-
3(十分位)在 a[3]
-
1(个位)在 a[2]
-
4(十位)在 a[1]
乘以2两次后得到 "1252"(反转),表示 12.56:
-
2(百分位)在 a[3]
-
5(十分位)在 a[4]
-
2(个位)在 a[2]
-
1(十位)在 a[1]
根据上述数字例子发现小数部分的第一位应该是a[p]=a[2]=5;
步骤8:处理进位传播
cs
从 i=p+1=3 开始:
a[3]=3 → 无进位
a[4]=5 → 无进位
步骤9:倒序输出结果
这道题目主要就是从没有想过十进制小数进位应该把它们一个一个的放到数组中,一个数字一个数字进行处理,即逐位进位。进位规则也要明确。特别还是没想到倒序数组。