Skip to content

Commit 5289380

Browse files
authored
Example: Fast exponentiation, streamingly 🌊 (#925)
Tiny & fast exponentiation using streams: <img width="491" alt="Screenshot 2025-04-05 at 16 04 07" src="https://github.com/user-attachments/assets/0ec1cdf1-6c86-4fa4-890f-063540c083f4" /> The approach is simple: to calculate `n^k`, zip squares of `n` (`n`, `n^2`, `n^4`, ...) with the bits of `k`. Sketch: ``` n^k = n^0b10011 = (1 * n^0b10000) * (0 * n^0b01000) * (0 * n^0b00100) * (1 * n^0b00010) * (1 * n^0b00001) ``` Also drags a few other changes along: - `product` and `iterate` in `stream` - temporary addition of `eachLSB` and `eachMSB` -- these would probably be better off using the toolchain from #923, but I don't want to forget about the example I'm adding it because: 1. it's a fun, small example, 2. it uses a bunch of different combinators, 3. it uses the new multiple param lambda case syntax (#914), 4. and it has some missed opt opportunities, I think.
1 parent b9d4108 commit 5289380

File tree

3 files changed

+172
-2
lines changed

3 files changed

+172
-2
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
0
2+
0b0
3+
0b0
4+
1
5+
0b1
6+
0b1
7+
2
8+
0b01
9+
0b10
10+
3
11+
0b11
12+
0b11
13+
4
14+
0b001
15+
0b100
16+
123456789
17+
0b101010001011001111011010111
18+
0b111010110111100110100010101
19+
20+
2 ^ 0
21+
1
22+
1
23+
2 ^ 5
24+
32
25+
32
26+
2 ^ 20
27+
1048576
28+
1048576
29+
10 ^ 10
30+
10000000000
31+
10000000000
32+
0 ^ 1
33+
0
34+
0
35+
1 ^ 0
36+
1
37+
1
38+
0 ^ 0
39+
1
40+
1
41+
200 ^ 2
42+
40000
43+
40000
44+
128 ^ 4
45+
268435456
46+
268435456
47+
7 ^ 13
48+
96889010407
49+
96889010407
50+
13 ^ 7
51+
62748517
52+
62748517
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import stream
2+
3+
/// Least significant bits
4+
def eachLSB(n: Int): Unit / emit[Bool] = {
5+
if (n <= 0) {
6+
do emit(false)
7+
} else {
8+
var tmp = n
9+
while (tmp > 0) {
10+
do emit(if (tmp.mod(2) == 1) true else false)
11+
tmp = tmp / 2
12+
}
13+
}
14+
}
15+
16+
/// Most significant bits
17+
def eachMSB(n: Int): Unit / emit[Bool] = {
18+
if (n <= 0) {
19+
do emit(false)
20+
} else {
21+
// First, calculate the highest bit position
22+
var highest = 0
23+
var m = n
24+
while (m > 0) {
25+
highest = highest + 1
26+
m = m / 2
27+
}
28+
29+
// Then emit from MSB to LSB
30+
for[Int] { range(0, highest) } { i =>
31+
val index = highest - i
32+
val bit = n.bitwiseShr(index - 1).mod(2) == 1
33+
do emit(bit)
34+
}
35+
}
36+
}
37+
38+
def fastexp(n: Int, k: Int) = product {
39+
stream::zip[Int, Bool] {n.iterate { x => x * x }} {k.eachLSB} {
40+
case res, true => do emit(res)
41+
case res, false => ()
42+
}
43+
}
44+
45+
def main() = {
46+
/// Computes n^exp for integers. (from #923!)
47+
def pow(n: Int, exp: Int): Int = {
48+
def go(n: Int, exp: Int, acc: Int): Int = {
49+
if (exp == 0) {
50+
acc
51+
} else if (mod(exp, 2) == 0) {
52+
go(n * n, exp / 2, acc)
53+
} else {
54+
go(n * n, exp / 2, acc * n)
55+
}
56+
}
57+
go(n, exp, 1)
58+
}
59+
60+
def prettyBits(bits: List[Bool]): String =
61+
"0b" ++ bits.map { b => if (b) "1" else "0"}.join("")
62+
63+
def testBits(n: Int) = {
64+
println(n)
65+
println(collectList[Bool] {n.eachLSB}.prettyBits)
66+
println(collectList[Bool] {n.eachMSB}.prettyBits)
67+
}
68+
69+
testBits(0)
70+
testBits(1)
71+
testBits(2)
72+
testBits(3)
73+
testBits(4)
74+
testBits(123456789)
75+
76+
println("")
77+
78+
def testExp(n: Int, k: Int) = {
79+
println(show(n) ++ " ^ " ++ show(k))
80+
println(n.pow(k))
81+
println(n.fastexp(k))
82+
}
83+
84+
testExp(2, 0)
85+
testExp(2, 5)
86+
testExp(2, 20)
87+
testExp(10, 10)
88+
testExp(0, 1)
89+
testExp(1, 0)
90+
testExp(0, 0)
91+
testExp(200, 2)
92+
testExp(128, 4)
93+
testExp(7, 13)
94+
testExp(13, 7)
95+
}

libraries/common/stream.effekt

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,23 @@ def replicate[A](number: Int) { action: () => A }: Unit / emit[A] =
173173
replicate(number - 1) {action}
174174
}
175175

176+
/// Creates an infinite iterated stream given by an `initial` seed and a `step` function:
177+
/// iterate(a){f} ~> a, f(a), f(f(a)), f(f(f(a))), ...
178+
def iterate[A](initial: A) { step: A => A }: Unit / emit[A] = {
179+
var current = initial
180+
while (true) {
181+
do emit(current)
182+
current = step(current)
183+
}
184+
}
185+
176186

177-
def sum { stream : () => Unit / emit[Int] }: Int =
187+
def sum { stream: () => Unit / emit[Int] }: Int =
178188
returning::sum[Unit]{stream}.second
179189

190+
def product { stream: () => Unit / emit[Int] }: Int =
191+
returning::product[Unit]{stream}.second
192+
180193
def collectList[A] { stream: () => Unit / emit[A] }: List[A] =
181194
returning::collectList[A, Unit]{stream}.second
182195

@@ -543,7 +556,7 @@ def limit[A, R](number: Int) { stream: () => R / emit[A] }: R / { emit[A], stop
543556
}
544557
}
545558

546-
def sum[R] { stream : () => R / emit[Int] }: (R, Int) = {
559+
def sum[R] { stream: () => R / emit[Int] }: (R, Int) = {
547560
var s = 0;
548561
try {
549562
(stream(), s)
@@ -553,6 +566,16 @@ def sum[R] { stream : () => R / emit[Int] }: (R, Int) = {
553566
}
554567
}
555568

569+
def product[R] { stream: () => R / emit[Int] }: (R, Int) = {
570+
var s = 1;
571+
try {
572+
(stream(), s)
573+
} with emit[Int] { v =>
574+
s = s * v;
575+
resume(())
576+
}
577+
}
578+
556579
def collectList[A, R] { stream: () => R / emit[A] }: (R, List[A]) =
557580
try {
558581
(stream(), Nil())

0 commit comments

Comments
 (0)