gxqcn 发表于 2008-1-24 15:22:16

我的可以计算更大的 :)

我用类似快速乘幂的算法,消除了递归,效率大幅提升::)r = 1000      耗时:0.000062 s    位数:765
r = 10000       耗时:0.001045 s    位数:7655
r = 100000      耗时:0.028692 s    位数:76555
r = 1000000   耗时:0.362074 s    位数:765551
r = 10000000    耗时:3.179194 s    位数:7655514
测试环境:Windows XP SP2,AMD Athlon 64 Processor 3200+,1GB DDR - 200MHz代码如下:#include < iostream.h >

#include < HugeCalc.h > // 公共接口
#include < HugeInt.h >// 10进制系统
#include < HugeIntX.h > // 16进制系统

#pragma message( "automatic link to .../HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
#pragma comment( lib, "HugeCalc.lib" )

int main(int argc, char* argv[])
{
    cout << "Call " << HugeCalc::GetVer() << endl;

    if ( HC_LICENSE_NONE == HugeCalc::GetLicenseLevel() )
    {
      cout << "\r\n警告:您未通过 HugeCalc.dll 的许可认证!\r\n" \
            << "\r\n解决方案可选下列方案之一:" \
            << "\r\n    一、请将本程序移动到“/CopyrightByGuoXianqiang/[../]”目录下运行;" \
            << "\r\n    二、或请在 HugeCalc.chm 中进行注册(一劳永逸)。" \
            << endl;
    }
    else
    {
      CHugeInt a, b, k;
      UINT32 i, r;

      cout << "*** ^r = sqrt(k) - sqrt(k-1) ***"<< endl;
      cout << "input r, output k ...(if r==0, then exit)" << endl;
      
      for ( ; ; )
      {
            cout << endl << "r = ";
            cin >> r;

            if ( 0 == r ) break;

            HugeCalc::EnableTimer();
            HugeCalc::ResetTimer();

            // a + b*sqrt(2)
            a = 1;
            b = -1;

            i = 1;
            while ( i <= r )
            {
                i <<= 1;
            }
            i >>= 1;

            while ( 1 != i )
            {
                k.Mul( a, b ) <<= 1;
                a.Pow(2) += ( b.Pow(2) <<= 1 );
                b.Swap(k);

                if ( 0 != ( r & ( i >>= 1 )))
                {
                  ( k = b ).Negate();
                  b -= a;
                  a.Swap( k ) -= b;
                }               
            }

            if ( 0 == ( 1 & r ))
            {
                k.Swap(a).Pow(2);
            }
            else
            {
                k.Swap(b).Pow(2) <<= 1;
            }

            HugeCalc::EnableTimer( FALSE );

#if 1   // 设“0”可关闭输出具体数值
            cout << "k = " << k.GetStr(FS_NORMAL) << endl;
#else
            cout << "k have " << k.GetDigits() << " digits!" << endl;
#endif
            cout << "computation took " << HugeCalc::GetTimerStr() << endl;
      }
    }

    cout << endl;
    system( "pause" );

    return 0;
}编译好的程序压缩包:再次证明 HugeCalc 比 GMP 更优越!(注:非 HugeCalc 注册用户也可正常运行)

mathe 发表于 2008-1-24 15:49:11

呵呵,程序运行结果输出太花时间了,
我在我的笔记本上运行
1,000,000计算时间是1.322009 s,不过输出好像花了更长的时间。
10,000,000的情况竟然也能够算下去,计算时间是16.754020s.

gxqcn 发表于 2008-1-24 15:57:21

原帖由 mathe 于 2008-1-24 15:49 发表 http://bbs.emath.ac.cn/images/common/back.gif
呵呵,程序运行结果输出太花时间了,
我在我的笔记本上运行
1,000,000计算时间是1.322009 s,不过输出好像花了更长的时间。
10,000,000的情况竟然也能够算下去,计算时间是16.754020s.

这不是我的程序吧?(在我印象中,你的笔记本速度比我的台式机速度还要好)
我的输出也会很快的。

mathe 发表于 2008-1-24 16:13:04

是你的程序。
如果不重定向,输出应该没有办法提高速度吧,比如r=10,000,000
显示的计算时间只有1.3秒左右,可是整个输出过程,我就看见窗口中白花花一片数字在向上闪去,闪了1分多钟,
你是不是运行时将输出重定向了?

gxqcn 发表于 2008-1-24 16:24:38

对照 14# 与 12# 的描述,感觉在12# 中的“r”值均少打了个“0”,
所以我才会有所疑惑。

我没有重定向输出,但由于不存在复杂的进制转换,所以 HugeCalc 输出会很高效,
如果不想输出具体数值,可把我在 11# 中源代码的“#if 1”改成“#if 0”即可。

mathe 发表于 2008-1-24 16:31:13

不是进制转换问题,只是纯粹屏幕输入输出问题。12#的确少了个0
通常我都在Linux上面写代码的,所以没有去编译你的代码,而是直接在笔记本上运行你编译后的程序。

gxqcn 发表于 2008-1-24 16:36:39

屏幕输出确实比较耗时。http://www.emath.ac.cn/image/bbs/smiley/57.gif

我重定向输出到文件测试,所耗时间也只是秒级。

gxqcn 发表于 2008-1-24 20:37:24

“终极”版本 :)

下面对该问题进行理论推导并尝试推广。

为叙述方便,记 sqrt(2)=j,
巧妙的推导,高效的算法,严谨的代码数 (a + b*j) 中 a 可称为“有理部”,b 可称为“无理部”。

则 r=0 时,(j-1)^r = (j+1)^(-r) = 1 ± 0*j   
当 r>0 时,(j-1)^r = (j+1)^(-r) = ar - br*j= sqrt( kr ) - sqrt( kr - 1 )
当 r<0 时,(j-1)^r = (j+1)^(-r) = ar + br*j = sqrt( kr ) + sqrt( kr - 1 )

楼主所要求的 k = max{ ar^2, 2*br^2 },更一般地:
当 r 为偶数时,kr = ar^2;当 r 为奇数时 kr = 2*br^2

从以上等式可看出,(j-1)^r、(j+1)^r、(j-1)^(-r)、(j+1)^(-r) 的 kr 值彼此相等。
(因为它们的“有理部”、“无理部”绝对值彼此相等)

有了这个结论就好办了,甚至可为下一步程序优化作理论指导,
因为大数同号加法更简单,所以我们尽可能采用“有理部”与“无理部”同号之情形。

(1+j)*(a + b*j) = (a+2*b) + (a+b)*j    => br+1 = ar + br, ar+1 = br+1 + br
(a + b*j)^2 = (a^2 + 2*b^2) + 2*a*b*j => a2r = ar^2 + 2*br^2, b2r = 2*ar*br
注意到 ar^2 - 2*br^2 = ±1,所以 a2r 的计算可以少算一次平方。
更好的结论则为 a2r = 2*kr - 1

于是乎,就有了下面的优化算法://////////////////////////////////////////////////////////////////////////
//
// 目的:快速计算 (sqrt(2) - 1)^r
// 设计:郭先强 ( gxqcn@163.com; HugeCalc@Gmail.com )
// 日期:2008-01-24
// 备注:运行编译好的程序,后面若带参数则输出到文件 Output.txt 中
//
// Web: http://www.emath.ac.cn/
// BBS: http://bbs.emath.ac.cn/
//
//////////////////////////////////////////////////////////////////////////

// Project -> Setting -> C/C++ -> Code Generation --> Use run-time library:
//      Win32 Debug:    Debug Multithreaded DLL
//      Win32 Release:Multithreaded DLL

#include < iostream.h >
#include < fstream.h >

#include < HugeCalc.h > // 公共接口
#include < HugeInt.h >// 10进制系统
#include < HugeIntX.h > // 16进制系统

#pragma message( "automatic link to .../HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
#pragma comment( lib, "HugeCalc.lib" )

int main(int argc, char* argv[])
{
    cout << "Call " << HugeCalc::GetVer() << endl << endl;

    if ( HC_LICENSE_NONE == HugeCalc::GetLicenseLevel() )
    {
      cout << "\r\n警告:您未通过 HugeCalc.dll 的许可认证!\r\n" \
            << "\r\n解决方案可选下列方案之一:" \
            << "\r\n    一、请将本程序移动到“/CopyrightByGuoXianqiang/[../]”目录下运行;" \
            << "\r\n    二、或请在 HugeCalc.chm 中进行注册(一劳永逸)。" \
            << endl;
    }
    else
    {
#if 1    // 用10进制内核
      CHugeInt    a, b, k;
#else    // 用16进制内核
      CHugeIntX   a, b, k;
#endif
      SINT32 r;   // r 可正可负

      BOOL bSave2File = ( argc > 1 );   // 后面若有参数则存盘

      cout << "*** ^r = sqrt(k) - sqrt(k-1) ***" << endl;
      cout << "input r, output k ...(if r==0, then exit)" << endl;

      for ( ; ; )
      {
            cout << endl << "r = ";
            cin >> r;

            if ( 0 == r )
            {
                cout << "k = 1" << endl;
                break;
            }

            if ( r < 0 ) r = -r;

            HugeCalc::EnableTimer();
            HugeCalc::ResetTimer();

            // a + b*sqrt(2)
            a = 1;
            b = 1;
            k = 2;

            UINT32 i = 1UL << 31;
            while ( i > (UINT32)r )
            {
                i >>= 1;
            }

            while ( 0 != ( r & ( i - 1 )))
            {
                ( b *= a ) <<= 1;
                --( a.Swap( k ) <<= 1 );

                if ( 0 != ( r & ( i >>= 1 )))
                {
                  k = b;
                  b += a;
                  a.Swap( k ) += b;

                  ( k = b ).Pow( 2 ) <<= 1;
                }
                else
                {
                  ( k = a ).Pow( 2 );
                }
            }

            // 变量 a,b 不再使用,清空其所占资源
            a = b = 0;

            while ( 1 != i )
            {
                ( --( k <<= 1 )).Pow( 2 );
                i >>= 1;
            }

            HugeCalc::EnableTimer( FALSE );

            if ( bSave2File )
            {
                ofstream outputFile( "Output.txt", ios::ate );
                if ( outputFile )
                {
                  outputFile << endl;
                  outputFile << "r = " << r << endl;
                  outputFile << "k = " << k.GetStr( FS_NORMAL ) << endl;
                  outputFile << "*** k have " << k.GetDigits() << " digits! ***" << endl;
                  outputFile << "computation took " << HugeCalc::GetTimerStr() << endl;
                  outputFile.close();
                }
                else
                {
                  cerr << "File could not be opened" << endl;
                  bSave2File = FALSE;
                }
            }

            if ( !bSave2File )
            {
                cout << "k = " << k.GetStr( FS_NORMAL ) << endl;
            }

            cout << "*** k have " << k.GetDigits() << " digits! ***" << endl;
            cout << "computation took " << HugeCalc::GetTimerStr() << endl;
      }
    }

    cout << endl;
    system( "pause" );

    return 0;
}编译好的程序压缩包:(无论注册 HugeCalc 与否均可正常运行;可存盘输出;内含源代码)

gxqcn 发表于 2008-1-25 07:58:42

“终极版”的测试报告

调用 HugeCalc 的 10 进制内核版本:
r = 1000      耗时:0.000048 s    位数:765
r = 10000       耗时:0.000632 s    位数:7655
r = 100000      耗时:0.012068 s    位数:76555
r = 1000000   耗时:0.236285 s    位数:765551
r = 10000000    耗时:1.586116 s    位数:7655514
r = 100000000   耗时:19.850545 s   位数:76555137

调用 HugeCalc 的 16 进制内核版本:
r = 1000      耗时:0.000067 s    位数:636
r = 10000       耗时:0.000379 s    位数:6358
r = 100000      耗时:0.021730 s    位数:63578
r = 1000000   耗时:0.227757 s    位数:635777
r = 10000000    耗时:1.378083 s    位数:6357767
r = 100000000   耗时:19.163445 s   位数:63577665

测试环境:Windows XP SP2,AMD Athlon 64 Processor 3200+,1GB DDR - 200MHz

mathe 发表于 2008-1-25 08:18:38

对于特殊情况应该还有优化机会,不过机会不是很大了
现在对于(a+bj)做一次平方,需要对系数做一次平方,一次乘法,两次移位。(一次简单减法可以忽略)
而(a+bj)乘上1+j或(1-j)需要两次加减法运算。
不知道现在通常情况现在乘法需要时间是加法的多少倍(当然同数据大小相关),如果我们对于这个关系有一个比较的表达式,那么应该还有少数优化机会。
比如如果计算(j-1)^1023,有可能写成(j-1)^1024*(j+1)还更加快一些。
页: 1 [2] 3
查看完整版本: 快速求(sqrt(2)-1)的幂展开形式