fibodevy/BigInts

GitHub: fibodevy/BigInts

为 Pascal 语言提供无依赖的任意精度整数和小数运算库,性能接近 GMP 并内置丰富的数论与数学函数。

Stars: 2 | Forks: 0

# BigInts 为 Pascal 提供的任意精度整数和小数,封装在单个自包含单元 `BigInts` ([bigints.pas](bigints.pas)) 中。包含三种值类型,支持普通数字拥有的所有运算符,除了可用内存外没有大小限制,除 RTL 外无任何依赖。 | 类型 | 描述 | |---|---| | `BigInt` | 有符号数;位运算符使用具有无限符号扩展的二进制补码语义,类似于 Python 整数 | | `UBigInt` | 无符号数;任何会导致值降到零以下的操作都会引发 `ERangeError` | | `BigDecimal` | 十进制浮点数:一个 `BigInt` 尾数乘以 10 的幂;精确的 `+ - *`,除法可选择精度 | ## 功能 - 完整的运算符覆盖:`+ - * div mod / ** shl shr and or xor not`,所有比较操作,`inc`/`dec`,复合赋值(`+=`, `*=` 等),一元 `+`/`-` - 与任意一侧的普通整数进行混合运算,支持从 `Int64`/`QWord`/string 隐式转换,双向显式转换(包括 `Double`;整数转换永远不会通过浮点数进行四舍五入) - 任意大小的字面量,支持 `_` 分隔符以及 `$ 0x % 0b & 0o` 前缀;支持 2 到 36 进制之间的解析和格式化 - 乘法:Schoolbook、Karatsuba 和 Toom-3,由可调阈值决定选择策略,并配有专门的平方计算路径 - 除法:Knuth 算法 D,外加针对大数的分治法进制转换 - 模运算:Montgomery `modPow`(加上常数时间复杂度的 `modPowSec`)、`modInverse`、`modSqrt` (Tonelli-Shanks)、`sqrtModN`、`crt`、`discreteLog` - 质数:Miller-Rabin `isProbablePrime`(在 3.3e24 以下为确定性判定)、Baillie-PSW `isPrime`、`nextPrime`/`prevPrime`、`randomPrime`/`randomSafePrime`/`randomStrongPrime`、精确的 `primePi`/`primeCount` - 因式分解:试除法加上 Pollard-Brent rho 算法,质因数及其指数被分组为 `(p, e)` 元组;由此衍生出乘法函数 `eulerPhi`、`carmichaelLambda`、`moebius`、`sigma`、`tau`、`divisors`、`radical` - 数论与组合数学:Lehmer `gcd`、`gcdExt`、`jacobi`、`kronecker`、`continuedFraction`,以及 `factorial`、`fibonacci`、`lucas`、`binomial`、`multinomial`、`catalan`、`bell`、`stirling1`/`stirling2`、`bernoulli`、`partitions`、`subfactorial`、`primorial` - 随机数:可插拔的生成器(xoshiro256**、PCG64、splitmix64、`System.Random`、OS 熵),确定性种子生成,均匀分布的 `randomBelow`/`randomRange` - 互操作性:支持大端序和小端序的字节序列化、`hashCode`、带分组的数字输出 - 小数:精确的十进制运算(`0.1 + 0.2 = 0.3`),任意精度的除法和开方,六种舍入模式,双向的最短和精确浮点数转换,以及完整的分析工具箱 - `pi`、`exp`、`ln`、`log2`/`log10`/`logBase`、分数幂、三角函数、双曲函数、`gamma`、`erf`、`atan2`、`hypot`、任意精度的 `agm`(见下文的 BigDecimal 章节) - 速度:在 x64 平台上,核心操作的速度实测为 GMP 的 1.3 到 4.6 倍(基准测试见下文);内层循环使用汇编语言编写,并在 `USEASM` 定义下提供纯 Pascal 回退方案 ## 快速入门 ``` program quickstart; {$mode unleashed} uses BigInts; begin var a: BigInt := '123456789012345678901234567890'; var b: BigInt := '-0xDEAD_BEEF'; writeln($'{a * b}'); writeln((BigInt(2) ** 4096).digitCount); // 1234 var (q, r) := a.divMod(b); writeln($'{q} rem {r}'); var p := UBigInt.randomPrime(256); writeln(p.isProbablePrime); // TRUE for var (f, e) in UBigInt(720).factorize do write($'{f}^{e} '); // 2^4 3^2 5^1 writeln; {$ifdef WINDOWS}readln;{$endif} end. ``` ## 方法 所有方法均采用 camelCase 命名,并可通过代码补全功能发现。除非另有说明,方法同时存在于两种类型上。 ### 转换输入与输出 | 方法 | 备注 | |---|---| | `parse(s)`, `parse(s, base)` | 静态方法;自动检测 `$ 0x % 0b & 0o` 前缀,允许使用 `_` 分隔符和正负号 | | `tryParse(s, out v)`, `tryParse(s, base, out v)` | 静态方法;遇到错误输入时不抛出异常 | | `toString`, `toString(base)` | 支持 2..36 进制;负数在任何进制下均格式化为正负号加上绝对值 | | `toHex`, `toBin`, `toOct` | 用于 16、2、8 进制的简写 | | `toStringGrouped(sep = '_', groupSize = 3)` | `1_234_567` 样式的输出 | | `toInt8`, `toUInt8`, `toInt16`, `toUInt16`, `toInt32`, `toUInt32`, `toInt64`, `toUInt64`, `toDouble` | 如果值超出范围,则引发 `ERangeError` | | `toInt128`, `toUInt128` | 适用于编译器提供原生 128 位类型的目标平台 | | `fitsInInt8`, `fitsInUInt8`, ... `fitsInInt64`, `fitsInUInt64`(以及在可用情况下的 `fitsInInt128`/`fitsInUInt128`) | 针对每种原生位宽的对应检查 | | `toUBigInt` / `toBigInt` | 跨越有符号与无符号的边界;如果为负值,则引发 `ERangeError` | | `toDecimal` | 扩展为 `BigDecimal`(精确转换,绝不进行四舍五入) | | `toBytesLE`, `toBytesBE`, `fromBytesLE`, `fromBytesBE` | `UBigInt`:原始绝对值;`BigInt`:带符号位的最小二进制补码,类似于 Java 的 `toByteArray` | ### 谓词与符号 | 方法 | 备注 | |---|---| | `isZero`, `isOne`, `isEven`, `isOdd`, `isPowerOfTwo` | | | `sign` | 返回 -1、0 或 1 | | `isNegative`, `isPositive` | 仅限 `BigInt` | | `abs`, `magnitude`, `negate` | 仅限 `BigInt`;`magnitude` 返回 `UBigInt` 类型的绝对值 | ### 位操作 | 方法 | 备注 | |---|---| | `bitLength`, `popCount`, `lowestSetBit` | | | `testBit(i)`, `setBit(i)`, `clearBit(i)`, `flipBit(i)`, `bits[i]` | 在 `BigInt` 上,这些操作基于无限的二进制补码扩展 | | `complement(width)` | 仅限 `UBigInt`:对低 `width` 位进行按位非操作;由于无符号值不存在无限的补码,因此 `UBigInt` 没有 `not` 运算符 | ### 比较与除法 | 方法 | 备注 | |---|---| | `compare`, `equals`, `min`, `max` | 外加全套比较运算符 | | `divMod(d)` | 执行一次除法,返回 `(q, r)` 元组 | | `floorDiv(d)`, `floorMod(d)` | 仅限 `BigInt`;类似于 Python,向负无穷方向舍入 | | `ceilDiv(d)` | 向正无穷方向舍入 | | `swap(other)`, `hashCode`, `digitCount` | | ### 数学运算 | 方法 | 备注 | |---|---| | `sqr`, `sqrt`, `nthRoot(n)`, `nthRootRem(n)` | 平方和整数(下取整)开方;`nthRootRem` 还会返回余数 | | `isKthPower(k)` | 判断该值是否为完全的 `k` 次方 | | `pow(e)`, `**` | 常规求幂 | | `modPow(e, m)` | 对奇数 `m` 使用窗口指数的 Montgomery 算法;在 `BigInt` 上,模数必须为正数,结果落在 `0..m-1` 区间内,负指数通过模逆元进行计算 | | `modPowSec(e, m)` | 结果与 `modPow` 相同,但运算序列不会根据指数的位产生分支(具备抗侧信道攻击能力,适用于保密指数) | | `modInverse(m)` | 当逆元不存在时引发 `EBigIntError` | | `gcd`, `lcm` | Lehmer gcd 算法 | | `isProbablePrime(rounds = 24)` | Miller-Rabin 算法;在 3.3e24 以下使用确定性见证,大于该值使用随机轮次 | | `isPrime` | Baillie-PSW(先进行确定性小范围测试,然后进行强基数 2 的 Miller-Rabin 测试以及强 Lucas 测试);目前无已知的反例 | | `nextPrime` | 大于自身值的下一个质数 | ### 常量与生成器(类函数) | 方法 | 备注 | |---|---| | `zero`, `one`, `two`, `ten`, `minusOne` | `minusOne` 仅限 `BigInt` | | `pow2(n)` | | | `random(bits)` | 在 `2^bits` 以下均匀分布;生成器是可插拔的,详见扩展功能章节 | | `factorial(n)` | 二进制拆分 | | `fibonacci(n)` | 快速倍增 | ## 扩展功能 构建在核心算术之上的可选数学层。 ### 随机数 | 方法 | 备注 | |---|---| | `randomBelow(bound)` | 在 `0..bound-1` 均匀分布,采用拒绝采样 | | `randomRange(lo, hi)` | 在 `lo..hi` 均匀分布,包含两端;在 `BigInt` 上支持负数边界 | | `randomPrime(bits, rounds = 24)` | 精确的位长度:最高位置 1、奇数、经过 Miller-Rabin 测试 | | `randomSafePrime(bits)` | 安全质数 `p`,即 `(p-1)/2` 也是质数 | | `randomStrongPrime(bits)` | Gordon 算法:`p-1` 和 `p+1` 都包含大的质因数 | `random` 及其相关方法的后端由 `BigIntRngAlgo` 变量选择: | 生成器 | 备注 | |---|---| | `rngXoshiro256ss` | 默认选项;xoshiro256** | | `rngPcg64` | 带有参考乘数和流的 PCG XSL-RR 128/64 | | `rngSplitMix64` | 小巧且快速;内部也用于扩展种子 | | `rngSystem` | 基于历史 `RandSeed` 的 `System.Random` 流 | | `rngOS` | 每次调用都获取新的 OS 熵(RtlGenRandom, /dev/urandom);用于生成密钥时请选择此项 | 生成器状态为每个线程独立(`threadvar`):线程中的第一次随机抽取会从 OS 熵进行自我播种,因此未设种子的值每次运行都会不同,且线程间拥有独立的流——没有共享状态,也没有锁。`BigIntRandomSeed(seed)` 使调用线程变得可复现(它同时会设置 `RandSeed`,因此 `rngSystem` 模式也会随之改变);`BigIntRandomize` 会根据 OS 熵重新播种。如果需要跨线程的复现性,请分别对每个线程进行播种。`rngSystem` 模式保留了普通的 `System.Random` 契约(由 `RandSeed` 驱动,延迟自动播种机制对其不产生影响)。 ### 数论 | 方法 | 备注 | |---|---| | `gcdExt(other)` | 仅限 `BigInt`;扩展欧几里得算法,返回 `(g, x, y)` 元组,满足 `a*x + b*y = g` | | `jacobi(n)` | 雅可比符号,适用于奇数正整数 `n`,返回 -1、0 或 1 | | `kronecker(n)` | 克罗内克符号,是雅可比符号对任意整数的完整扩展(处理因数 2 和负数参数) | | `modSqrt(p)` | 模质数平方根(Tonelli-Shanks 算法);如果是非二次剩余则引发 `EBigIntError` | `sqrtModN(n)` | 模合数 `n` 的所有平方根(分解、提升、CRT);需要满足 `gcd(self, n) = 1`,如果是非二次剩余则返回空数组 | | `discreteLog(target, m)` | 小步大步算法:寻找满足 `self^x = target (mod m)` 的最小 `x`,若无则返回 -1;返回 `Int64`,适用于小规模实例 | | `crt(remainders, moduli)` | `BigInt` 类函数;用于两两互质的正模数的中国剩余定理 | | `isPerfectSquare` | 快速 mod-16 过滤器,然后进行精确的根检查 | | `sqrtRem` | 返回 `(root, rem)` 元组,满足 `self = root^2 + rem` | | `prevPrime` | 小于自身值的最大质数;如果 `self <= 2` 则引发异常 | | `factorize` | 返回按质数升序排列的 `(p, e)` 元组数组;10^4 以下使用试除法,以上使用 Pollard-Brent rho 算法;`BigInt.factorize` 对绝对值进行因式分解 | `factorize` 的运行时间随第二大质因数的平方根成正比增长,因此两个随机大质数的乘积将需要极长的时间才能分解——这是因式分解的本质,而不是 bug。 以下方法直接基于因式分解结果运行(因此其开销等同于 `factorize`): | 方法 | 备注 | |---|---| | `eulerPhi` | 欧拉函数,即不超过 `n` 且与 `n` 互质的整数个数 | | `carmichaelLambda` | 群的指数:对于所有互质的 `a`,满足 `a^k = 1 (mod n)` 的最小 `k` | | `moebius` | 莫比乌斯函数,返回 -1、0 或 1 | | `sigma(k = 1)` | 约数的 `k` 次方之和;`sigma(0)` 即为 `tau` | | `tau` | 约数的个数 | | `radical` | 不同质因数的乘积 | | `divisors` | 升序排列的所有约数 | | `isSquarefree`, `isPerfect`, `isCarmichael` | 对应的谓词判定 | 质数计数和有理近似(`BigInt`/`UBigInt` 类函数): | 方法 | 备注 | |---|---| | `primePi(n)` | 使用分段筛法得出 `<= n` 的质数的精确数量(`QWord`,实际适用于约 1e10) | | `primeCount(lo, hi)` | 在 `lo..hi` 范围内质数的精确数量 | | `continuedFraction(num, den)` | `num/den` 连续分数的系数 | | `fromContinuedFraction(cf)` | 将系数计算并还原为最简分数 `(num, den)`;对 `cf` 进行切片可获得渐近分数 | ### 组合数学 全部为类函数。 | 方法 | 备注 | |---|---| | `lucas(n)` | 斐波那契的伴随序列,采用一次快速倍增运算 | | `binomial(n, k)` | 乘法形式,每个中间除法都是精确的 | | `multinomial(ks)` | `(sum ks)! / prod(ks[i]!)`,以二项式之积表示 | | `catalan(n)` | `binomial(2n, n) div (n + 1)` | | `primorial(n)` | 所有不超过 `n` 的质数的乘积,使用奇数筛法加上平衡乘法 | | `risingFactorial(x, n)`, `fallingFactorial(x, n)` | Pochhammer 符号;`BigInt` 接受负数底数 | | `subfactorial(n)` | 错排数 `!n` | | `bell(n)` | 贝尔数,通过贝尔三角形计算 | | `stirling1(n, k)` | 第一类有符号斯特林数(`BigInt`) | | `stirling2(n, k)` | 第二类斯特林数 | | `partitions(n)` | 整数分拆数 `p(n)`,使用欧拉五边形定理递推 | | `bernoulli(n)` | `BigInt` 类函数;伯努利数,返回精确的最简 `(num, den)` 分数 | ### 罗马数字与单词 `BigInt` 和 `UBigInt` 还支持将自身格式化为人类可读的形式: | 方法 | 备注 | |---|---| | `toRoman` | 将 `1..3999` 之间的数值转换为罗马数字 | | `toWords` | 英语短进位制单词(`one million two hundred thirty-four thousand ...`),最高达 `10^66` | ## BigDecimal 基于相同整数核心构建的任意精度十进制浮点数:一个值由一个 `BigInt` 尾数乘以 10 的幂构成,并保持规范形式(没有尾随零)。`0.1` 就是精确的 `0.1`,财务计算永远不会发生漂移,尾数也能完全利用整数引擎的速度。 - `+ - *` 总是精确的,`div`/`mod`(整数商和精确余数)也一样。 - 默认情况下,`/` 会四舍五入保留 18 位小数;`divide(b, precision)` 可以自定义。商总是保留完整的整数部分以及至少 `precision` 位有效数字,并包含一位会被 `toString` 舍去的隐藏保护位,因此 `(1/3) * 3` 会打印为 `1`。完全精确的商仍然是精确的:`1 / 8` 结果为 `0.125`。 - 比较运算是数值层面的(`0.5 = 5E-1`),且能看到存储的保护位,所以即使 `(1/3) * 3` 打印出来是 `1`,`(1/3) * 3 < 1` 仍然成立。 - 与整数、字符串、`BigInt` 和 `UBigInt` 的混合运算可隐式转换;浮点数只能通过显式转换或 `from*` 构造器进行转换,因此不会有未知的二进制舍入误差混入。 ``` program decimals; {$mode unleashed} uses BigInts; begin var price: BigDecimal := '19.99'; writeln($'{price * 3}'); // 59.97 writeln($'{BigDecimal(1) / 3}'); // 0.333333333333333333 writeln($'{BigDecimal(1) / 3 * 3}'); // 1 writeln($'{BigDecimal(2).sqrt(30)}'); // 1.41421356237309504880168872421 writeln($'{BigDecimal.fromDouble(0.1)}'); // 0.1 writeln($'{BigDecimal.fromDoubleExact(0.1)}'); // 0.1000000000000000055511151231257827021181583404541015625 var pi: BigDecimal := '3.14159265'; writeln($'{pi.rounded(-2)} {pi.rounded(-2, bdrCeil)} {pi.trunc}'); // 3.14 3.15 3 writeln($'{BigDecimal('123456.789').toScientific}'); // 1.23456789E5 {$ifdef WINDOWS}readln;{$endif} end. ``` 解析层运行在相同的缩放整数核心之上:每个函数都接受一个 `precision` 参数(小数位数,默认为 18),并像除法一样通过隐藏的保护位对其显示的最后一位进行舍入。`pi` 来源于 Chudnovsky 二进制拆分并进行了缓存,巨大的三角函数参数会使用匹配精度的 pi 进行规约。 ``` program analytic; {$mode unleashed} uses BigInts; begin writeln($'{BigDecimal.pi(50)}'); // 3.14159265358979323846264338327950288419716939937511 writeln($'{BigDecimal(2).ln(40)}'); // 0.6931471805599453094172321214581765680755 writeln($'{BigDecimal(2) ** BigDecimal('0.5')}'); // 1.414213562373095049 writeln($'{BigDecimal(1).sin(40)}'); // 0.8414709848078965066525023216302989996226 writeln($'{BigDecimal('1E6').logBase(BigDecimal(10))}'); // 6 writeln($'{BigDecimal('19.99').quantize(BigDecimal('0.05'))}'); // 20 var (num, den) := BigDecimal('0.375').toFraction; writeln($'{num}/{den}'); // 3/8 writeln(BigDecimal('0.000123').toEngineering); // 123E-6 {$ifdef WINDOWS}readln;{$endif} end. ``` | 方法 | 备注 | |---|---| | `parse(s)`, `tryParse(s, out v)`, `:=` from string | `[sign]digits[.digits][E[sign]digits]`,允许使用 `_` 分隔符 | | `calc(s, precision = 18)`, `tryCalc(s, out v, precision = 18)` | 计算整个表达式字符串,详情见下方的计算器部分 | | `toString`, `toScientific`, `toEngineering` | 普通格式 `-123.45` / 标准化格式 `-1.2345E2` / 指数为 3 的倍数的工程格式,如 `123E-6` | | `toInt8`..`toInt64`, `toUInt8`..`toUInt64`, `toBigInt`, `toUBigInt`, `fitsInInt8`..`fitsInUInt64` | 精确转换:如果不属于整数或超出范围则引发 `ERangeError`(如果是负数,`toUInt*`/`toUBigInt` 也会报错) | | `trunc`, `floor`, `ceil`, `round` | 转换为 `BigInt`:分别向零、向负无穷、向正无穷舍入,以及向最近的偶数舍入(如同 Pascal 的 `round`) | | `frac` | `trunc` 丢弃的小数部分,满足 `self = trunc + frac` | | `toFraction` | 转换为精确的分数形式 `(num, den)` 元组:`0.375` 变为 `(3, 8)` | | `rounded(toDigit = 0, mode = bdrRound)` | 在任何小数位进行舍入:`0` = 整数,`-2` = 分,`3` = 千位;模式包括 `bdrTrunc bdrCeil bdrFloor bdrRound bdrHalfUp bdrHalfEven` | | `quantize(step, mode = bdrRound)` | 舍入到任意步长的最近倍数,如 `0.05` | | `divide(b, precision = 18)`, `divMod(d)` | 按指定精度进行除法运算 / 返回带有精确余数的整数商 | | `fromDouble`, `fromSingle`, explicit float casts | 能够读取并还原为相同浮点数的最短小数表示:`0.1` 得到 `0.1` | | `fromDoubleExact`, `fromSingleExact` | 精确的二进制值表示:`0.1` 会给出全部 55 位数字 | | `toDouble`, `toSingle` | 正确地舍入到最近的浮点数,逢半向上;向上溢出得到无穷大,向下溢出得到零 | | `toExtended`, `fromExtended`, `fromExtendedExact` | 适用于支持 80 位浮点数类型的目标平台 | | `sqrt(precision = 18)`, `nthRoot(n, precision = 18)` | 保留 `precision` 位小数,带有与除法相同的隐藏保护位 | | `pow(e)` | 当 `e >= 0` 的整数时完全精确;负数指数则按默认精度作除法处理 | | `pow(y, precision = 18)`, `**` | 通过 `exp(y * ln x)` 计算分数指数幂 | | `exp`, `ln`, `log2`, `log10`, `logBase(b)` | 均接受 `(precision = 18)` 参数;`log10` 对 10 的幂是精确的,`log2` 对 2 的幂是精确的 | | `sin`, `cos`, `tan`, `arcsin`, `arccos`, `arctan` | 弧度制;大参数会在匹配精度下对 pi/2 进行模运算规约 | | `sinh`, `cosh`, `tanh` | 在相同的指数核心上计算双曲函数 | | `gamma`, `lnGamma` | Gamma 函数(通过反射公式覆盖负数)及其对数(正参数) | | `factorial` | 实数阶乘 `x! = gamma(x+1)`,对于小的非负整数是精确的 | | `erf`, `erfc` | 误差函数及其补函数;`erfc` 在参数较大时使用连续分数计算 | | `continuedFraction(maxTerms = 0)` | 该值的(有限、精确的)连分数;非常适合用于寻找最佳有理近似 | | `roundToSignificant(digits, mode = bdrRound)` | 舍入到指定的有效数字位数,而不是小数位数 | | `pi(precision)`, `e(precision)` | 类函数;pi 的值在多次调用间会被缓存 | | `atan2(y, x, precision)`, `hypot(x, y, precision)`, `agm(a, b, precision)` | 类函数:感知象限的反正切、欧几里得长度、算术几何平均值 | | `gcd`, `lcm` | 基于十进制网格的运算:`gcd(0.25, 0.15) = 0.05` | | `precision`, `mostSignificantExponent`, `getDigit(i)` | 有效数字位数、最高位数字的指数、位于 `10^i` 上的数字 | | `shift10(n)`, `shifted10(n)` | 在不改变尾数的情况下乘以 10 的幂 | | `isZero`, `isOne`, `isIntegral`, `isEven`, `isOdd`, `isNegative`, `isPositive`, `sign`, `abs`, `negate` | 谓词和符号辅助方法;带小数部分的值既不是偶数也不是奇数 | | `compare`, `equals`, `approxEquals(other, eps)`, `min`, `max`, `hashCode`, `swap` | 外加完整的运算符和比较集合 | | `zero`, `one`, `two`, `ten` | 类常量 | ### 计算器 `calc` 会根据您传入的精度对整个字符串表达式进行求值,并返回一个 `BigDecimal`。它是一个纯粹、无状态的求值器:没有变量,没有赋值,相同的字符串总是产生相同的结果。`tryCalc` 在遇到错误表达式时返回 `false` 而不是抛出异常;而 `calc` 在遇到语法错误时会抛出带有字符位置信息的 `EConvertError`,并让数学错误(`EDivByZero`、`ERangeError`、EBigIntError`)直接透传抛出。 ``` writeln(BigDecimal.calc('2^100 / 3').toScientific); // 4.2...E29 writeln(BigDecimal.calc('sin(pi/6)', 30)); // 0.5 writeln(BigDecimal.calc('(1 + sqrt(5)) / 2', 50)); // the golden ratio, 50 digits ``` 运算符(从最低优先级到最高优先级): | 运算符 | 含义 | 备注 | |---|---|---| | `+` `-` | 加、减 | 左结合 | | `*` `/` `div` `mod` `%` | 乘法、实数除法、整数除法、余数 | `/` 对于 `10/4` 会给出 `2.5`;`div` 给出 `2`;`%` 等同于 `mod` | | `-` `+` (一元) | 符号 | 优先级低于幂运算,因此 `-2^2 = -4` | | `^` `**` | 幂运算 | 右结合,`2^3^2 = 512`;`2^-3 = 0.125` | | `!` (后缀) | 阶乘 | 作用于非负整数 | 函数(名称不区分大小写): | 组别 | 函数 | |---|---| | 根与幂 | `sqrt(x)` `cbrt(x)` `root(x, n)` `pow(x, y)` `sqr(x)` | | 指数与对数 | `exp(x)` `ln(x)` `log(x)`=log10, `log(x, b)`=以 b 为底, `log2(x)` `log10(x)` `logb(x, b)` | | 三角函数 | `sin cos tan (x)`, `asin acos atan (x)` (也可写作 `arcsin`/`arccos`/`arctan`), `sinh cosh tanh (x)` | | 舍入函数 | `floor(x)` `ceil(x)` `round(x)` `trunc(x)` | | 特殊函数 | `gamma(x)` `lngamma(x)` `erf(x)` `erfc(x)` `factorial(x)` | | 双参数函数 | `min max gcd lcm atan2 hypot agm (a, b)` | | 常量 | `pi` `e` `tau` `phi` | 每个函数都会在工作精度下映射到同名的方法,因此 `calc('sin(1)', 40)` 等同于 `BigDecimal(1).sin(40)`。目前仅公开了接收数字并返回单个数字的函数;整数数论相关的方法和返回元组的方法仍保留在 API 层面。 ## 值得了解的语义 - `div`/`mod` 像 Pascal 一样向零截断;`floorDiv`/`floorMod` 像 Python 一样向负无穷舍入;`ceilDiv` 向上舍入。 - `/` 是整数除法,与 `div` 相同(针对整数类型遵循 C 语言家族的惯例)。 - 负数 `BigInt` 的 `shr` 是算术右移(向负无穷舍入);`shl` 会保留符号位。 - 负数 `BigInt` 的位运算使用带有无限符号扩展的二进制补码;`not x = -x-1`。 - 在任何进制下,负数的格式化形式均为正负号加上绝对值:`-255` 在十六进制下输出为 `-FF`。 - 复制这些值开销很低:小值(不超过 256 位)直接内联在值本身中,较大的值则共享一个引用计数的内存块,但进行修改的方法会优先取消共享,因此绝不会有变量在背后被意外改变。 - `0 ** 0 = 1`,除以零会引发 `EDivByZero`,无法容纳的转换会引发 `ERangeError`,解析错误会引发 `EConvertError`,定义域错误(负指数、无逆元、非二次剩余)会引发 `EBigIntError`。 ## 性能 在 x86_64 平台上使用带有汇编内层循环的 64 位肢(用于乘法/带进位加法的行原语,并在 CPU 支持 ADX 时在运行时选用 mulx/adcx/adox 的 `addmul_1` 实现);在其他平台上则使用可移植的 32 位 Pascal 肢。汇编代码隐藏在单元顶部的 `USEASM` 定义之下——将其注释掉即可获得完全可移植的纯 Pascal 版本(在 x64 上核心操作大约慢 4 到 8 倍)。实现了 Knuth 算法 D 除法、超过可调阈值(`BigIntKaratsubaThreshold`、`BigIntToom3Threshold`)的 Karatsuba 和 Toom-3 乘法与平方算法、带有窗口指数的 Montgomery modPow、分治法进制转换以及 Lehmer gcd。最大 256 位(`BIGINT_INLINE_LIMBS` 肢)的数值直接内联存储在变量中,无需分配堆内存;较大的结果则直接在热点路径中构建大小精确的堆缓冲区。在 x64 桌面平台上:计算 `factorial(50000)` 约需 ~12 ms,计算 `fibonacci(1000000)` 约需 ~10 ms。 ### 对比 GMP 的基准测试 在一台 x64 桌面电脑上与 GMP 6.3.0(随 Git for Windows 附带的 64 位肢 `libgmp-10.dll`)进行对比测试,双方都启用了 `-O3`,结果为单次操作耗时。GMP 方面重用了其 mpz 目标变量,这符合 GMP 代码的常规编写方式;而 BigInts 方面为每次操作分配全新的值,这正是值语义所必须付出的代价。 | 操作 | BigInts | GMP | 比率 | |---|---|---|---| | 128位加法 | 0.015 us | 0.005 us | 3.0x | | 1024位加法 | 0.037 us | 0.008 us | 4.6x | | 16384位加法 | 0.148 us | 0.066 us | 2.2x | | 262144位加法 | 1.78 us | 1.20 us | 1.5x | | 128位乘法 | 0.019 us | 0.006 us | 3.2x | | 1024位乘法 | 0.181 us | 0.13 us | 1.4x | | 8192位乘法 | 6.23 us | 4.20 us | 1.5x | | 65536位乘法 | 180 us | 82.1 us | 2.2x | | 262144位乘法 | 1560 us | 548 us | 2.8x | | 65536x1024位乘法 | 6.71 us | 8.64 us | 0.8x | | 8192位平方 | 5.32 us | 2.59 us | 2.1x | | 65536位平方 | 178 us | 54.9 us | 3.2x | | 2048/1024位 divmod | 0.48 us | 0.283 us | 1.7x | | 8192/4096位 divmod | 3.50 us | 2.63 us | 1.3x | | 131072/65536位 divmod | 709 us | 208 us | 3.4x | | 4096位 toString | 8.93 us | 4.25 us | 2.1x | | 65536位 toString | 423 us | 229 us | 1.8x | | 4096位 parse | 5.89 us | 3.83 us | 1.5x | | 65536位 parse | 230 us | 132 us | 1.7x | | 512位 modPow | 79.6 us | 37.9 us | 2.1x | | 1024位 modPow | 431 us | 260 us | 1.7x | | 2048位 modPow | 2710 us | 1960 us | 1.4x | | 1024位 gcd | 10.6 us | 2.48 us | 4.3x | | 16384位 gcd | 413 us | 114 us | 3.6x | 批量算术运算的性能达到了 GMP 的 1.3 至 4.6 倍。剩余的性能差距主要源于 GMP 采用的手写优化汇编、其在处理超大操作数时使用的更高阶 Toom 算法和 FFT,以及本单元尚未实现的次平方阶 gcd 和除法算法。最高 256 位的小数值直接内联存储而无需分配内存,因此一肢或两肢长度的加法或乘法运算耗时约为 15-20 纳秒,即便在这种尺寸下,其速度依然保持在 GMP 的 3 倍以内,否则对于这种每次操作都新建值的库来说,其耗时将主要耗费在内存分配上。 ## 示例 以下每个代码块都是一个完整的程序:将其复制到 `.lpr` 文件中,并将 `bigints.pas` 放在同一路径下,即可直接编译并运行。 ### 字面量与格式化 ``` program literals; {$mode unleashed} uses BigInts; begin var a: UBigInt := '123_456_789_000_000_000_000_000'; var b: BigInt := '-0xDEAD_BEEF'; var c: UBigInt := '%1010_1010'; writeln(a.toStringGrouped); // 123_456_789_000_000_000_000_000 writeln(b.toString); // -3735928559 writeln(c.toString(36)); // 4Q writeln(UBigInt.parse('zz', 36).toString); // 1295 {$ifdef WINDOWS}readln;{$endif} end. ``` ### 各种形式的除法 ``` program division; {$mode unleashed} uses BigInts; begin var (q, r) := BigInt(-7).divMod(BigInt(2)); writeln($'{q} {r}'); // -3 -1 (truncated, like Pascal div/mod) writeln(BigInt(-7).floorDiv(2).toString); // -4 (like Python) writeln(BigInt(-7).floorMod(2).toString); // 1 writeln(UBigInt(7).ceilDiv(UBigInt(2)).toString); // 4 {$ifdef WINDOWS}readln;{$endif} end. ``` ### 二进制补码位运算 ``` program bitwise; {$mode unleashed} uses BigInts; begin writeln((BigInt(-1) and BigInt($FF)).toString); // 255: -1 is an infinite run of ones writeln((not BigInt(0)).toString); // -1 writeln((BigInt(-5) shr 1).toString); // -3: arithmetic shift writeln(BigInt(-255).toHex); // -FF: sign plus magnitude in every base {$ifdef WINDOWS}readln;{$endif} end. ``` ### 质数与一个简易 RSA 示例 ``` program rsa; {$mode unleashed} uses BigInts; begin BigIntRandomize; var p := UBigInt.randomPrime(512); var q := UBigInt.randomPrime(512); var n := p * q; var e: UBigInt := 65537; var d := e.modInverse((p - 1) * (q - 1)); var msg: UBigInt := '0x48656C6C6F21'; // "Hello!" var cipher := msg.modPow(e, n); writeln(cipher.modPow(d, n) = msg); // TRUE {$ifdef WINDOWS}readln;{$endif} end. ``` ### 随机数套件 ``` program random_suite; {$mode unleashed} uses BigInts; begin BigIntRngAlgo := rngPcg64; BigIntRandomSeed(42); // reproducible from here on writeln(UBigInt.random(128).toHex); // F6A4492CA8314B92F0D3403191F1E9AF writeln(UBigInt.randomBelow(UBigInt.ten ** 20).toString); writeln(BigInt.randomRange(-50, 50).toString); {$ifdef WINDOWS}readln;{$endif} end. ``` ### 因式分解 ``` program factor; {$mode unleashed} uses BigInts; begin var n: UBigInt := '123456789012345678'; for var (p, e) in n.factorize do write($'{p}^{e} '); // 2^1 3^3 21491747^1 106377431^1 writeln; {$ifdef WINDOWS}readln;{$endif} end. ``` ### 中国剩余定理 ``` program crt_demo; {$mode unleashed} uses BigInts; begin // x = 2 (mod 3), x = 3 (mod 5), x = 2 (mod 7) var x := BigInt.crt([BigInt(2), BigInt(3), BigInt(2)], [BigInt(3), BigInt(5), BigInt(7)]); writeln(x.toString); // 23 {$ifdef WINDOWS}readln;{$endif} end. ``` ## 许可证 本项目采用 Mozilla Public License 2.0 (MPL-2.0) 进行授权。详情请参阅 LICENSE 文件。 `examples/` 目录下的示例程序可无限制免费使用;您可以自由地将其中代码复制到您自己的项目中,且无需承担任何 MPL 义务。
标签:Pascal, 任意精度数学库, 大整数计算, 密码学基础, 数论算法, 算法库