极致数学 (Ultimathnum)

“凡听见我这话就去行的,好比一个聪明人,把房子盖在磐石上。雨淋,水冲,风吹,撞着那房子,房子总不倒塌,因为根基立在磐石上。”(马太福音 7:24-25)

目录

序言

抽象交响曲

这项工作旨在统一底层算术的各种概念。它简化了错误处理,并引入了任意二进制整数无穷大,以及其他特性。但是,重点不是思想的多样性,而是可扩展抽象的编排。它更像是一部交响曲,而不是一个库。

感恩的客人

在这个项目中,我们使用 Swift 强大的类型系统构建自定义抽象。为了表达感谢,我们通过各种互操作性模块将我们的功能扩展到 Swift 本身。导入这些模块使您可以访问符合相应协议的类型。

var randomness = RandomInt() // from RandomIntKit
let random = Bool.random(using: &randomness.stdlib) // from Swift

一些模型(例如核心二进制整数类型和系统随机数生成器)本质上是可互操作的,这意味着它们的 stdlib() 函数返回适当的标准库类型。因此,Interoperable 协议的互操作性特性不会引入任何不必要的单态化。

安装

SemVer 2.0.0

请安装最新的发布版本,或特定的提交版本。

主版本号为零 (0.y.z) 表示初始开发阶段。
任何内容都可能随时更改。
公共 API 不应被认为是稳定的。

Swift Package Manager

将此软件包添加到您的软件包依赖项列表中。

.package(url: "https://github.com/oscbyspro/Ultimathnum.git", exact: "x.y.z"),

从此产品列表中选择目标依赖项。

.product(name: "Ultimathnum",  package: "Ultimathnum"), // umbrella
.product(name: "CoreKit",      package: "Ultimathnum"),
.product(name: "DoubleIntKit", package: "Ultimathnum"),
.product(name: "FibonacciKit", package: "Ultimathnum"),
.product(name: "InfiniIntKit", package: "Ultimathnum"),
.product(name: "RandomIntKit", package: "Ultimathnum"),

.product(name: "Ultimathiop",  package: "Ultimathnum"), // umbrella
.product(name: "CoreIop",      package: "Ultimathnum"),
.product(name: "DoubleIntIop", package: "Ultimathnum"),
.product(name: "InfiniIntIop", package: "Ultimathnum"),
.product(name: "RandomIntIop", package: "Ultimathnum"),

如何运行单元测试

使用以下终端命令运行单元测试

swift test --skip Benchmarks -Xswiftc -O
swift test --skip Benchmarks -Xswiftc -O --configuration release

如何运行性能测试

使用以下终端命令运行性能测试

xcodebuild test -scheme Ultimathnum-Performance -destination 'platform=macOS'

此设置启用优化并省略不相关的指标。由于该项目既是抽象的又是模块化的,因此这些步骤至关重要。省略编译器优化会使性能降低高达两个数量级。同样,未能省略代码覆盖率会使性能再降低一个数量级。如果您的测量结果显示合理输入的结果不佳,请随时提出问题或提出您可能有的任何疑问。

概述

强大的 BinaryInteger

           ┌───────────┬───────────┐
           │  Systems  │ Arbitrary │
┌──────────┼───────────┤───────────┤
│   Signed │ B,E,F,S,X │  A,B,F,X  │
├──────────┼───────────┤───────────┤
│ Unsigned │ B,E,F,S,Y │  A,B,E,Y  │
└──────────┴───────────┴───────────┘
 A) ArbitraryInteger: B
 B)    BinaryInteger: -
 E)      EdgyInteger: B
 F)    FiniteInteger: B
 S)   SystemsInteger: E, F
 X)    SignedInteger: F
 Y)  UnsignedInteger: E

有损不变性

有损操作的表现非常良好。对于二进制整数而言,我们专门使用此术语来表示截断。换句话说,有损二进制整数结果会省略不适合的位,这意味着所有现有位仍然有效。在实践中,不同大小的类型在较小类型中适合的位上达成一致。以下示例演示了这种不变性。

let a = I32.random()
let b = I32.random()
#expect((a).times(b) == I32.exactly(IXL(a) * IXL(b)))

类型无关的哈希值

值相等的二进制整数在通用的底层表示形式上达成一致,具体来说,就是其规范化字节序列的内容。这种共同的基础为跨类型边界的相等值生成相等的哈希值。请注意,我们也可以使用这种重新解释策略来执行比较。以下代码片段向您展示了预期结果。

let x = I32.random()
#expect(x.hashValue == I64(x).hashValue)
#expect(x.hashValue == IXL(x).hashValue)

独立的描述编码器

您可能需要将二进制整数转换为人类可读的文本。在这种情况下,description(using:)init(_:using:) 允许您使用给定的编码器执行动态基数转换。它使用固定数量的非泛型和非内联算法来编码和解码所有二进制整数类型。

//  MacBook Pro, 13-inch, M1, 2020, -O, code coverage disabled.

let fib1e6    = IXL.fibonacci(1_000_000)                // 0.015s
let fib1e6r10 = fib1e6.description(using:     .decimal) // 0.297s (208988 numerals)
let fib1e6s10 = IXL(((fib1e6r10)), using:     .decimal) // 0.040s (208988 numerals)
let fib1e6r16 = fib1e6.description(using: .hexadecimal) // 0.002s (173561 numerals)
let fib1e6s16 = IXL(((fib1e6r16)), using: .hexadecimal) // 0.002s (173561 numerals)

Fallible<T> 的救赎之路

当值保留相关信息时,例如在许多二进制整数运算中,值-错误对无疑是一流的恢复机制。 Fallible<T> 通过添加强大的转换和符合人体工程学的实用程序来增强此概念。它通过简化传播错误的过程来提高稳健性。让我们通过检查您可扩展的错误处理库来让您快速上手。

传播:veto(_:)map(_:)sink(_:)

单个错误通常足以使操作无效,但您可能希望将与语义上有意义的工作单元对应的所有错误分组。 veto(_:) 操作允许您高效且轻松地组合多个错误指示器。它将底层值与当前错误指示器和给定参数的逻辑析取一起转发。以下是它在实践中的工作方式。

U8.max.veto(false).veto(false) // value: 255, error: false
U8.max.veto(false).veto(true ) // value: 255, error: true
U8.max.veto(true ).veto(false) // value: 255, error: true
U8.max.veto(true ).veto(true ) // value: 255, error: true

链接多个表达式是一种常见的功能性方法,您可能需要考虑一下。 map(_:) 转换底层值并在给定函数末尾合并其他错误指示器。请仔细查看以下示例。我们稍后会重新审视它。

func sumsquare<T: UnsignedInteger>(a: T, b: T) -> Fallible<T> {
    a.plus(b).map{$0.squared()}
}

现在您已经了解了错误传播的基础知识,让我们为您配备应对世界的手段。虽然 map(_:) 非常棒,但有时您可能会注意到,它有时会演变成金字塔式的厄运。换句话说,它无法扩展以满足更复杂问题的需求。但请不要担心,sink(_:) 方法已经到来!它允许您在操作之间卸载错误指示器。让我们使用另一个公式重写我们的示例。

func sumsquare<T: UnsignedInteger>(a: T, b: T) -> Fallible<T> {
    var w: Bool = false
    let x: T = a.squared().sink(&w)
    let y: T = a.times(b ).sink(&w).doubled().sink(&w)
    let z: T = b.squared().sink(&w)
    return x.plus(y).sink(&w).plus(z).veto(w)
}

转换:optional()result(_:)prune(_:)

值-错误对对于编写富有表现力的库代码具有重要属性。但是,如果您是最终用户呢?好消息!让我们看一下 optional()result(_:)prune(_:)。前两种方法将错误转换为名称相似且易于理解的类型,简洁明了。最后一个方法会在出错时抛出其参数。想象一下 sink(_:) 的轻松版本。让我们再次重写我们的示例,这次使用 prune(_:)

enum Oops: Error { case such, error, much, wow, very, impressive }

func sumsquare<T: UnsignedInteger>(a: T, b: T) throws(Oops) -> T {
    let x: T = try a.squared().prune(Oops.such)
    let y: T = try a.times(b ).prune(Oops.error).doubled().prune(Oops.much)
    let z: T = try b.squared().prune(Oops.wow)
    return try x.plus(y).prune(Oops.very).plus(z).prune(Oops.impressive)
}

期望:unwrap(_:)unchecked(_:)

期望呢?有时,您会进行可能永远不会产生错误的可失败函数调用;该函数只是恰好被泛化到调用站点的范围之外。在这种情况下,错误将表明程序行为不当。考虑使用 unwrap(_:) 停止程序执行,并使用 unchecked(_:) 仅停止未优化构建的程序执行。此外,您可以传递澄清性注释以用于调试目的。

U8.min.incremented().unwrap("precondition") // must not fail
U8.min.incremented().unchecked("assertion") // must not fail, must go brrr

解构:valueerrorcomponents()

虽然 Fallible<T> 致力于简化错误处理过程,但您可能喜欢手动执行操作。祝您好运,玩得开心!您可以读取和写入其值和错误字段。或者,考虑通过调用消耗性的 components() 方法来解构该对。

var pair = Fallible("Hello")
pair.value.append(", World")
pair.error.toggle()
let (value, error) = pair.components()

便捷性:error(...)init(_:error:setup:)

凭借足够的实践经验,您可能会注意到一些重复出现的用法模式。非常有用的 sink(_:) 方法需要一个可变错误指示器,您通常希望在最后合并它,例如。静态 error(...) 函数在两个方面都为您提供支持。在其他时候,您可能想要消耗初始值。在这种情况下,您应该考虑使用 init(_:error:setup:)

let x0 = Fallible.error {
    U8.zero.decremented().sink(&$0)
}   // value: 255, error: true

let x1 = Fallible(U8.zero) {
    $0 = $0.decremented().sink(&$1)
}   // value: 255, error: true

前提条件 Guarantee 类型

类型 保证
除数 x ≠ 0
有限 x ∈ ℤ
自然 x ∈ ℕ
非零 x ≠ 0
移位 x ∈ 0 到 T.size

Guarantee 类型将其前提条件编码到类型系统中。您可以利用它们的语义来组合开销较小的复杂类型,或减少算法中的故障模式数量。此外,类型系统将提示您使用以下方法之一接受或拒绝它们的参数。

init(_:prune:)   // error: throws
init(exactly:)   // error: nil
init(_:)         // error: precondition
init(unchecked:) // error: assert
init(unsafe:)    // error: %%%%%%

神奇的 Divider<T> 常量

您知道编译器有时会用乘法替换除法,对吗?太好了,现在您也可以成为魔法师了! Divider<T>Divider21<T> 查找魔法常量,并仅使用乘法、加法和移位来除法数字。请注意,后者除法的数字是相应除数的两倍大小。

let random  = U8.random()
let divisor = Nonzero(U8.random(in: 1...255))
let divider = Divider(divisor)
let typical = random.division(divisor) as Division<U8, U8> // div
let magical = random.division(divider) as Division<U8, U8> // mul-add-shr
precondition(typical == magical) // quotient and remainder
let random  = Doublet(low: U8.random(), high: U8.random())
let divisor = Nonzero(U8.random(in: 1...255))
let divider = Divider21(divisor)
let typical = U8.division(random, by: divisor) as Fallible<Division<U8, U8>>
let magical = U8.division(random, by: divider) as Fallible<Division<U8, U8>>
precondition(typical == magical) // quotient, remainder, and error indicator

任意位 Count 统一

请进行一次 D20 奥术检定。

任意整数的位模式无限扩展——但它有一个结尾。它必须是这样的,否则它将不像它的系统整数兄弟姐妹。因此,我们需要一个不同的模型来表示任意整数大小。 Count 通过将机器字的最后一位重新解释为对数无限来满足我们的需求。

UXL.max = &0
min ..< msb: [0,  IX.max + 0]
msb ... max: [∞ - IX.max,  ∞] ≤ log2(&0+1)