大数计算:e^1000/300!

文章讨论了大数计算中可能遇到的数据类型限制问题,提出使用科学计数法解决,并给出了一个基于模板的BigNumeric类实现,包括乘法、除法、加法、减法、开方和幂运算的代码示例。

1.问题:大数计算可能超出数据类型范围

当单独计算 e^{1000},因为 e^{1000} > e^{700} \approx 1.01432e304,double的最大取值为1.79769e+308,所以 e^{1000} 肯定超过了double的表示范围。

同样,对于300!也是如此。

那我们应该怎么去计算和存储结果呢?

2.解决方案

从数学角度出发,对于超级大的数,运算方式、运算规律等肯定保持不变的。

很多时候,我们主要是利用相关的定理、公式进行简化或者极限处理。

由于我项目里的精度要求不是很高,于是,可以采用相对宽松的方式解决这个问题:

科学计数法!

3.代码实现

/*
* @file     BigNumeric.hpp
* @brief    模板类,用于大数计算,以科学计数法为实现形式,尾数部分有效位数12,指数部分完全准确.
* 直接在工程中引入该hpp文件即可使用该类,重载了+-/*以及幂运算和开方等.
* 由于指数部分采用的市 int ,故限制了本类使用时科学计数法的最大表示范围为:
*                       a*10^2147483647 (-1.0<=a<=1.0)         
**
* @author   DongFengChuiLiu
* @email    308021782@qq.com 
* @version  1.0 
* @date     2023/10/15
*/


#ifndef __BigNumeric_h
#define __BigNumeric_h
#include <cmath>
#include <limits>


template<typename _T>
class BigNumeric
{
private:
    _T mantissa;        //尾数
    int exponent;       //指数

public:
    BigNumeric(_T num)
    {
        if (num == (_T)0)
        {
            exponent = 0;
            mantissa = 0;
        }
        else
        {
            exponent = std::floor(std::log10(std::abs(num)));
            mantissa = num / std::pow(10.0, exponent);
        }
    }

    BigNumeric(_T mnt,int exp)
    {
        this->mantissa = mnt;
        this->exponent = exp;
    }

    ~BigNumeric() {}

    BigNumeric(const BigNumeric& other)
    {
        if (this == &other)
            return;
        this->exponent = other.exponent;
        this->mantissa = other.mantissa;
    }

    BigNumeric& operator=(const BigNumeric& other)
    {
        if(this == &other)
            return *this;
        this->exponent = other.exponent;
        this->mantissa = other.mantissa;
        return *this;
    }

public:
    //取 BigNumeric 对象的实际值
    _T value()
    {
        if (this->exponent >= 308) return std::numeric_limits<_T>::infinity();
        return this->mantissa * std::pow(10.0, this->exponent);
    }


    //乘法运算
    // BigNumeric * BigNumeric
    BigNumeric operator*(const BigNumeric opr)
    {
        BigNumeric<_T> resmnt(this->mantissa * opr.mantissa);
        resmnt.exponent = CheckRange((_T)resmnt.exponent + this->exponent + opr.exponent);
        return resmnt;
    }
    // BigNumeric * _T
    BigNumeric operator*(const _T opr)
    {
        return *this * BigNumeric<_T>(opr);
    }
    //_T * BigNumeric
    friend    BigNumeric operator*(const _T opr, BigNumeric opr1)
    {
        return opr1 * opr;
    }


    //除法
    // BigNumeric / BigNumeric
    BigNumeric operator/(const BigNumeric opr)
    {
        BigNumeric<_T> resmnt(this->mantissa / opr.mantissa);
        resmnt.exponent = CheckRange((_T)resmnt.exponent + this->exponent - opr.exponent);
        return resmnt;
    }
    // BigNumeric / _T
    BigNumeric operator/(const _T opr)
    {
        return *this / BigNumeric<_T>(opr);
    }
    // _T / BigNumeric
    friend     BigNumeric operator/(const _T opr, const BigNumeric opr1)
    {
        return BigNumeric<_T>(opr) / opr1;
    }


    //加法
    // BigNumeric + BigNumeric
    BigNumeric operator+(const BigNumeric opr)
    {
        if (this->exponent - opr.exponent > 15) return *this;
        else if (this->exponent - opr.exponent < -15) return opr;
        int min = this->exponent > opr.exponent ? opr.exponent : this->exponent;
        BigNumeric<_T> resmnt(this->mantissa * std::pow(10.0, this->exponent - min) + opr.mantissa * std::pow(10.0, opr.exponent - min));
        resmnt.exponent = CheckRange((_T)resmnt.exponent + min);
        return resmnt;
    }
    // BigNumeric + _T
    BigNumeric operator+(const _T opr)
    {
        return *this + BigNumeric<_T>(opr);
    }
    // _T + BigNumeric
    friend    BigNumeric operator+(const _T opr, BigNumeric opr1)
    {
        return opr1 + opr;
    }


    //减法
    // BigNumeric - BigNumeric
    BigNumeric operator-(const BigNumeric opr)
    {
        return *this + BigNumeric<_T>(opr) * (-1.0);
    }
    // BigNumeric - _T
    BigNumeric operator-(const _T opr)
    {
        return *this - BigNumeric<_T>(opr);
    }
    // _T - BigNumeric
    friend    BigNumeric operator-(const _T opr, BigNumeric opr1)
    {
        return opr1 - opr;
    }


    //开方
    // BigNumeric^0.5
    BigNumeric Sqrt()
    {
        BigNumeric<_T> result(0.0);
        _T bgnmant = std::sqrt(this->mantissa);
        int bgnexp = this->exponent;
        if (bgnexp % 2 == 0)
        {
            result.mantissa = bgnmant;
            result.exponent = bgnexp / 2;
        }
        else
        {
            BigNumeric temp(bgnmant * std::sqrt(10.0));
            result.mantissa = temp.mantissa;
            result.exponent = temp.exponent + bgnexp / 2;
        }
        return result;
    }


    //幂
    // BigNumeric^_T
    BigNumeric Pow(_T exp)
    {
        BigNumeric temp(Vpow(this->mantissa, exp) * Vpow(10.0, this->exponent * (exp - (int)exp)));
        temp.exponent = CheckRange((_T)temp.exponent + (_T)this->exponent * exp);
        return temp;
    }

public:
    //整数阶乘 int!
    static BigNumeric Factorial(const int opr)
    {
        if (opr < 0) return 1.0 / Factorial(-1.0 * opr + 1);
        else if (opr == 0) return BigNumeric(1.0);
        return Factorial(opr - 1) * opr;
    }


    //e指数幂运算 e^_T
    static BigNumeric Epow(const _T exp)
    {
        BigNumeric res(1.0);
        double e = 2.71828182845904523536;
        if (exp <= 700) return BigNumeric(std::pow(e, exp));
        int count = exp / 700;
        BigNumeric bgn(std::pow(e, 700));
        for (size_t i = 0; i < count; i++)
            res = res * bgn;
        BigNumeric bgn1(std::pow(e, exp - count * 700));
        res = res * bgn1;
        return res;
    }
    //e^BigNumeric
    static BigNumeric Epow(const BigNumeric opr)
    {
        BigNumeric res(1.0);
        double e = 2.71828182845904523536;
        BigNumeric mnt(std::pow(e, opr.mantissa));
        int count = std::abs(opr.exponent) > 1000 ? 1000 : std::abs(opr.exponent);
        if (count == 0) return mnt;
        if(opr.exponent < 0) res = mnt.Pow(0.1);
        else res = mnt.Pow(10.0);
        for (size_t i = 0; i < count - 1; i++)
        {
            if (opr.exponent < 0) res = res.Pow(0.1);
            else res = res.Pow(10.0);
        }        
        return res;
    }


    //任意底数的幂运算 a^_T
    static BigNumeric Vpow(const _T e, const _T exp)
    {
        BigNumeric res(1.0);
        BigNumeric bgnmant(e);
        int chk = bgnmant.exponent == 0 ? exp : CheckRange(exp * bgnmant.exponent);
        if (chk <= 300) return BigNumeric(std::pow(e, exp));
        int count = exp / 300;
        BigNumeric bgn(std::pow(bgnmant.mantissa, 300));
        for (size_t i = 0; i < count; i++)
        {
            res = res * bgn;
            res.exponent = CheckRange(res.exponent + (_T)bgnmant.exponent * 300);
        }
        BigNumeric bgn1(std::pow(bgnmant.mantissa, exp - count * 300));
        res = res * bgn1;
        res.exponent = CheckRange(res.exponent + (_T)bgnmant.exponent * (exp - count * 300));
        return res;
    }

private:
    //控制指数的值范围
    static int CheckRange(const _T num)
    {
        if (num >= (_T)2147483647) return 2147483647;
        else if( num <= (_T)-2147483648) return -2147483648;
        return num;
    }
};

#endif // !__BigNumeric_h

4.测试

//测试代码
#include "BigNumeric.hpp"


int main() 
{    
    // 乘法测试
    /*BigNumeric<double> bignum(2.13e303);
    BigNumeric<double> bignum1(5e303);
    BigNumeric<double> res = bignum * bignum1;
    res = bignum * 2;
    res = 2 * bignum;
    double t = res.value();*/

    // 除法测试
    /*BigNumeric<double> bignum(2.13e303);
    BigNumeric<double> bignum1(4.26e303);  
    BigNumeric<double> res = bignum1 / bignum;
    res = 240.0 / bignum;
    res = bignum / 2.13e302;*/

    //加法测试
    /*BigNumeric<double> bignum(987654321);
    BigNumeric<double> bignum1(9);
    BigNumeric<double> res(bignum + bignum1);
    res = bignum + 9;
    res = 9 + bignum;*/


    //减法测试
    /*BigNumeric<double> bignum(987654321);
    BigNumeric<double> bignum1(9);
    BigNumeric<double> res(bignum - bignum1);
    res = bignum - 9;
    res = 987654321 - bignum;*/


    //幂测试
    /*BigNumeric<double> bignum(2.15 * 1.0e303);
    BigNumeric<double> TMP = (bignum*54321).Sqrt();
    BigNumeric<double> TMP1 = (bignum * 54321).Pow(0.5);
        
    bignum  = BigNumeric<double>(9.15);
    BigNumeric<double> TMP2 = bignum.Pow(400);
    double t = TMP2.value();*/

    //阶乘测试
    BigNumeric<double> bignum = BigNumeric<double>::Factorial(300);
    BigNumeric<double> bignum1 = BigNumeric<double>::Epow(1000);
    bignum = bignum1 / bignum;
    double t = bignum.value();

    return 0;
}

结果:6.4369310844548986e-181,尾数部分有效位数为 12,指数部分完全准确。

<think>好的,我现在需要帮用户解决用Python实现泰勒展开计算e^x的问题。首先,用户提供的公式是e^x的泰勒展开式:e^x = 1 + x + x²/2! + … + xⁿ/n!,其中n是自然数。用户希望输入实数x和自然数n,计算这个展开式的结果,并保留两位小数。 首先,我得确认泰勒展开的正确性。e^x的泰勒展开确实是在x=0处的展开,也就是麦克劳林级数,每一项的形式是x^k/k!,从k=0到n。所以用户给出的公式是对的。不过需要注意,当n=0时,结果应该是1,因为x^0/0!等于1。但根据问题描述,n是自然数,通常自然数包括从1开始,所以可能需要确认用户的需求是否需要处理n=0的情况。不过根据用户的问题,输入n是自然数,所以可能默认n≥1。但代码中最好处理n=0的情况,或者用户可能输入0?需要进一步考虑。 接下来,考虑如何实现这个计算。思路是循环从0到n,计算每一项的值,然后累加。这里需要注意阶乘计算可能会很大,但Python的整数可以处理大数,所以不用担心溢出问题,不过当n很大时,计算阶乘会消耗较多资源。不过对于用户输入的n来说,应该不会有太大的问题,因为用户输入的n可能不会太大。 然后,具体步骤分解: 1. 输入x和n:使用input函数获取用户输入,并转换为float和int类型。 2. 初始化结果变量,比如result = 0。 3. 循环从k=0到k=n,计算每一项x^k / k!,然后加到结果中。 4. 对于每一项的计算,x的k次方可以用循环累乘,或者直接用幂运算x**k。 5. 阶乘计算可以用循环从1到k相乘,或者使用math库中的factorial函数,但需要注意math.factorial在k=0时会返回1,所以没问题。 6. 累加每一项到结果。 7. 最后,将结果保留两位小数,可以用round函数或者格式化输出。 可能的优化点:阶乘和幂的计算可以分开,但每次循环可以利用前一次的结果。比如,在计算第k项时,可以利用k-1项的结果。例如,term_k = term_{k-1} * x / k。这样每次循环只需要乘以x再除以当前的k值,而不需要每次都计算x的k次方和k的阶乘,这样效率更高,尤其是当n很大时。 比如,当k=0时,term=1(因为x^0/0! = 1/1 = 1) 当k=1时,term = term_prev * x /1 当k=2时,term = term_prev * x /2 以此类推。这样可以减少重复计算,提高效率。 所以,正确的做法是使用这种迭代的方式,而不是每次计算x**k和k!,这样时间复杂度更低。 然后,代码的结构大致如下: 导入math模块中的factorial,或者自己计算阶乘。不过用迭代方法更高效,所以应该采用迭代法。 例如: x = float(input()) n = int(input()) result = 0.0 current_term = 1.0 # k=0时的项 result += current_term for k in range(1, n+1): current_term *= x / k result += current_term print(round(result, 2)) 这样,循环从1到n,每次计算current_term乘以x/k,并累加到结果。这样时间复杂度是O(n),而如果每次计算x**k和k!的话,时间复杂度会更高,因为每次计算阶乘需要O(k)时间,总时间复杂度会是O(n^2)。 所以,这种方法更高效,尤其是当n较大时。 测试一下这个代码是否正确。例如,当x=1,n=0时,可能有问题?因为用户输入的n是自然数,可能需要确保n≥1,但代码中处理n=0的情况需要循环不执行,初始的current_term=1,所以结果是1,正确。而如果用户允许n=0的话,比如输入n=0,那么循环不会执行,结果就是1.0。但根据自然数的定义,如果自然数从1开始,那么用户输入n=0时可能需要处理,但题目中用户可能保证n是自然数,所以可能不需要处理。 测试用例:x=1,n=5。泰勒展开到x^5/5!的和应该是1 +1 +0.5 +0.166666… +0.041666… +0.008333…,总和约为2.716666…,保留两位小数是2.72。而实际e的值约为2.71828,所以当n=5时得到2.716666…,正确。 另一个测试用例:x=0,不管n是多少,结果都是1.0,正确。 再比如x=2,n=10,结果应该接近e^2≈7.389,计算到n=10的泰勒展开式总和是否正确? 计算时,当k=10,项是2^10/(10!),即1024/3628800≈0.000282,总和会比实际值略低,但保留两位小数可能接近7.39?需要实际计算。 但不管怎样,代码的逻辑是正确的。 另外,保留两位小数可以用round函数,但需要注意四舍五入的问题。例如,如果计算结果是2.716666…,round到两位小数是2.72,正确。而如果是2.714,则变成2.71。 用户希望输出保留两位小数,所以用round(result, 2)是合适的,或者用格式化字符串,例如print("{:.2f}".format(result)),但两者在四舍五入时可能行为一致。例如,对于2.716,round到两位是2.72,而格式化也会显示2.72。 综上,代码的大致结构应该是: 输入x和n,初始化current_term和result,循环累加,然后输出两位小数。 现在,再考虑可能的错误处理,比如用户输入非数值的x或n,但题目中可能不需要处理,假设用户输入的是正确的实数x和自然数n。所以代码中不需要额外的错误处理。 最后,编写代码示例: 例如: x = float(input("请输入实数x:")) n = int(input("请输入自然数n:")) result = 0.0 current_term = 1.0 # k=0的项 result += current_term for k in range(1, n+1): current_term *= x / k result += current_term print("结果为:{:.2f}".format(result)) 或者更简洁的写法: x = float(input()) n = int(input()) total = 1.0 term = 1.0 for i in range(1, n+1): term *= x / i total += term print(f"{total:.2f}") 这样代码更简洁,且效率高。 现在,检查引用是否需要参考。用户提供的引用中有关于SymPy解微分方程的内容,可能与当前问题无关。但引用[2]提到数值计算中的精度问题,比如当x很小时,1 - cosx可能因为精度问题导致误差。不过在这个问题中,用户要求的是泰勒展开计算e^x,可能不需要考虑这个问题,因为展开式的每一项都是正数,累加时不会有相减导致的有效数字丢失问题。不过当x是负数且绝对值很大时,泰勒展开可能收敛较慢,需要较大的n才能准确,但用户的问题只是按照给定的n计算,不管误差如何,所以代码只需按照公式实现即可。 综上,代码正确,能够完成任务。</think>要使用泰勒展开公式 $e^x = 1 + x + \frac{x^2}{2!} + \cdots + \frac{x^n}{n!}$ 计算 $e^x$ 的值并保留两位小数,可以通过以下步骤实现: ### 实现步骤 1. **输入处理**:接收用户输入的实数 `x` 和自然数 `n`。 2. **迭代计算**:通过逐项累加的方式计算泰勒展开式的值,避免重复计算阶乘和幂次。 3. **格式化输出**:将结果保留两位小数。 ### Python 代码示例 ```python x = float(input("请输入实数x:")) n = int(input("请输入自然数n:")) result = 1.0 # 初始项为1 (k=0) current_term = 1.0 # 当前项的值 for k in range(1, n + 1): current_term *= x / k # 通过迭代计算每一项:x^k / k! = (x^{k-1}/(k-1)!) * (x/k) result += current_term print(f"计算结果为:{result:.2f}") ``` ### 代码说明 1. **迭代优化**:通过 `current_term *= x / k` 逐项计算,避免了重复计算 $x^k$ 和 $k!$,时间复杂度为 $O(n)$。 2. **数值稳定性**:直接累加正项,避免了大数相减导致的有效数字丢失问题[^2]。 3. **格式化输出**:使用 `f"{result:.2f}"` 保留两位小数。 ### 测试示例 - **输入**:`x=1`, `n=5` **输出**:`2.72`(真实值 $e \approx 2.71828$) - **输入**:`x=0`, `n=10` **输出**:`1.00`($e^0 = 1$)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东风吹柳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值