同余
在介紹補碼之前先引入同余的概念,因為補碼的原理利用了同余的性質。
同余是數論中最重要的基礎概念之一,由德國數學家高斯在其1801年出版的«算術探索»中系統地進行了研究,書中所創造的同余符號"\(\equiv\)"也沿用至今。那么什么是同余呢?
【定義】給定正整數m,若有整數a、b,使得\(m|(a-b)\),則稱a與b關於模m同余,記作\(a\equiv b(mod\,m)\).
例如對於m=8而言,1和9就是同余的,同理可知-7和1是同余的,9和17是同余的等等。由同余性質的等價關系可得到一個同余類:
【推論】對於特定的正整數m,將有m個同余類.一般用\(0,1,\cdots ,m-1\)來唯一標識這m個同余類.
前面說補碼利用了同余的性質,其實更准確的說法應該是補碼利用了同余類之間的運算性質:
【性質一】若\(a\equiv b(mod\,m)\),\(x\equiv y(mod\,m)\),那么\(a+x\equiv b+y(mod\,m)\)
【性質二】若\(a\equiv b(mod\,m)\),\(x\equiv y(mod\,m)\),那么\(a\cdot x\equiv b\cdot y(mod\,m)\)
補碼
在計算機世界中,任何數據都是一段二進制編碼,是程序根據執行上下文來決定當前這段二進制編碼具體表達了什么含義。
對於n位二進制編碼而言,很自然地就可以表示0到\(2^n-1\)的數字。起初,我們可以很輕易地對這些數字進行加法或乘法運算,得到的結果是模\(2^n\)的余數。
為了讓負數也能參與運算,補碼定義最高位的bit為符號位,表示該位原本的值的相反數即\(-2^{n-1}\),為什么這樣定義呢,因為這樣得到的負數和原本的正數剛好相差了\(2\times 2^{n-1}\)。看到這里是不是很眼熟,那就是負數和對應的正數模\(2^n\)是同余的,也就是說補碼沒有用\(0,1,\cdots ,2^n-1\)來唯一標識同余類,而是用\(-2^{n-1},\cdots, 0,\cdots ,2^{n-1}-1\)來唯一標識同余類。這里演示當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;
}