Dhall 中的整数除法
Integer division in Dhall
我想计算两个 Natural
的商。我需要满足的要求是我有一些配置项必须动态定义为另一个共享(即一个容器具有 X
内存,该容器中进程的两个配置键被定义为 X / Y
和 X / Z
).
我的第一个想法是使用递归,但这种方法行不通:
let quotient =
\(n: Natural) ->
\(m: Natural) ->
if lessThan n m then 0
else 1 + quotient (Natural/subtract m n) m
特别是,当我尝试调用它时,Dhall 抱怨 quotient
尚未定义。考虑到 Dhall 的总体功能范例(以及我对它的不熟悉),这似乎是合理的。我认为可能有某种方法可以做到这一点,但不幸的是我做不到。
我使用 Natural/fold
进行了另一次尝试,但我不确定这是否有意义。
let quotient =
λ(n : Natural) →
λ(m : Natural) →
let div =
λ(n : Natural) →
λ(m : Natural) →
let Div = { q : Natural, r : Natural }
in Natural/fold
n
Div
( λ(d : Div) →
if Natural/isZero m
then d
else if lessThan d.r m
then d
else { q = d.q + 1, r = Natural/subtract m d.r }
)
{ q = 0, r = n }
in (div n m).q
这通过了以下所有断言。
let example1 = assert : quotient 1 1 ≡ 1
let example2 = assert : quotient 2 2 ≡ 1
let example3 = assert : quotient 3 1 ≡ 3
let example4 = assert : quotient 3 2 ≡ 1
let example5 = assert : quotient 9 4 ≡ 2
let example6 = assert : quotient 4 5 ≡ 0
let example7 = assert : quotient 0 1 ≡ 0
let example8 = assert : quotient 0 2 ≡ 0
let example9 = assert : quotient 1 0 ≡ 0
let example9 = assert : quotient 0 0 ≡ 0
let example9 = assert : quotient 2 0 ≡ 0
除以 0
时返回 0
对我来说没问题。
有没有更惯用的方法来实现这个?我在Prelude
里找现成的整数除法函数,没找到
目前没有更简单的方法来实现除法 Natural
除了你已经写的之外,但可能有更有效的方法。另一种会给出对数时间复杂度但实现复杂得多的方法是“猜测和检查”,您可以在所需数字周围进行二进制搜索以找到最大数字 x
使得 x * m = n
.
不过,我真的不推荐这样做,而且我认为可能更好的方法是查看是否有明智的 built-in 添加到可以有效支持整数除法的语言中。理想情况下,这样的 built-in 对于所有输入都是 well-defined,因此直接添加整数除法可能行不通(因为 x / 0
不是 well-defined)。但是(我在这里吐口水),也许像 Natural/safeDivide x y == x / (y + 1)
这样的 built-in 可以工作,然后如果用户想允许除以 0
,他们可以围绕它定义自己的包装器。就 built-in 的外观征求意见的最佳地点可能是 Discourse 论坛:
TL;WR 编辑:
let Natural/div = λ(n : Natural) → λ(m : Natural) →
let div = https://github.com/jcaesar/dhall-div/releases/download/1/quotient.dhall sha256:d6a994f4b431081e877a0beac02f5dcc98f3ea5b027986114487578056cb3db9
in (div n m).q
Gabriel Gonzalez nerd-sniped 提到二分查找时 让我很满意。有一段时间,我在兜圈子 运行 ,尝试是否不能通过将数字转换为 List Bool
来实现搜索所需的除以二(好吧,它可以,与相同的问题下面),然后我注意到你可以实现长除法:
let Natural/le = λ(a : Natural) → λ(b : Natural) → Natural/isZero (Natural/subtract b a)
let Natural/equals = λ(a : Natural) → λ(b : Natural) → Natural/le a b && Natural/le b a
let bits =
λ(bits : Natural) →
Natural/fold
bits
(List Natural)
( λ(l : List Natural) →
l
# [ merge
{ Some = λ(i : Natural) → i * 2, None = 1 }
(List/last Natural l)
]
)
([] : List Natural)
in λ(w : Natural) →
let bits = bits w
in λ(n : Natural) →
λ(m : Natural) →
let T = { r : Natural, q : Natural } : Type
let div =
List/fold
Natural
bits
T
( λ(bit : Natural) →
λ(t : T) →
let m = m * bit
in if Natural/le m t.r
then { r = Natural/subtract m t.r, q = t.q + bit }
else t
)
{ r = n, q = 0 }
in if Natural/equals m 0 then 0 else div.q
唯一的问题是,由于没有对数,你不能在画面中做长除法的left-alignment,即你不知道MSB在[=14=中的位置如何] 或者 subs
需要多长时间。
我的理论家很伤心,因为我只是将除法简化为(粗略的近似)对数,但实践者说“你一直对 u64 感到满意,闭嘴。”
[编辑] 经过一番思考,我仍然无法有效地计算所有输入的对数(我认为这是不可能的)。但是我可以从数字的对数中找到下一个 2 的幂,直到固定的大限制(2^2^23 或 42 × 10^2525221,但见下文)。可以通过以下方式修改上述函数(让我们称之为quotient
):
let Natural/less =
λ(a : Natural) →
λ(b : Natural) →
if Natural/isZero (Natural/subtract a b) then False else True
let max = 23
let powpowT = { l : Natural, n : Natural }
let powpow =
Natural/fold
max
(List powpowT)
( λ(ts : List powpowT) →
merge
{ Some =
λ(t : powpowT) → [ { l = t.l + t.l, n = t.n * t.n } ] # ts
, None = [ { l = 1, n = 2 } ]
}
(List/head powpowT ts)
)
([] : List powpowT)
let powpow = List/reverse powpowT powpow
let bitapprox =
λ(n : Natural) →
List/fold
powpowT
powpow
Natural
( λ(e : powpowT) →
λ(l : Natural) →
if Natural/less n e.n then e.l else l
)
0
in λ(n : Natural) → λ(m : Natural) → quotient (bitapprox n) n m
这提供了一个可以接受的有效商实现,其中一个长除法画面最大是必要的两倍。在我的桌面 (62GB) m 上,我可以计算例如2^(2^18) / 7 在 11 秒内,但 运行 内存不足,无法容纳更大的数字。
不管怎样,如果你对那么大的数字感到厌烦,那你就用错了语言。
我想计算两个 Natural
的商。我需要满足的要求是我有一些配置项必须动态定义为另一个共享(即一个容器具有 X
内存,该容器中进程的两个配置键被定义为 X / Y
和 X / Z
).
我的第一个想法是使用递归,但这种方法行不通:
let quotient =
\(n: Natural) ->
\(m: Natural) ->
if lessThan n m then 0
else 1 + quotient (Natural/subtract m n) m
特别是,当我尝试调用它时,Dhall 抱怨 quotient
尚未定义。考虑到 Dhall 的总体功能范例(以及我对它的不熟悉),这似乎是合理的。我认为可能有某种方法可以做到这一点,但不幸的是我做不到。
我使用 Natural/fold
进行了另一次尝试,但我不确定这是否有意义。
let quotient =
λ(n : Natural) →
λ(m : Natural) →
let div =
λ(n : Natural) →
λ(m : Natural) →
let Div = { q : Natural, r : Natural }
in Natural/fold
n
Div
( λ(d : Div) →
if Natural/isZero m
then d
else if lessThan d.r m
then d
else { q = d.q + 1, r = Natural/subtract m d.r }
)
{ q = 0, r = n }
in (div n m).q
这通过了以下所有断言。
let example1 = assert : quotient 1 1 ≡ 1
let example2 = assert : quotient 2 2 ≡ 1
let example3 = assert : quotient 3 1 ≡ 3
let example4 = assert : quotient 3 2 ≡ 1
let example5 = assert : quotient 9 4 ≡ 2
let example6 = assert : quotient 4 5 ≡ 0
let example7 = assert : quotient 0 1 ≡ 0
let example8 = assert : quotient 0 2 ≡ 0
let example9 = assert : quotient 1 0 ≡ 0
let example9 = assert : quotient 0 0 ≡ 0
let example9 = assert : quotient 2 0 ≡ 0
除以 0
时返回 0
对我来说没问题。
有没有更惯用的方法来实现这个?我在Prelude
里找现成的整数除法函数,没找到
目前没有更简单的方法来实现除法 Natural
除了你已经写的之外,但可能有更有效的方法。另一种会给出对数时间复杂度但实现复杂得多的方法是“猜测和检查”,您可以在所需数字周围进行二进制搜索以找到最大数字 x
使得 x * m = n
.
不过,我真的不推荐这样做,而且我认为可能更好的方法是查看是否有明智的 built-in 添加到可以有效支持整数除法的语言中。理想情况下,这样的 built-in 对于所有输入都是 well-defined,因此直接添加整数除法可能行不通(因为 x / 0
不是 well-defined)。但是(我在这里吐口水),也许像 Natural/safeDivide x y == x / (y + 1)
这样的 built-in 可以工作,然后如果用户想允许除以 0
,他们可以围绕它定义自己的包装器。就 built-in 的外观征求意见的最佳地点可能是 Discourse 论坛:
TL;WR 编辑:
let Natural/div = λ(n : Natural) → λ(m : Natural) →
let div = https://github.com/jcaesar/dhall-div/releases/download/1/quotient.dhall sha256:d6a994f4b431081e877a0beac02f5dcc98f3ea5b027986114487578056cb3db9
in (div n m).q
Gabriel Gonzalez nerd-sniped 提到二分查找时 List Bool
来实现搜索所需的除以二(好吧,它可以,与相同的问题下面),然后我注意到你可以实现长除法:
let Natural/le = λ(a : Natural) → λ(b : Natural) → Natural/isZero (Natural/subtract b a)
let Natural/equals = λ(a : Natural) → λ(b : Natural) → Natural/le a b && Natural/le b a
let bits =
λ(bits : Natural) →
Natural/fold
bits
(List Natural)
( λ(l : List Natural) →
l
# [ merge
{ Some = λ(i : Natural) → i * 2, None = 1 }
(List/last Natural l)
]
)
([] : List Natural)
in λ(w : Natural) →
let bits = bits w
in λ(n : Natural) →
λ(m : Natural) →
let T = { r : Natural, q : Natural } : Type
let div =
List/fold
Natural
bits
T
( λ(bit : Natural) →
λ(t : T) →
let m = m * bit
in if Natural/le m t.r
then { r = Natural/subtract m t.r, q = t.q + bit }
else t
)
{ r = n, q = 0 }
in if Natural/equals m 0 then 0 else div.q
唯一的问题是,由于没有对数,你不能在画面中做长除法的left-alignment,即你不知道MSB在[=14=中的位置如何] 或者 subs
需要多长时间。
我的理论家很伤心,因为我只是将除法简化为(粗略的近似)对数,但实践者说“你一直对 u64 感到满意,闭嘴。”
[编辑] 经过一番思考,我仍然无法有效地计算所有输入的对数(我认为这是不可能的)。但是我可以从数字的对数中找到下一个 2 的幂,直到固定的大限制(2^2^23 或 42 × 10^2525221,但见下文)。可以通过以下方式修改上述函数(让我们称之为quotient
):
let Natural/less =
λ(a : Natural) →
λ(b : Natural) →
if Natural/isZero (Natural/subtract a b) then False else True
let max = 23
let powpowT = { l : Natural, n : Natural }
let powpow =
Natural/fold
max
(List powpowT)
( λ(ts : List powpowT) →
merge
{ Some =
λ(t : powpowT) → [ { l = t.l + t.l, n = t.n * t.n } ] # ts
, None = [ { l = 1, n = 2 } ]
}
(List/head powpowT ts)
)
([] : List powpowT)
let powpow = List/reverse powpowT powpow
let bitapprox =
λ(n : Natural) →
List/fold
powpowT
powpow
Natural
( λ(e : powpowT) →
λ(l : Natural) →
if Natural/less n e.n then e.l else l
)
0
in λ(n : Natural) → λ(m : Natural) → quotient (bitapprox n) n m
这提供了一个可以接受的有效商实现,其中一个长除法画面最大是必要的两倍。在我的桌面 (62GB) m 上,我可以计算例如2^(2^18) / 7 在 11 秒内,但 运行 内存不足,无法容纳更大的数字。
不管怎样,如果你对那么大的数字感到厌烦,那你就用错了语言。