题目数据规模:-1e20≤A,B≤1e20,小数点数位最大为20位,要求输出A+b的结果,保留小数点后30位
时间: 2025-08-06 15:00:07 浏览: 5
<think>题目要求:输入两个实数A和B,每个数的绝对值不超过1e20,小数部分不超过20位,输出A+B并保留30位小数。
由于数据范围很大,且要求精确到小数点后30位,我们无法使用常规的浮点数类型(如double,其精度只有15-17位小数)。
因此,我们需要使用高精度算法来处理。这里我们选择将数字拆分为整数部分和小数部分,分别进行高精度计算。
思路:
1. 将每个数解析为整数部分、小数部分和符号。
2. 对齐小数部分:将两个数的小数部分都扩展到30位(题目要求输出30位小数,但输入小数部分最多20位,所以我们需要在输入的小数部分后面补0到30位,这样在计算时就能保证精度)。
3. 将整数部分和小数部分分别视为大整数,然后进行加法运算(注意小数部分相加时可能向整数部分进位)。
4. 处理进位:小数部分相加后,如果产生进位,则加到整数部分。
5. 处理符号:两个数可能异号,这时需要做减法。我们需要考虑大数减小数,以及符号的确定。
但是,由于题目中A和B可能为负数,我们需要考虑两种情况:
- 同号:绝对值相加,符号不变。
- 异号:绝对值相减,符号取决于绝对值大的那个数。
然而,将整数部分和小数部分分开处理会使得异号的情况变得复杂。因此,我们可以将整个数(包括整数和小数)当作一个大的整数来处理,也就是将两个数都乘以10^30(相当于将小数部分变为整数),然后相加,最后再调整小数点的位置。
具体步骤:
1. 读取两个字符串A和B。
2. 将每个字符串解析为两部分:整数部分和小数部分(如果没有小数点,则小数部分为空)。同时记录符号。
3. 将整数部分和小数部分组合成一个整数:整数部分后面拼接小数部分(补足到30位),这样我们就得到了一个表示这个数乘以10^30的大整数。
4. 然后根据符号,将两个大整数进行加法或减法运算(同号相加,异号相减)。
5. 运算结果是一个大整数,它表示(A+B)*10^30。然后我们将其转换为字符串,并在倒数第30位前插入小数点(即整数部分为除最后30位的前面部分,小数部分为最后30位,注意如果结果不足30位,需要在前面补0)。
6. 同时注意处理进位和借位,以及结果的符号。
但是,注意题目要求保留30位小数,所以我们实际上在计算时已经将小数部分扩展到了30位(即乘以10^30),因此计算后的整数结果就包含了30位小数。
然而,由于两个数的绝对值可能很大(1e20),整数部分最大有21位(因为1e20有21个数字),小数部分30位,所以整个大整数的位数最多为51位。这个范围在C++的long long范围内?但是1e20已经接近long long的最大值(约9e18),所以这里不能使用long long,而必须使用字符串来存储大整数。
因此,我们需要实现大整数的加法和减法(带符号)。
为了简化问题,我们可以将每个数(包括小数部分)当作一个字符串表示的大整数,同时记录符号。然后进行大整数加减法。
步骤:
1. 解析每个数,得到符号和整数部分、小数部分(小数部分补0到30位)。
2. 将整数部分和小数部分合并成一个字符串(去掉小数点),注意整数部分要去除前导0(但整数部分为0时保留0),小数部分补足30位(如果小数部分位数不足30,则在后面补0;如果超过30位,则只取前30位?题目要求小数部分不超过20位,所以输入最多20位,我们直接补0到30位)。
3. 这样,每个数就变成了一个字符串表示的大整数(总长度为整数部分长度+30),这个数实际上是原数乘以10^30。
4. 然后根据符号,进行大整数加法或减法(同号相加,异号相减,减法时注意绝对值大小)。
5. 得到结果后,将这个结果字符串转换为带小数点的形式:从右往左数30位,左边是整数部分,右边是小数部分(如果结果字符串长度小于30,则需要在前面补0直到长度为30,然后整数部分为0)。
6. 同时,注意处理符号和进位。
但是,合并后的大整数可能很大:整数部分最多21位,小数部分30位,总共最多51位。两个51位的数相加,结果最多52位。
我们实现两个大整数(字符串形式)的加法和减法(支持负数运算)。
然而,为了处理符号,我们可以将符号单独处理,然后只对绝对值进行加法和减法运算。
具体算法:
1. 解析两个数,得到两个大整数(乘以10^30后的整数)和符号。
2. 如果两个数同号,则绝对值相加,符号不变。
3. 如果两个数异号,则比较两个绝对值的大小,用大的减去小的,符号取绝对值大的那个数的符号。
4. 将结果转换为字符串,并插入小数点。
但是,注意:在解析输入时,整数部分如果为空,则视为0;小数部分不足30位则补0。
例子:
输入:1.5 2.5
解析第一个数:整数部分"1",小数部分"5" -> 补0到30位,得到小数部分"500000000000000000000000000000"
合并:整数部分+"小数部分" -> "1"+"500000000000000000000000000000" -> "1500000000000000000000000000000"
注意:这里实际上应该是1.5 * 10^30 = 1500000000000000000000000000000,但是合并时我们直接拼接,所以整数部分后面跟着30位小数,这个数就是1500000000000000000000000000000(16位整数?不对,这里整数部分1,小数部分30位,拼接后是31位数字)
实际上,我们合并后的字符串表示的是原数乘以10^30,所以1.5变成了1500000000000000000000000000000(31位),2.5变成了2500000000000000000000000000000(31位),相加得到4000000000000000000000000000000(31位),然后我们将其转换为字符串,并插入小数点:整数部分为4,小数部分为30个0,即4.000000000000000000000000000000。
但是,如果输入是负数呢?
输入:-1.5 2.5
解析:第一个数为负,第二个数为正,异号。比较绝对值:1.5和2.5,2.5大,所以结果为正。然后2.5的整数减去1.5的整数:2500000000000000000000000000000 - 1500000000000000000000000000000 = 1000000000000000000000000000000,然后除以10^30(即小数点左移30位)得到1.000000000000000000000000000000。
然而,这里我们实际上已经将两个数都乘以10^30,所以直接进行整数运算,最后在输出时再除以10^30(即插入小数点)。
因此,我们只需要实现两个大整数的加法和减法(非负整数,因为我们已经将符号单独处理)。
但是,注意:如果两个数异号,我们实际上是用绝对值大的减去绝对值小的,所以我们需要比较两个数的绝对值(即合并后的大整数)的大小。
步骤总结:
1. 解析输入字符串,得到符号和合并后的大整数字符串(去掉小数点,整数部分+小数部分补0到30位)。
2. 将两个大整数字符串(非负整数)和符号传入一个函数,计算它们带符号的和。
3. 函数内部:
如果同号,则进行加法,符号不变。
如果异号,则比较两个大整数的大小,用大的减去小的,符号取大数的符号。
4. 将得到的大整数结果(字符串)格式化为小数:如果结果字符串长度小于等于30,则整数部分为0,小数部分为结果字符串(前面补0到30位?实际上,如果长度小于30,需要在前面补0,然后整数部分为0;如果等于30,则整数部分为0,小数部分为结果字符串;如果大于30,则整数部分为前len-30位,小数部分为后30位。
5. 注意:如果结果是0,则符号为正(避免-0)。
但是,合并后的大整数字符串可能有前导0,我们需要在比较和计算前去除前导0吗?在解析时整数部分已经去除了前导0(除非整数部分为0,则保留0)。但是合并后,整数部分可能为0,那么整个字符串就是30个0?不对,我们合并后的小数部分有30位,所以整个字符串长度为整数部分长度+30。整数部分去除了前导0,但整数部分为0时,我们合并后的字符串是"0"+"000...0"(30个0)-> "0"后面30个0,总共31个字符?实际上,整数部分为0时,我们解析得到的整数部分是"0",所以合并后是"0"+"000...0"(30个0)-> "0000...0"(31个0)?这样不好,我们应该在整数部分为0时,合并后的大整数就是30个0?不对,因为整数部分为0,那么整个数就是0.xxx,乘以10^30后是一个30位或31位的数?实际上,0.1乘以10^30是100000000000000000000000000000(30位),所以整数部分为0时,我们合并后的字符串应该是小数部分(30位)?但是小数部分我们补0到30位,所以整数部分为0时,合并后的字符串就是小数部分(30位),这30位可能包含前导0(比如0.1解析后小数部分是"1"然后补29个0,得到"100000000000000000000000000000")。
因此,在解析时,整数部分和小数部分分别处理:
整数部分:如果为空则设为"0",否则去除前导0,如果去除后为空则设为"0"。
小数部分:补0到30位。
合并后的大整数 = 整数部分 + 小数部分(一个字符串,长度为整数部分长度+30)
但是,这样合并后,整数部分为0时,整个大整数就是小数部分(30位),而小数部分可能前面有0(比如0.0001,小数部分为0001后面补26个0,那么合并后就是"0000...0001"共30位)。这样在比较时,我们需要去除前导0吗?在计算时,我们的加法和减法函数应该能够处理前导0。
所以,我们需要实现两个大整数的比较(字符串形式,无前导0?不,可能有前导0,所以我们在比较函数中需要去除前导0?或者我们的加法和减法函数可以处理前导0?)
因此,我们实现的大整数加法和减法函数需要能够处理前导0(即忽略前导0,但注意全0的情况)。
但是,为了简单,我们可以在合并后的大整数字符串中,如果整数部分是0,那么整个字符串前面可能会有0(比如"0"+"000...0"得到"0000...0"),这样我们可以先去除前导0,但是注意如果去除后为空,则设为"0"。
然而,在解析时,整数部分已经去除了前导0,所以整数部分不会出现前导0(除非是0)。但是小数部分我们补0到30位,所以合并后的字符串前面可能有0(当整数部分为0时,小数部分前面可能有0)。例如,输入0.1,整数部分为"0",小数部分为"1"补29个0,合并后为"0"+"100000000000000000000000000000" -> 这个字符串是"0100000000000000000000000000000"?不对,我们合并时是整数部分字符串直接拼接小数部分字符串,所以整数部分"0"和小数部分"100000000000000000000000000000"拼接后是"0100000000000000000000000000000",这有31位,而且开头是0。这个0是多余的,所以我们需要在合并后去除整数部分的前导0?但是注意,整数部分可能是0,但小数部分不为0,所以这个0是整数部分的0,不能去掉整个0?因为去掉后就成了100000000000000000000000000000(30位),这相当于10^29,而0.1*10^30=10^29,所以实际上应该去掉整数部分的前导0,但整数部分为0时,我们只保留一个0?这样不对,因为0.1*10^30=100000000000000000000000000000(30位),所以整数部分应该是1后面29个0?不对,0.1*10^30=10^29,所以有30位?10^29是1后面29个0,共30位?不对,1后面29个0是30位数。
所以,0.1乘以10^30就是100000000000000000000000000000(30个数字),所以我们在合并时,整数部分为0,小数部分为100000000000000000000000000000(30位),那么合并后的大整数应该是100000000000000000000000000000(30位)?不对,因为整数部分0和小数部分合并后,我们得到的是"0"+"100000000000000000000000000000" -> "0100000000000000000000000000000"(31位)。这显然不对。
因此,我们应该在整数部分为0时,将整个大整数设置为小数部分(30位)?但是小数部分可能前面有0,所以我们需要将整数部分为0和小数部分合并后的大整数去除前导0,但注意保留至少一位(如果全0则为0)。
所以,在得到合并后的大整数字符串(整数部分+小数部分)后,我们去除前导0,如果去除后为空,则设为"0"。这样,0.1解析后合并的字符串为"0100000000000000000000000000000"(31位),去除前导0后为"100000000000000000000000000000"(30位),这就正确了。
但是,整数部分不为0时,比如1.1,整数部分"1",小数部分"1"补29个0,合并后为"1100000000000000000000000000000"(31位),去除前导0后还是它,正确。
所以步骤:
合并后的大整数字符串 = 整数部分字符串 + 小数部分字符串(30位)
然后去除这个字符串的前导0(注意,如果整数部分为0,小数部分前面有0,那么合并后前面会有0,去除后得到的就是小数部分去掉前导0?但是小数部分我们补0到30位,所以整数部分为0时,合并后的字符串前面可能有多个0,去除后就是小数部分去掉前导0,但小数部分我们补0到30位,所以去掉前导0后可能小于30位?不对,我们去除前导0后,剩下的就是整个数的有效部分,然后我们进行运算,最后在输出时再补0到小数部分30位?不,我们在计算时已经乘以10^30,所以结果应该是一个整数,然后我们将其格式化为30位小数。
但是,去除前导0后,我们得到的是这个数的实际值(乘以10^30的整数),然后我们进行运算,运算结果也是整数(乘以10^30的结果),然后我们将其转换为字符串,再格式化为小数。
所以,在解析后,我们得到两个大整数(字符串,已经去除前导0)和符号。
然后进行带符号的运算。
因此,我们需要实现:
- 大整数加法(两个非负整数)
- 大整数减法(两个非负整数,要求a>=b,否则交换并取负号?但是我们在比较时已经保证用大的减小的)
- 大整数比较(两个非负整数)
注意:在异号相减时,我们比较两个数的绝对值(即大整数)的大小,然后决定用大的减小的,并取大数的符号。
具体实现:
1. 解析函数:parse(string s)
输入:字符串s
输出:符号(bool,true为负,false为正),大整数(字符串,已经去除前导0,如果是0则输出"0")
步骤:
a. 判断符号:如果第一个字符是'-',则符号为true,并去掉这个字符;否则为false。
b. 查找小数点:
如果没有小数点,则整数部分为整个字符串,小数部分为空。
如果有小数点,则整数部分为小数点前,小数部分为小数点后(最多取20位?题目说小数部分不超过20位,但我们要补0到30位)。
c. 整数部分处理:如果整数部分为空,则设为"0";否则去除前导0,如果去除后为空则设为"0"。
d. 小数部分处理:截取前20位(如果超过20位,题目保证不超过20位),然后补0到30位(所以小数部分总是30位)。
e. 合并:整数部分 + 小数部分 -> 得到一个字符串(长度=整数部分长度+30)
f. 去除这个字符串的前导0:如果全部是0,则结果为"0",否则去掉前面的0。
注意:去除前导0后,这个字符串就是原数乘以10^30的整数。
2. 大整数加法:string add(string a, string b)
输入:两个非负整数的字符串
输出:和(字符串)
3. 大整数减法:string sub(string a, string b) // 要求a>=b
输入:两个非负整数的字符串,且a>=b
输出:差(字符串)
4. 大整数比较:int compare(string a, string b) // 返回1表示a>b,0表示a==b,-1表示a<b
5. 主函数:
读取两个字符串s1, s2。
解析得到 (sign1, num1_str) 和 (sign2, num2_str)
然后:
如果 sign1 == sign2,则结果符号为sign1,结果数值 = add(num1_str, num2_str)
否则,比较num1_str和num2_str:
如果 compare(num1_str, num2_str) >= 0,则结果符号=sign1,结果数值 = sub(num1_str, num2_str)
否则,结果符号=sign2,结果数值 = sub(num2_str, num1_str)
注意:如果结果数值为"0",则结果符号设为false(避免-0)。
6. 格式化输出:
将结果数值(字符串)格式化为30位小数的形式。
如果结果字符串的长度<=30,则整数部分为"0",小数部分为结果字符串(前面补0到30位)。
否则,整数部分为前len-30位,小数部分为后30位(注意小数部分如果不足30位?不可能,因为结果数值是整数,我们只取后30位,前面整数部分可能有多位,小数部分固定30位?不对,因为两个数乘以10^30后相加,结果最多有31位整数?不对,两个30位的数相加,结果最多31位(30+30=60位?不对,我们合并后的大整数长度是整数部分长度+30,两个数的整数部分长度可能不同,但最大21,所以两个数最大长度51,相加后最多52位)。所以,当结果字符串长度大于30时,小数部分就是最后30位,整数部分是前面的部分。
但是,如果结果字符串长度小于30,比如29,那么我们需要在整数部分为0,小数部分前面补0,使得小数部分有30位。
具体:
整数部分 = 结果字符串.substr(0, len-30) [如果len>30]
小数部分 = 结果字符串.substr(len-30, 30) [如果len>30]
如果len<=30,则整数部分为"0",小数部分 = 结果字符串(此时,小数部分长度不足30,需要前面补0到30位?但是我们的结果数值是整数,它应该就是小数部分,所以我们需要在它前面补0,使得小数部分有30位。
所以,小数部分总是30位:如果结果字符串的长度>30,则小数部分取后30位;如果小于等于30,则小数部分为结果字符串(前面补0到30位)。
整数部分:如果长度>30,则取前len-30位(注意去除前导0?但是我们在计算后没有前导0,因为加法减法后都会去除前导0,所以整数部分可能有前导0?不会,因为我们在计算后得到的结果字符串是去除前导0的,所以整数部分如果有前导0,那么整个结果就是0?不对,比如0+0,结果字符串是"0",那么整数部分为0,小数部分为30个0?但是按照我们的规则,长度<=30,所以整数部分为0,小数部分为结果字符串("0")然后补0到30位?这样小数部分就是30个0?不对,我们需要输出0.000...0(30个0)?但是注意,0.000...0(30个0)就是0,所以整数部分为0,小数部分为30个0。
但是,我们要求输出30位小数,所以即使整数部分不为0,小数部分也要有30位(从结果字符串的后30位取,如果结果字符串长度大于30,那么后30位就是小数部分,前面的是整数部分)。
但是,如果结果字符串长度大于30,那么整数部分就是前len-30位,这个字符串可能包含前导0?不会,因为我们在计算后(加法和减法)都会去除前导0,所以整数部分不会有前导0(除非为0,但0的情况我们已经在结果字符串为"0"时处理了)。
所以,格式化:
如果结果数值字符串为"0",则输出"0."+30个0
否则:
if (len>30) {
integer_part = result_str.substr(0, len-30); // 整数部分
fractional_part = result_str.substr(len-30, 30); // 小数部分
} else {
integer_part = "0";
fractional_part = result_str; // 此时,result_str长度<=30,我们需要在fractional_part前面补0到30位
fractional_part = string(30 - fractional_part.length(), '0') + fractional_part;
}
然后,如果符号为负,则输出负号。
但是,注意:整数部分可能为0,小数部分不为0(例如0.1+0.2=0.3),那么结果数值字符串为"3"(因为0.3*10^30=300000000000000000000000000000,但我们的计算过程:0.1*10^30=100000000000000000000000000000,0.2*10^30=200000000000000000000000000000,相加=300000000000000000000000000000,所以结果字符串为"300000000000000000000000000000",长度=30,那么整数部分为0,小数部分为300000000000000000000000000000?不对,30位全部作为小数部分,整数部分为0,所以输出0.300000000000000000000000000000,但这样输出应该是0.300000000000000000000000000000,而题目要求30位小数,所以正确。
但是,有一个问题:如果两个数异号,相减后得到0,那么结果字符串为"0",此时整数部分为0,小数部分为30个0,输出0.000000000000000000000000000000,符号为正。
因此,我们按照上述步骤实现。
但是,注意:在解析时,如果输入是整数(如123),那么整数部分为"123",小数部分为30个0,合并后为"123000000000000000000000000000000",去除前导0后还是它(长度33位)。然后与另一个数运算。
另外,输入可能是负数,如-123.45,解析后符号为true,整数部分为"123",小数部分为"45"补28个0,合并后为"12345000000000000000000000000000",去除前导0后不变(长度32位)。
实现大整数加法和减法(非负整数):
大整数加法(字符串形式):
string add(string a, string b) {
int i = a.size()-1, j = b.size()-1;
string res;
int carry = 0;
while (i>=0 || j>=0 || carry) {
int digitA = i>=0 ? a[i--]-'0' : 0;
int digitB = j>=0 ? b[j--]-'0' : 0;
int sum = digitA + digitB + carry;
carry = sum/10;
res.push_back('0' + (sum%10));
}
reverse(res.begin(), res.end());
// 去除前导0(但注意如果全0,则保留一个0)
if (res.empty()) res = "0";
else {
// 去除前导0
size_t pos = res.find_first_not_of('0');
if (pos == string::npos) res = "0";
else if (pos>0) res = res.substr(pos);
}
return res;
}
大整数减法(a>=b):
string sub(string a, string b) {
// 已知a>=b
int i = a.size()-1, j = b.size()-1;
string res;
int borrow = 0;
while (i>=0) {
int digitA = a[i--]-'0' - borrow;
int digitB = j>=0 ? b[j--]-'0' : 0;
borrow = 0;
if (digitA < digitB) {
borrow = 1;
digitA += 10;
}
res.push_back('0' + (digitA - digitB));
}
reverse(res.begin(), res.end());
// 去除前导0
size_t pos = res.find_first_not_of('0');
if (pos == string::npos) return "0";
return res.substr(pos);
}
大整数比较:
int compare(string a, string b) {
if (a.size() != b.size()) {
return a.size() > b.size() ? 1 : -1;
}
for (int i=0; i<a.size(); i++) {
if (a[i] != b[i]) {
return a[i] > b[i] ? 1 : -1;
}
}
return 0;
}
注意:减法中,我们假设a>=b,所以不会出现负数结果。
但是,在异号时,我们比较两个数的绝对值(即合并后的大整数字符串)的大小,然后决定用大的减小的。
最后,输出时,如果符号为负且结果不为0,则输出负号。
但是,有一个特殊情况:如果结果数值为0,则符号设为正。
所以,在得到结果后,检查结果数值字符串是否为"0",如果是,则将符号设为false。
因此,我们可以在主函数中:
string res_str;
bool res_sign;
if (sign1 == sign2) {
res_str = add(num1_str, num2_str);
res_sign = sign1;
} else {
int cmp = compare(num1_str, num2_str);
if (cmp == 0) {
res_str = "0";
res_sign = false;
} else if (cmp > 0) {
res_str = sub(num1_str, num2_str);
res_sign = sign1;
} else {
res_str = sub(num2_str, num1_str);
res_sign = sign2;
}
}
// 如果结果为0,则符号设为false
if (res_str == "0") {
res_sign = false;
}
然后格式化输出。
但是,注意:两个数异号,且相减后为0,我们已经处理。
格式化输出函数:
string format_result(string res_str) {
int len = res_str.length();
string integer_part, fractional_part;
if (len > 30) {
integer_part = res_str.substr(0, len-30);
fractional_part = res_str.substr(len-30, 30);
} else {
integer_part = "0";
// 小数部分需要补0到30位
fractional_part = res_str;
if (len < 30) {
fractional_part = string(30-len, '0') + fractional_part;
}
}
// 注意:整数部分可能为空?不会,因为len>30时,整数部分至少1位(因为len-30>=1),所以整数部分不会为空。len<=30时,整数部分为"0"。
return integer_part + '.' + fractional_part;
}
然后,如果res_sign为true,则输出负号。
但是,整数部分为0时,我们输出"0.xxx",负数则输出"-0.xxx",但前面我们已经将0的符号设为正,所以不会出现-0。
然而,整数部分不为0,小数部分30位,直接输出即可。
但是,整数部分可能有前导0吗?不会,因为我们在加法和减法后都去除了前导0。
所以,最终输出:
if (res_sign) {
cout << '-';
}
cout << format_result(res_str) << endl;
但是,注意:整数部分为0时,我们输出"0.xxx",这是正确的。
测试:
输入:0.1 0.2 -> 0.3
解析:0.1 -> num1_str = "100000000000000000000000000000" (去除前导0后,因为合并后是"0"+"100000000000000000000000000000" -> "0100000000000000000000000000000",去除前导0为"100000000000000000000000000000"(30位))
0.2 -> num2_str = "200000000000000000000000000000"
相加:res_str = add("100000000000000000000000000000", "200000000000000000000000000000") = "300000000000000000000000000000"(30位)
格式化:len=30 -> 整数部分="0",小数部分="300000000000000000000000000000" -> 输出"0.300000000000000000000000000000"
输入:1.5 -0.5
1.5 -> 符号false, num1_str = "1500000000000000000000000000000"(整数部分"1"和小数部分"5"补29个0 -> "1"+"500000000000000000000000000000" -> "1500000000000000000000000000000",去除前导0不变,长度31)
-0.5 -> 符号true, num2_str = "0500000000000000000000000000000" -> 去除前导0 -> "500000000000000000000000000000"(30位)
异号:比较"1500000000000000000000000000000"(31位)和"500000000000000000000000000000"(30位),显然31位>30位,所以用大的减小的:sub("1500000000000000000000000000000", "500000000000000000000000000000")
计算:1500000000000000000000000000000 - 500000000000000000000000000000 = 1000000000000000000000000000000(31位)
符号为false(因为第一个数的符号)
格式化:31-30=1,整数部分为"1",小数部分为"000000000000000000000000000000"(30个0) -> 输出"1.000000000000000000000000000000"
输入:12345678901234567890.12345678901234567890 0.1
第一个数:整数部分"12345678901234567890"(20位),小数部分"12345678901234567890"(20位)补10个0,合并后为20+30=50位,去除前导0不变(因为整数部分不为0) -> 50位
第二个数:0.1 -> 30位
同号相加:50位+30位,结果最多51位。
然后格式化:51-30=21位整数部分,30位小数部分。
但是,题目要求保留30位小数,所以符合。
因此,我们按照这个思路编写代码。
注意:输入可能有负号,也可能没有负号。
但是,输入格式:一行输入两个实数,用空格隔开。
我们使用cin>>s1>>s2。
下面给出完整代码:
注意:大整数加法和减法函数中,我们假设输入的字符串没有前导0(除了"0"),但我们在解析后已经去除了前导0,所以输入都是非负整数的字符串,且没有前导0(除非是"0")。
但是,减法函数中,我们假设a>=b,所以不会出现负数。但是,我们在调用前已经通过比较确保a>=b。
代码较长,但我们将每个函数分开。
注意:在减法函数中,我们是从低位开始减,所以最后需要反转字符串,并去除前导0。
另外,在解析函数中,如果输入是负数,我们处理了负号,然后剩下的字符串不能有负号。
解析函数中,小数部分补0到30位:小数部分可能为空,比如整数,那么小数部分就是30个0。
代码实现:
由于题目要求保留30位小数,且输入小数部分不超过20位,所以补0到30位是安全的。
但是,注意:输入可能没有小数点,比如整数。
我们开始编写代码:
注意:在解析函数中,如果整数部分和小数部分合并后,去除前导0时,如果整个字符串全0,则设为"0"。
例如,输入0.0,解析后整数部分"0",小数部分30个0,合并后为"0"+"000...0"(30个0)->"0000...0"(31个0),去除前导0后为"0"。
因此,解析函数:
我们将编写一个函数:pair<bool, string> parse(string s)
代码如下:</think>以下是用 C++ 实现的高精度小数加法解决方案,满足题目要求(输入两个实数,输出它们的和并保留 30 位小数):
```cpp
#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>
using namespace std;
// 比较两个无符号数字字符串大小
int compare(string a, string b) {
if (a.length() != b.length())
return a.length() > b.length() ? 1 : -1;
return a.compare(b);
}
// 无符号大数加法
string add(string a, string b) {
int carry = 0;
string res;
int i = a.size() - 1, j = b.size() - 1;
while (i >= 0 || j >= 0 || carry) {
int x = (i >= 0) ? a[i--] - '0' : 0;
int y = (j >= 0) ? b[j--] - '0' : 0;
int sum = x + y + carry;
carry = sum / 10;
res.push_back('0' + (sum % 10));
}
reverse(res.begin(), res.end());
return res;
}
// 无符号大数减法 (a >= b)
string sub(string a, string b) {
int borrow = 0;
string res;
int i = a.size() - 1, j = b.size() - 1;
while (i >= 0) {
int x = (a[i] - '0') - borrow;
int y = (j >= 0) ? (b[j] - '0') : 0;
borrow = 0;
if (x < y) {
borrow = 1;
x += 10;
}
res.push_back('0' + (x - y));
i--;
if (j >= 0) j--;
}
reverse(res.begin(), res.end());
// 移除前导零
size_t start = res.find_first_not_of('0');
return (start == string::npos) ? "0" : res.substr(start);
}
// 解析数字:返回符号和标准化的整数+小数部分
pair<bool, pair<string, string>> parseNumber(string s) {
// 处理符号
bool negative = false;
if (!s.empty() && s[0] == '-') {
negative = true;
s = s.substr(1);
}
// 处理整数和小数部分
size_t dotPos = s.find('.');
string intPart, fracPart;
if (dotPos != string::npos) {
intPart = s.substr(0, dotPos);
fracPart = s.substr(dotPos + 1);
} else {
intPart = s;
fracPart = "";
}
// 处理空整数部分
if (intPart.empty()) intPart = "0";
// 移除整数部分前导零
if (intPart != "0") {
size_t start = intPart.find_first_not_of('0');
if (start != string::npos) intPart = intPart.substr(start);
else intPart = "0";
}
// 小数部分补零到30位
if (fracPart.length() < 30)
fracPart += string(30 - fracPart.length(), '0');
else
fracPart = fracPart.substr(0, 30); // 截断多余小数位
return {negative, {intPart, fracPart}};
}
// 带符号的高精度加法
pair<bool, string> signedAdd(string a, bool negA, string b, bool negB) {
// 同号情况
if (negA == negB) {
return {negA, add(a, b)};
}
// 异号情况
int cmp = compare(a, b);
if (cmp == 0) {
return {false, "0"}; // 避免 -0
} else if (cmp > 0) {
return {negA, sub(a, b)};
} else {
return {negB, sub(b, a)};
}
}
int main() {
string s1, s2;
cin >> s1 >> s2;
// 解析两个数字
auto [neg1, parts1] = parseNumber(s1);
auto [neg2, parts2] = parseNumber(s2);
string int1 = parts1.first, frac1 = parts1.second;
string int2 = parts2.first, frac2 = parts2.second;
// 拼接整数和小数部分
string num1 = int1 + frac1;
string num2 = int2 + frac2;
// 执行带符号的高精度加法
auto [negative, result] = signedAdd(num1, neg1, num2, neg2);
// 处理结果为0的特殊情况
if (result == "0") negative = false;
// 分割整数和小数部分
string integer, fractional;
if (result.length() > 30) {
integer = result.substr(0, result.length() - 30);
fractional = result.substr(result.length() - 30);
} else {
integer = "0";
fractional = result;
// 小数部分不足30位时补零
if (fractional.length() < 30) {
fractional = string(30 - fractional.length(), '0') + fractional;
}
}
// 移除整数部分前导零
if (integer != "0") {
size_t start = integer.find_first_not_of('0');
if (start != string::npos) integer = integer.substr(start);
else integer = "0";
}
// 输出结果
if (negative) cout << '-';
cout << integer << '.' << fractional << endl;
return 0;
}
```
### 代码解释
#### 1. 核心函数说明
- **`compare()`**: 比较两个无符号数字字符串的大小
- **`add()`**: 实现无符号大数加法
- **`sub()`**: 实现无符号大数减法(要求 a ≥ b)
- **`parseNumber()`**: 解析输入字符串,分离符号、整数部分和小数部分
- **`signedAdd()`**: 处理带符号的高精度加法
#### 2. 算法流程
1. **输入解析**:
- 处理符号(正负号)
- 分离整数和小数部分
- 整数部分去除前导零
- 小数部分补零到30位
2. **数值处理**:
- 将整数和小数部分拼接为完整数字
- 根据符号执行高精度加法或减法
- 处理进位和借位
3. **结果格式化**:
- 分离结果的整数和小数部分
- 整数部分去除前导零
- 小数部分补零到30位
- 处理结果为0的特殊情况
- 按格式输出带符号的结果
#### 3. 关键处理技术
- **精度保障**:使用字符串存储数字,避免浮点数精度损失
- **符号处理**:正确处理正负数组合情况
- **边界情况**:
- 整数部分为0(如0.1+0.2)
- 结果为0(避免输出-0)
- 大数运算(支持10²⁰范围)
- **小数对齐**:补零确保所有操作数有30位小数
#### 4. 测试用例
```plaintext
输入: "1" "1" 输出: "2.000000000000000000000000000000"
输入: "0.1" "0.2" 输出: "0.300000000000000000000000000000"
输入: "-1.5" "2.5" 输出: "1.000000000000000000000000000000"
输入: "12345678901234567890" "0.12345678901234567890"
输出: "12345678901234567890.123456789012345678900000000000"
输入: "99999999999999999999.99999999999999999999" "0.00000000000000000001"
输出: "100000000000000000000.000000000000000000000000000000"
```
#### 5. 复杂度分析
- **时间复杂度**:O(n),n为数字最大位数(约50位)
- **空间复杂度**:O(1),使用固定大小的字符串存储
这种方法通过字符串操作精确处理大数和小数运算,完全满足题目要求的数据范围和精度要求。
---
阅读全文
相关推荐




















