zoukankan      html  css  js  c++  java
  • BUG 记录:移位运算与扩展欧几里得算法

    BUG 记录:移位运算与扩展欧几里得算法

    起因

    上个月就开始打算用C++写一个ECC的轮子(为什么?折磨自己呗!),奈何自己水平有点差,拖到现在才算写完底层的大数运算。在实现欧几里得算法的时候,我开始纠结了...

    欧几里得算法的两种实现

    耳熟能详的实现方案

    这个实现只要了解过欧几里得算法的同学都很清楚,我把维基百科上的代码粘贴到这里,最开始我也是按照这样的方式写出来的代码,没过几个测试,bug就出来了。

    def ext_euclid(a, b):
        old_s,s=1,0
        old_t,t=0,1
        old_r,r=a,b
        if b == 0:
            return 1, 0, a
        else:
            while(r!=0):
                q=old_r//r
                old_r,r=r,old_r-q*r
                old_s,s=s,old_s-q*s
                old_t,t=t,old_t-q*t
        return old_s, old_t, old_r
    

    乍一看,这个实现真是天衣无缝,毫无破绽,但是要是实现大数运算的时候,如果要是这么想的话,就太naive了。

    矩阵计算欧几里得算法

    原理我就不抄了,刚学会的,链接在此

    https://www.wikiwand.com/zh/輾轉相除法#/矩阵法

    这两个方法看起来其实是等价的,第二个方法无非就是拿矩阵表示了一下,有同学会问了,这能有啥区别?

    书本上的方法的问题所在

    其实最开始的时候,我在我的大数系统实现的时候,我就意识到了有可能会有这样的问题:

    注意上面代码中循环中的操作,减法?乘法?q*sq*t其实在ab很大的时候有溢出的风险。

    注意到这个问题之后,我用python实现了一下,果然!这个操作会溢出!又用python实现了一个matrix,这回没有这个问题。(我把代码放在最后)

    结论

    仔细看看这两种方法,第二种方法其实是一种推迟了减法的做法。矩阵上的元素始终在增长,且不会超过ab的最高位,代价就是多了两个需要存储的元素。

    移位运算坑死我这样的小白

    我修改了上面的代码,又测试了一下,还是不对,我慌了,最终使用中间量对比大法,一步一步去对比,人肉检查,我发现了问题所在。

    实现gcd免不了带余除法(欧几里得除法)的操作,对于除法,我本来还想优化优化,但是奈何水平有点捉急,最后写了个textbook division(列等式减法那种),里面的当然有移位运算了,就是这个移位运算搞我。

    void NB::rshift(int bitnum){
        /* calculate block num and remain num */
        int blocknum = bitnum/64;
        int remainnum = bitnum%64;
        WORD tmp1 = 0;
        WORD tmp2 = 0;
        /* first step , move the blocknum */
        for(int i=0;i<MAX_number_word;i++){
            if(i-blocknum>=0){
                this->val[i-blocknum] = this->val[i];
            }
        }
        for(int i=MAX_number_word-1;i>=MAX_number_word-blocknum;i--){
            this->val[i] = 0;
        }
        /* second step , move the remainnum */
        for(int i=MAX_number_word-1;i>=0;i--){
            tmp1 = this->val[i]<<(WORDSIZE-remainnum);
            this->val[i] = this->val[i]>>remainnum;
            this->val[i] += tmp2;
            tmp2 = tmp1;
        }
    }
    

    毫无破绽的代码(划掉,并不是)。注意第二步写移位运算的时候有一句:

    tmp1 = this->val[i]<<(WORDSIZE-remainnum);

    remainnumd=0,就会出现移位WORDSIZE的情况,聪明的小伙伴都知道,这种情况在C++标准里是没有定义的,即

    移位运算的右操作符的大小应该大于等于0小于左操作符的最大位数,其余情况是未定义的。

    所以不太聪明但是有效的做法->

    /* second step , move the remainnum */
    for(int i=MAX_number_word-1;i>=0;i--){
        if(remainnum==0){
            tmp1 = 0;
        }else{
            tmp1 = this->val[i]<<(WORDSIZE-remainnum);
        }
        this->val[i] = this->val[i]>>remainnum;
        this->val[i] += tmp2;
        tmp2 = tmp1;
    }
    

    结束

    表达了对眼前有码,心中无码的境界的期待。

  • 相关阅读:
    AGC/ARC
    日常训练
    SQL经典问题四表查询(教师,学生,成绩,课程表)---MySQL版
    15个最佳的 JavaScript 表单验证库
    推荐6个国内技术大牛博客,全栈工程师修行的秘籍!(建议收藏)
    java-linux安装和配置
    vue学习笔记
    JVM学习笔记
    SpringMVC学习笔记
    Mybatis学习笔记
  • 原文地址:https://www.cnblogs.com/zhuowangy2k/p/12179579.html
Copyright © 2011-2022 走看看