【问题标题】:Karatsuba Integer Multiplication failing with segmentation faultKaratsuba 整数乘法因分段错误而失败
【发布时间】:2017-09-10 10:10:00
【问题描述】:

当我运行程序时,它因分段错误而崩溃。此外,当我在 codeblocks IDE 中调试代码时,我也无法调试它。甚至在调试开始之前程序就崩溃了。我无法理解这个问题。任何帮助,将不胜感激。谢谢!!

#include <iostream>
#include <math.h>
#include <string>
using namespace std;

// Method to make strings of equal length
int makeEqualLength(string& fnum,string& snum){
    int l1 = fnum.length();
    int l2 = snum.length();
    if(l1>l2){
        int d = l1-l2;
        while(d>0){
            snum  = '0' + snum;
            d--;
        }
        return l1;
    }
    else if(l2>l1){
        int d = l2-l1;
        while(d>0){
            fnum  = '0' + fnum;
            d--;
        }
        return l2;
    }
    else
        return l1;
}

int singleDigitMultiplication(string& fnum,string& snum){
    return ((fnum[0] -'0')*(snum[0] -'0'));
}

string addStrings(string& s1,string& s2){
  int length = makeEqualLength(s1,s2);
  int carry = 0;
  string result;
  for(int i=length-1;i>=0;i--){
    int fd = s1[i]-'0';
    int sd = s2[i]-'0';
    int sum = (fd+sd+carry)%10+'0';
    carry = (fd+sd+carry)/10;
    result = (char)sum + result;
  }
  result = (char)carry + result;
  return result;
}

long int multiplyByKaratsubaMethod(string fnum,string snum){

    int length = makeEqualLength(fnum,snum);
    if(length==0) return 0;
    if(length==1) return singleDigitMultiplication(fnum,snum);

    int fh = length/2;
    int sh = length - fh;

    string Xl = fnum.substr(0,fh);
    string Xr = fnum.substr(fh,sh);
    string Yl = snum.substr(0,fh);
    string Yr = snum.substr(fh,sh);

    long int P1 = multiplyByKaratsubaMethod(Xl,Yl);
    long int P3 = multiplyByKaratsubaMethod(Xr,Yr);
    long int P2 = multiplyByKaratsubaMethod(addStrings(Xl,Xr),addStrings(Yl,Yr)) - P1-P3;

    return (P1*pow(10,length) + P2*pow(10,length/2) + P3);
}


int main()
{
    string firstNum = "62";
    string secondNum = "465";
    long int result = multiplyByKaratsubaMethod(firstNum,secondNum);
    cout << result << endl;
    return 0;
}

【问题讨论】:

  • 至少通过在函数开头添加cerr &lt;&lt; "(" &lt;&lt; fnum &lt;&lt; "," &lt;&lt; snum &lt;&lt; ")" &lt;&lt; endl;来观察调用,您会看到问题(在某些时候无限递归)。
  • @RK21 我真的不相信在输入main() 之前程序会崩溃。我也看不到任何可能对此感到怀疑的代码,我在 Visual Studio 中调试它时也无法验证这一点。请考虑 Jean-Baptiste Yunès 的建议和/或一步调试您的代码。顺便提一句。由于调试,我在addStrings() 中发现了一个严重的问题。修复此问题后,我注意到堆栈溢出。这意味着,递归的终止不起作用(或者至少递归消耗了太多的堆栈)。我只是在寻找这个的原因......
  • @Scheff...你是对的。程序在调用 main 之前不会崩溃。它为 P1 和 P2 生成一些值,并在一段时间后以分段错误终止。
  • @Scheff,您能否建议您在 addStrings 方法中所做的代码更改

标签: c++ karatsuba


【解决方案1】:

您的代码中存在三个严重问题:

  1. result = (char)carry + result; 不起作用。
    进位的值介于 0 (0 * 0) 和 8 (9 * 9) 之间。必须将其转换为对应的 ASCII 值:
    result = (char)(carry + '0') + result;

  2. 这导致了下一个问题:如果是0,则即使插入进位。缺少if 语句:
    if (carry/* != 0*/) result = (char)(carry + '0') + result;

  3. 修复前两个问题并再次测试后,仍然出现堆栈溢出。因此,我将您的算法与我在 google 找到的另一个算法进行了比较:
    Divide and Conquer | Set 4 (Karatsuba algorithm for fast multiplication)
    (可能是您的起源,因为它看起来非常相似)。在没有深入挖掘的情况下,我修复了一个看似简单的传输错误:
    return P1 * pow(10, 2 * sh) + P2 * pow(10, sh) + P3;
    (我将length 替换为2 * sh,将length/2 替换为sh,就像我在谷歌搜索代码中看到的那样。 ) 这对我来说很明显,在调试器中看到长度可以有奇数值,因此 shlength/2 是不同的值。

之后,您的程序开始运行。

我更改了main() 函数以更难测试:

#include <cmath>
#include <iostream>
#include <string>

using namespace std;

string intToStr(int i)
{
  string text;
  do {
    text.insert(0, 1, i % 10 + '0');
    i /= 10;
  } while (i);
  return text;
}

// Method to make strings of equal length
int makeEqualLength(string &fnum, string &snum)
{
  int l1 = (int)fnum.length();
  int l2 = (int)snum.length();
  return l1 < l2
    ? (fnum.insert(0, l2 - l1, '0'), l2)
    : (snum.insert(0, l1 - l2, '0'), l1);
}

int singleDigitMultiplication(const string& fnum, const string& snum)
{
  return ((fnum[0] - '0') * (snum[0] - '0'));
}

string addStrings(string& s1, string& s2)
{
  int length = makeEqualLength(s1, s2);
  int carry = 0;
  string result;
  for (int i = length - 1; i >= 0; --i) {
    int fd = s1[i] - '0';
    int sd = s2[i] - '0';
    int sum = (fd + sd + carry) % 10 + '0';
    carry = (fd + sd + carry) / 10;
    result.insert(0, 1, (char)sum);
  }
  if (carry) result.insert(0, 1, (char)(carry + '0'));
  return result;
}

long int multiplyByKaratsubaMethod(string fnum, string snum)
{
  int length = makeEqualLength(fnum, snum);
  if (length == 0) return 0;
  if (length == 1) return singleDigitMultiplication(fnum, snum);

  int fh = length / 2;
  int sh = length - fh;

  string Xl = fnum.substr(0, fh);
  string Xr = fnum.substr(fh, sh);
  string Yl = snum.substr(0, fh);
  string Yr = snum.substr(fh, sh);

  long int P1 = multiplyByKaratsubaMethod(Xl, Yl);
  long int P3 = multiplyByKaratsubaMethod(Xr, Yr);
  long int P2
    = multiplyByKaratsubaMethod(addStrings(Xl, Xr), addStrings(Yl, Yr))
    - P1 - P3;
  return P1 * pow(10, 2 * sh) + P2 * pow(10, sh) + P3;
}

int main()
{
  int nErrors = 0;
  for (int i = 0; i < 1000; i += 3) {
    for (int j = 0; j < 1000; j += 3) {
      long int result
        = multiplyByKaratsubaMethod(intToStr(i), intToStr(j));
      bool ok = result == i * j;
      cout << i << " * " << j << " = " << result
        << (ok ? " OK." : " ERROR!") << endl;
      nErrors += !ok;
    }
  }
  cout << nErrors << " error(s)." << endl;
  return 0;
}

关于我所做更改的说明:

  1. 关于std 库:请不要将标头与“.h”混为一谈。 std 库的每个标题都以“非后缀风格”提供。 (带有“.h”的标头要么是 C 标头,要么是老式的。) C 库的标头已适应 C++。它们的旧名称带有前缀“c”,没有后缀“.h”。
    因此,我将#include &lt;math.h&gt; 替换为#include &lt;cmath&gt;

  2. 我忍不住要把makeEqualLength() 缩短一点。

  3. 请注意,std 中的许多方法使用std::size_t 而不是intunsignedstd::size_t 具有适当的宽度来进行数组下标和指针运算,即它具有“机器字宽”。我一直相信intunsigned 也应该有“机器字宽”,而不关心size_t。当我们在 Visual Studio 中从 x86(32 位)更改为 x64(64 位)时,我学到了一个非常错误的方法:std::size_t 现在是 64 位,但 intunsigned 仍然是 32 位. (MS VC++ 也不例外。其他编译器供应商(但不是全部)也这样做。)
    我插入了一些 C 类型转换以从编译器输出中删除警告。此类用于消除警告的强制转换(无论您使用 C 强制转换还是更好的 C++ 强制转换)应始终谨慎使用,并且应理解为确认:亲爱的编译器。我知道您有顾虑,但我(相信)知道并向您保证它应该可以正常工作。

  4. 我不确定您是否打算在某些地方使用 long int。 (很可能,您从原始源中转移了此代码而不关心。)您肯定知道,所有int 类型的实际大小可能会有所不同,以匹配目标平台的最佳性能。我正在使用 Visual Studio 在装有 Windows 10 的 Intel-PC 上工作。 sizeof (int) == sizeof (long int)(32 位)。这与我编译 x86 代码(32 位)还是 x64 代码(64 位)无关。 gcc(在我的例子中是 cygwin)以及任何带有 Linux 的 Intel-PC(AFAIK)也是如此。对于比int 更大的授权类型,您必须选择long long int

我在 Windows 10(64 位)的 cygwin 中进行了示例会话:

$ g++ -std=c++11 -o karatsuba karatsuba.cc 

$ ./karatsuba
0 * 0 = 0 OK.
0 * 3 = 0 OK.
0 * 6 = 0 OK.

等等。等等

999 * 993 = 992007 OK.
999 * 996 = 995004 OK.
999 * 999 = 998001 OK.
0 error(s).

$

【讨论】:

  • 感谢您的建议。该代码有效,但您能否详细说明第三点,因为我发现很难找到连接。
  • 另外,我怎样才能使它适用于乘以 64 位数字
  • 对不起,这个要你自己开发。我会使用std::vector 来存储任意精度的整数。然后我会更改算法以处理这些std::vector 而不是std::string。字符转换将过时。在这种情况下,移动将通过简单地移动向量元素而不是pow()&lt;&lt; 来完成。您可以想象uint64 值就像您当前算法中的数字一样。考虑operator * (uint64, uint64) 必须返回uint128。可能最好先尝试uint32...
  • 您可能还会注意到:How can I multiply 64 bit operands and get 128 bit result portably?。使用uint32 可能更快,因为您可以将它们转换为uint64,只需将它们与operator * 相乘,然后将返回结果拆分为存储的结果并使用位运算符进行进位。
  • 抱歉,我没有完整阅读您的第一条评论。我只是添加了额外的解释。
猜你喜欢
  • 2016-06-13
  • 2018-08-14
  • 2018-09-01
  • 2023-03-10
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多