补码的原理及其应用

同余

在介绍补码之前先引入同余的概念,因为补码的原理利用了同余的性质。
同余是数论中最重要的基础概念之一,由德国数学家高斯在其1801年出版的«算术探索»中系统地进行了研究,书中所创造的同余符号""也沿用至今。那么什么是同余呢?

【定义】给定正整数m,若有整数a、b,使得m|(ab),则称a与b关于模m同余,记作ab(modm).

例如对于m=8而言,1和9就是同余的,同理可知-7和1是同余的,9和17是同余的等等。由同余性质的等价关系可得到一个同余类

71917(mod8)

【推论】对于特定的正整数m,将有m个同余类.一般用0,1,,m1来唯一标识这m个同余类.

前面说补码利用了同余的性质,其实更准确的说法应该是补码利用了同余类之间的运算性质

【性质一】若ab(modm)xy(modm),那么a+xb+y(modm)
【性质二】若ab(modm)xy(modm),那么axby(modm)

补码

在计算机世界中,任何数据都是一段二进制编码,是程序根据执行上下文来决定当前这段二进制编码具体表达了什么含义
对于n位二进制编码而言,很自然地就可以表示0到2n1的数字。起初,我们可以很轻易地对这些数字进行加法或乘法运算,得到的结果是模2n的余数。
为了让负数也能参与运算,补码定义最高位的bit为符号位,表示该位原本的值的相反数即2n1,为什么这样定义呢,因为这样得到的负数和原本的正数刚好相差了2×2n1。看到这里是不是很眼熟,那就是负数和对应的正数模2n是同余的,也就是说补码没有用0,1,,2n1来唯一标识同余类,而是用2n1,,0,,2n11来唯一标识同余类。这里演示当n=3时,各个二进制数所代表的数:
补码数轴表示
当要计算4+1的时候,首先会将-4转换为模8的同余类4,再计算4+1模8的值得到5,再将5“解读”为对应的同余类-3。最终就得到了正确的结果。

用补码实现Int128

先实现了简单的加法,减法,乘法。除法麻烦一点,等以后有机会了再写吧。

//Int128.h
#pragma once

#include <string>
#include <cstdint>              //int64_t uint64_t

namespace rc {

class Int128 final {
public:
    //提供默认构造函数,构造局部变量的时候为随机值
    Int128() = default;

    //没有explicit,允许整型隐式转换为Int128类型
    template<typename IntegralType>
    Int128(IntegralType t_num) noexcept : m_high64(t_num < IntegralType(0) ? -1 : 0), m_low64(t_num) {}

    //加法,相当于+=
    Int128 &add(const Int128 &rhs) noexcept;

    //减法,相当于-=
    Int128 &sub(const Int128 &rhs) noexcept;

    //乘法,相当于*=
    Int128 &mul(const Int128 &rhs) noexcept;

    //求补(求相反数)
    Int128 &neg() noexcept;

    //相等
    bool equal(const Int128 &rhs) const noexcept;

    //大于
    bool greater(const Int128 &rhs) const noexcept;

    //小于
    bool lesser(const Int128 &rhs) const noexcept;

    //大于等于
    bool greaterOrEqual(const Int128 &rhs) const noexcept;

    //小于等于
    bool lesserOrEqual(const Int128 &rhs) const noexcept;

    //取反(not作为函数名会报错)
    Int128 &bitnot() noexcept;

    //获取在内存中的16进制表示
    std::string toHex() const;

private:
    //计算64位整型的完整的128位积
    static Int128 mul64(uint64_t x, uint64_t y) noexcept;

private:
    int64_t m_high64;
    uint64_t m_low64;
};

}
//Int128.cpp
#include "Int128.h"
#include <sstream>              //ostringstream
#include <iomanip>              //setw setfill

using namespace rc;

//加法,相当于+=
Int128 &Int128::add(const Int128 &rhs) noexcept {
    //当低64位加法溢出将产生进位1
    m_low64 += rhs.m_low64;
    m_high64 += (rhs.m_high64 + (m_low64 < rhs.m_low64 ? 1 : 0));

    //返回自身引用
    return *this;
}

//减法,相当于-=
Int128 &Int128::sub(const Int128 &rhs) noexcept {
    add(Int128(rhs).neg());

    //返回自身引用
    return *this;
}

//乘法,相当于*=
Int128 &Int128::mul(const Int128 &rhs) noexcept {
    auto high64 = m_high64 * rhs.m_low64 + m_low64 * rhs.m_high64;
    *this = mul64(m_low64, rhs.m_low64);
    m_high64 += high64;

    //返回自身引用
    return *this;
}

//求补(求相反数)
Int128 &Int128::neg() noexcept {
    //取反加1
    bitnot();
    add(1);

    //返回自身引用
    return *this;
}

//相等
bool Int128::equal(const Int128 &rhs) const noexcept {
    return m_high64 == rhs.m_high64 && m_low64 == rhs.m_low64;
}

//大于
bool Int128::greater(const Int128 &rhs) const noexcept {
    return m_high64 > rhs.m_high64 ? true : (m_high64 == rhs.m_high64 ? (m_low64 > rhs.m_low64 ? true : false) : false);
}

//小于
bool Int128::lesser(const Int128 &rhs) const noexcept {
    return m_high64 < rhs.m_high64 ? true : (m_high64 == rhs.m_high64 ? (m_low64 < rhs.m_low64 ? true : false) : false);
}

//大于等于
bool Int128::greaterOrEqual(const Int128 &rhs) const noexcept {
    return !lesser(rhs);
}

//小于等于
bool Int128::lesserOrEqual(const Int128 &rhs) const noexcept {
    return !greater(rhs);
}

//取反(not作为函数名会报错)
Int128 &Int128::bitnot() noexcept {
    m_high64 = ~m_high64;
    m_low64 = ~m_low64;

    //返回自身引用
    return *this;
}

//获取在内存中的16进制表示
std::string Int128::toHex() const {
    std::ostringstream os;

    //setw设置域宽,使用一次就得设置一次
    os << "0x"
        << std::hex << std::setfill('0')
        << std::setw(16) << m_high64
        << std::setw(16) << m_low64;

    return os.str();
}

//计算64位整型的完整的128位积
Int128 Int128::mul64(uint64_t x, uint64_t y) noexcept {
    //将x,y分解为32位整型
    uint32_t xHigh32 = x >> 32;
    uint32_t xLow32 = x;
    uint32_t yHigh32 = y >> 32;
    uint32_t yLow32 = y;

    //计算各个位置的积
    uint64_t low64 = static_cast<uint64_t>(xLow32) * yLow32;
    uint64_t mid64a = static_cast<uint64_t>(xHigh32) * yLow32;
    uint64_t mid64b = static_cast<uint64_t>(xLow32) * yHigh32;
    uint64_t high64 = static_cast<uint64_t>(xHigh32) * yHigh32;

    //计算最终的128位积
    Int128 prod;
    prod.m_low64 = low64 + (mid64a << 32);
    prod.m_high64 = high64 + (mid64a >> 32) + (mid64b >> 32) + (prod.m_low64 < low64 ? 1 : 0);

    //将最后剩下的mid64b的低32位数加进来
    uint64_t mid32b = mid64b << 32;
    prod.m_low64 += mid32b;
    prod.m_high64 += (prod.m_low64 < mid32b ? 1 : 0);

    return prod;
}
posted @   HachikoT  阅读(1780)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示