Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve Int128 API #123

Merged
merged 8 commits into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion spec/builtin.nim
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const arr = [
("*", proc(n: Node): Node = makeNum(n[0].num * n[1].num)),
("/", proc(n: Node): Node =
# TODO: this needs to produce a rational number, not an integer
makeNum(n[0].num / n[1].num)
makeNum(n[0].num div n[1].num)
),
("neg", proc(n: Node): Node = makeNum(-n.num)),
("^", proc(n: Node): Node =
Expand Down
23 changes: 12 additions & 11 deletions spec/int128.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ type Int128* = object
const
Zero* = Int128()

proc toInt128*(x: int): Int128 =
proc toInt128*(x: SomeInteger): Int128 =
result.lo = cast[uint64](x)
if x < 0:
if x is SomeSignedInt and x < 0:
result.hi = high(uint64) # sign extend the lower bits

proc toInt*(x: Int128): int =
Expand Down Expand Up @@ -42,7 +42,7 @@ proc `-`*(x: Int128): Int128 =
result.hi = not(x.hi)
result = result + Int128(lo: 1)

proc `shl`*(x: Int128, by: uint8): Int128 =
proc `shl`*(x: Int128, by: int): Int128 =
## Logical left shift.
let by = by and 127
if by == 0:
Expand All @@ -56,6 +56,7 @@ proc `shl`*(x: Int128, by: uint8): Int128 =

proc `shr`*(x: Int128, by: int): Int128 =
## Logical right shift.
let by = by and 127
if by == 0:
result = x
elif by < 64:
Expand All @@ -65,25 +66,25 @@ proc `shr`*(x: Int128, by: int): Int128 =
result.lo = (x.hi shr (by - 64))
result.hi = 0

proc `<`*(a, b: Int128): bool =
proc `<%`*(a, b: Int128): bool =
## Unsigned less-than comparison.
if a.hi < b.hi: true
elif a.hi == b.hi: a.lo < b.lo
else: false

proc `<=`*(a, b: Int128): bool =
proc `<=%`*(a, b: Int128): bool =
## Unsigned less-than-or-equal comparison.
if a.hi < b.hi: true
elif a.hi == b.hi: a.lo <= b.lo
else: false

proc below*(a, b: Int128): bool =
proc `<`*(a, b: Int128): bool =
## Signed less-than comparison.
if cast[int64](a.hi) < cast[int64](b.hi): true
elif a.hi == b.hi: a.lo < b.lo
else: false

proc bequal*(a, b: Int128): bool =
proc `<=`*(a, b: Int128): bool =
## Signed less-than-or-equal comparison.
if cast[int64](a.hi) < cast[int64](b.hi): true
elif a.hi == b.hi: a.lo <= b.lo
Expand Down Expand Up @@ -111,7 +112,7 @@ proc `*`*(a, b: Int128): Int128 =
proc udivMod*(dividend, divisor: Int128): (Int128, Int128) =
## Unsigned 128-integer division. Returns the quotient and
## remainder.
if divisor > dividend:
if divisor >% dividend:
return (Zero, dividend)

# shift-subtract algorithm (refer to
Expand All @@ -121,11 +122,11 @@ proc udivMod*(dividend, divisor: Int128): (Int128, Int128) =
remainder = dividend

let digits = fastLog2(dividend) - fastLog2(divisor)
var divisor = divisor shl digits.uint8
var divisor = divisor shl digits

for _ in 0..digits:
quotient = quotient shl 1
if remainder >= divisor:
if remainder >=% divisor:
remainder = remainder - divisor
quotient.lo = quotient.lo or 1 # left-shift 1 into the quotient

Expand All @@ -148,7 +149,7 @@ proc divMod*(dividend, divisor: Int128): (Int128, Int128) =
else:
result = udivMod(dividend, divisor)

proc `/`*(a, b: Int128): Int128 =
proc `div`*(a, b: Int128): Int128 =
## Signed 128-bit truncating integer division.
divMod(a, b)[0]

Expand Down
1 change: 1 addition & 0 deletions tests/unittest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Unit-like tests for modules.
1 change: 1 addition & 0 deletions tests/unittest/runner.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nim --outdir:build/tests --path:. ${args=c} -r ${file}
213 changes: 213 additions & 0 deletions tests/unittest/tint128.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
discard """
description: "Tests for the 128-bit integer implementation"
"""
import spec/int128

block parse_stringify_roundtrip:
template test(str: string): bool = $parseInt128(str) == str

doAssert test("12345678987654321012345678987")
doAssert test("0")
# leading zeroes:
doAssert $parseInt128("0001") == "1"
doAssert $parseInt128("-0001") == "-1"
doAssert $parseInt128("-0") == "0"
# trailing zeroes:
doAssert test("10")
doAssert test("-10")

block negation:
doAssert -toInt128(1) == toInt128(-1)
doAssert -toInt128(0) == toInt128(0)

var d: array[39, Int128] ## array of powers of 10
d[0] = parseInt128("1")
d[1] = parseInt128("10")
d[2] = parseInt128("100")
d[3] = parseInt128("1000")
d[4] = parseInt128("10000")
d[5] = parseInt128("100000")
d[6] = parseInt128("1000000")
d[7] = parseInt128("10000000")
d[8] = parseInt128("100000000")
d[9] = parseInt128("1000000000")
d[10] = parseInt128("10000000000")
d[11] = parseInt128("100000000000")
d[12] = parseInt128("1000000000000")
d[13] = parseInt128("10000000000000")
d[14] = parseInt128("100000000000000")
d[15] = parseInt128("1000000000000000")
d[16] = parseInt128("10000000000000000")
d[17] = parseInt128("100000000000000000")
d[18] = parseInt128("1000000000000000000")
d[19] = parseInt128("10000000000000000000")
d[20] = parseInt128("100000000000000000000")
d[21] = parseInt128("1000000000000000000000")
d[22] = parseInt128("10000000000000000000000")
d[23] = parseInt128("100000000000000000000000")
d[24] = parseInt128("1000000000000000000000000")
d[25] = parseInt128("10000000000000000000000000")
d[26] = parseInt128("100000000000000000000000000")
d[27] = parseInt128("1000000000000000000000000000")
d[28] = parseInt128("10000000000000000000000000000")
d[29] = parseInt128("100000000000000000000000000000")
d[30] = parseInt128("1000000000000000000000000000000")
d[31] = parseInt128("10000000000000000000000000000000")
d[32] = parseInt128("100000000000000000000000000000000")
d[33] = parseInt128("1000000000000000000000000000000000")
d[34] = parseInt128("10000000000000000000000000000000000")
d[35] = parseInt128("100000000000000000000000000000000000")
d[36] = parseInt128("1000000000000000000000000000000000000")
d[37] = parseInt128("10000000000000000000000000000000000000")
d[38] = parseInt128("100000000000000000000000000000000000000")

block add_sub_test:
# test addition:
var sum: Int128
for it in d.items:
sum = sum + it

doAssert $sum == "111111111111111111111111111111111111111"

# test subtraction:
for it in d.items:
sum = sum - it

doAssert sum == Zero

# test comparison, multiplication, and division with positive nubers:
for i, a in d.pairs:
for j, b in d.pairs:
doAssert(cmp(a, b) == cmp(i, j))
if i + j < d.len:
doAssert a * b == d[i + j]
if i - j >= 0:
doAssert a div b == d[i - j]

# test comparison, multiplication, and division with negative nubers:
for it in d.mitems:
it = -it

for i, a in d.pairs:
for j, b in d.pairs:
doAssert(cmp(a, b) == -cmp(i, j))
if i + j < d.len:
doAssert a * b == -d[i + j]
if i - j >= 0:
doAssert a div b == -d[i - j]

block sign_test:
# ake sure the sign of quotients, remainders, and products is correct
let
a = 100'i64
b = 13

doAssert toInt128(a) * toInt128(0) == toInt128(0)
doAssert toInt128(-a) * toInt128(0) == toInt128(0)

template compare(a, b): bool =
divMod(toInt128(a), toInt128(b)) == (toInt128(a div b), toInt128(a mod b))

doAssert compare( a, b)
doAssert compare(-a, b)
doAssert compare(-a, -b)
doAssert compare( a, -b)

doAssert compare( b, b)
doAssert compare(-b, b)
doAssert compare(-b, -b)
doAssert compare( b, -b)

doAssert compare( b, a)
doAssert compare(-b, a)
doAssert compare(-b, -a)
doAssert compare( b, -a)

# test logical left and right shift:

proc toHex(i: Int128): string =
var val = i
while val != Zero:
let (q, r) = udivMod(val, toInt128(16))
result.insert $"0123456789abcdef"[r.toInt]
val = q

# pad to 32 characters:
for _ in result.len..<32:
result.insert "0"

let e = parseInt128("70997106675279150998592376708984375")
let rshifted = [
# toHex(e shr 0), toHex(e shr 1), toHex(e shr 2), toHex(e shr 3), ...
"000dac6d782d266a37300c32591eee37", "0006d636bc1693351b9806192c8f771b", "00036b1b5e0b499a8dcc030c9647bb8d", "0001b58daf05a4cd46e601864b23ddc6",
"0000dac6d782d266a37300c32591eee3", "00006d636bc1693351b9806192c8f771", "000036b1b5e0b499a8dcc030c9647bb8", "00001b58daf05a4cd46e601864b23ddc",
"00000dac6d782d266a37300c32591eee", "000006d636bc1693351b9806192c8f77", "0000036b1b5e0b499a8dcc030c9647bb", "000001b58daf05a4cd46e601864b23dd",
"000000dac6d782d266a37300c32591ee", "0000006d636bc1693351b9806192c8f7", "00000036b1b5e0b499a8dcc030c9647b", "0000001b58daf05a4cd46e601864b23d",
"0000000dac6d782d266a37300c32591e", "00000006d636bc1693351b9806192c8f", "000000036b1b5e0b499a8dcc030c9647", "00000001b58daf05a4cd46e601864b23",
"00000000dac6d782d266a37300c32591", "000000006d636bc1693351b9806192c8", "0000000036b1b5e0b499a8dcc030c964", "000000001b58daf05a4cd46e601864b2",
"000000000dac6d782d266a37300c3259", "0000000006d636bc1693351b9806192c", "00000000036b1b5e0b499a8dcc030c96", "0000000001b58daf05a4cd46e601864b",
"0000000000dac6d782d266a37300c325", "00000000006d636bc1693351b9806192", "000000000036b1b5e0b499a8dcc030c9", "00000000001b58daf05a4cd46e601864",
"00000000000dac6d782d266a37300c32", "000000000006d636bc1693351b980619", "0000000000036b1b5e0b499a8dcc030c", "000000000001b58daf05a4cd46e60186",
"000000000000dac6d782d266a37300c3", "0000000000006d636bc1693351b98061", "00000000000036b1b5e0b499a8dcc030", "0000000000001b58daf05a4cd46e6018",
"0000000000000dac6d782d266a37300c", "00000000000006d636bc1693351b9806", "000000000000036b1b5e0b499a8dcc03", "00000000000001b58daf05a4cd46e601",
"00000000000000dac6d782d266a37300", "000000000000006d636bc1693351b980", "0000000000000036b1b5e0b499a8dcc0", "000000000000001b58daf05a4cd46e60",
"000000000000000dac6d782d266a3730", "0000000000000006d636bc1693351b98", "00000000000000036b1b5e0b499a8dcc", "0000000000000001b58daf05a4cd46e6",
"0000000000000000dac6d782d266a373", "00000000000000006d636bc1693351b9", "000000000000000036b1b5e0b499a8dc", "00000000000000001b58daf05a4cd46e",
"00000000000000000dac6d782d266a37", "000000000000000006d636bc1693351b", "0000000000000000036b1b5e0b499a8d", "000000000000000001b58daf05a4cd46",
"000000000000000000dac6d782d266a3", "0000000000000000006d636bc1693351", "00000000000000000036b1b5e0b499a8", "0000000000000000001b58daf05a4cd4",
"0000000000000000000dac6d782d266a", "00000000000000000006d636bc169335", "000000000000000000036b1b5e0b499a", "00000000000000000001b58daf05a4cd",
"00000000000000000000dac6d782d266", "000000000000000000006d636bc16933", "0000000000000000000036b1b5e0b499", "000000000000000000001b58daf05a4c",
"000000000000000000000dac6d782d26", "0000000000000000000006d636bc1693", "00000000000000000000036b1b5e0b49", "0000000000000000000001b58daf05a4",
"0000000000000000000000dac6d782d2", "00000000000000000000006d636bc169", "000000000000000000000036b1b5e0b4", "00000000000000000000001b58daf05a",
"00000000000000000000000dac6d782d", "000000000000000000000006d636bc16", "0000000000000000000000036b1b5e0b", "000000000000000000000001b58daf05",
"000000000000000000000000dac6d782", "0000000000000000000000006d636bc1", "00000000000000000000000036b1b5e0", "0000000000000000000000001b58daf0",
"0000000000000000000000000dac6d78", "00000000000000000000000006d636bc", "000000000000000000000000036b1b5e", "00000000000000000000000001b58daf",
"00000000000000000000000000dac6d7", "000000000000000000000000006d636b", "0000000000000000000000000036b1b5", "000000000000000000000000001b58da",
"000000000000000000000000000dac6d", "0000000000000000000000000006d636", "00000000000000000000000000036b1b", "0000000000000000000000000001b58d",
"0000000000000000000000000000dac6", "00000000000000000000000000006d63", "000000000000000000000000000036b1", "00000000000000000000000000001b58",
"00000000000000000000000000000dac", "000000000000000000000000000006d6", "0000000000000000000000000000036b", "000000000000000000000000000001b5",
"000000000000000000000000000000da", "0000000000000000000000000000006d", "00000000000000000000000000000036", "0000000000000000000000000000001b",
"0000000000000000000000000000000d", "00000000000000000000000000000006", "00000000000000000000000000000003", "00000000000000000000000000000001",
"00000000000000000000000000000000", "00000000000000000000000000000000", "00000000000000000000000000000000", "00000000000000000000000000000000",
"00000000000000000000000000000000", "00000000000000000000000000000000", "00000000000000000000000000000000", "00000000000000000000000000000000",
"00000000000000000000000000000000", "00000000000000000000000000000000", "00000000000000000000000000000000", "00000000000000000000000000000000",
]
let lshifted = [
# toHex(e shl 0), toHex(e shl 1), toHex(e shl 2), toHex(e shl 3), ...
"000dac6d782d266a37300c32591eee37", "001b58daf05a4cd46e601864b23ddc6e", "0036b1b5e0b499a8dcc030c9647bb8dc", "006d636bc1693351b9806192c8f771b8",
"00dac6d782d266a37300c32591eee370", "01b58daf05a4cd46e601864b23ddc6e0", "036b1b5e0b499a8dcc030c9647bb8dc0", "06d636bc1693351b9806192c8f771b80",
"0dac6d782d266a37300c32591eee3700", "1b58daf05a4cd46e601864b23ddc6e00", "36b1b5e0b499a8dcc030c9647bb8dc00", "6d636bc1693351b9806192c8f771b800",
"dac6d782d266a37300c32591eee37000", "b58daf05a4cd46e601864b23ddc6e000", "6b1b5e0b499a8dcc030c9647bb8dc000", "d636bc1693351b9806192c8f771b8000",
"ac6d782d266a37300c32591eee370000", "58daf05a4cd46e601864b23ddc6e0000", "b1b5e0b499a8dcc030c9647bb8dc0000", "636bc1693351b9806192c8f771b80000",
"c6d782d266a37300c32591eee3700000", "8daf05a4cd46e601864b23ddc6e00000", "1b5e0b499a8dcc030c9647bb8dc00000", "36bc1693351b9806192c8f771b800000",
"6d782d266a37300c32591eee37000000", "daf05a4cd46e601864b23ddc6e000000", "b5e0b499a8dcc030c9647bb8dc000000", "6bc1693351b9806192c8f771b8000000",
"d782d266a37300c32591eee370000000", "af05a4cd46e601864b23ddc6e0000000", "5e0b499a8dcc030c9647bb8dc0000000", "bc1693351b9806192c8f771b80000000",
"782d266a37300c32591eee3700000000", "f05a4cd46e601864b23ddc6e00000000", "e0b499a8dcc030c9647bb8dc00000000", "c1693351b9806192c8f771b800000000",
"82d266a37300c32591eee37000000000", "05a4cd46e601864b23ddc6e000000000", "0b499a8dcc030c9647bb8dc000000000", "1693351b9806192c8f771b8000000000",
"2d266a37300c32591eee370000000000", "5a4cd46e601864b23ddc6e0000000000", "b499a8dcc030c9647bb8dc0000000000", "693351b9806192c8f771b80000000000",
"d266a37300c32591eee3700000000000", "a4cd46e601864b23ddc6e00000000000", "499a8dcc030c9647bb8dc00000000000", "93351b9806192c8f771b800000000000",
"266a37300c32591eee37000000000000", "4cd46e601864b23ddc6e000000000000", "99a8dcc030c9647bb8dc000000000000", "3351b9806192c8f771b8000000000000",
"66a37300c32591eee370000000000000", "cd46e601864b23ddc6e0000000000000", "9a8dcc030c9647bb8dc0000000000000", "351b9806192c8f771b80000000000000",
"6a37300c32591eee3700000000000000", "d46e601864b23ddc6e00000000000000", "a8dcc030c9647bb8dc00000000000000", "51b9806192c8f771b800000000000000",
"a37300c32591eee37000000000000000", "46e601864b23ddc6e000000000000000", "8dcc030c9647bb8dc000000000000000", "1b9806192c8f771b8000000000000000",
"37300c32591eee370000000000000000", "6e601864b23ddc6e0000000000000000", "dcc030c9647bb8dc0000000000000000", "b9806192c8f771b80000000000000000",
"7300c32591eee3700000000000000000", "e601864b23ddc6e00000000000000000", "cc030c9647bb8dc00000000000000000", "9806192c8f771b800000000000000000",
"300c32591eee37000000000000000000", "601864b23ddc6e000000000000000000", "c030c9647bb8dc000000000000000000", "806192c8f771b8000000000000000000",
"00c32591eee370000000000000000000", "01864b23ddc6e0000000000000000000", "030c9647bb8dc0000000000000000000", "06192c8f771b80000000000000000000",
"0c32591eee3700000000000000000000", "1864b23ddc6e00000000000000000000", "30c9647bb8dc00000000000000000000", "6192c8f771b800000000000000000000",
"c32591eee37000000000000000000000", "864b23ddc6e000000000000000000000", "0c9647bb8dc000000000000000000000", "192c8f771b8000000000000000000000",
"32591eee370000000000000000000000", "64b23ddc6e0000000000000000000000", "c9647bb8dc0000000000000000000000", "92c8f771b80000000000000000000000",
"2591eee3700000000000000000000000", "4b23ddc6e00000000000000000000000", "9647bb8dc00000000000000000000000", "2c8f771b800000000000000000000000",
"591eee37000000000000000000000000", "b23ddc6e000000000000000000000000", "647bb8dc000000000000000000000000", "c8f771b8000000000000000000000000",
"91eee370000000000000000000000000", "23ddc6e0000000000000000000000000", "47bb8dc0000000000000000000000000", "8f771b80000000000000000000000000",
"1eee3700000000000000000000000000", "3ddc6e00000000000000000000000000", "7bb8dc00000000000000000000000000", "f771b800000000000000000000000000",
"eee37000000000000000000000000000", "ddc6e000000000000000000000000000", "bb8dc000000000000000000000000000", "771b8000000000000000000000000000",
"ee370000000000000000000000000000", "dc6e0000000000000000000000000000", "b8dc0000000000000000000000000000", "71b80000000000000000000000000000",
"e3700000000000000000000000000000", "c6e00000000000000000000000000000", "8dc00000000000000000000000000000", "1b800000000000000000000000000000",
"37000000000000000000000000000000", "6e000000000000000000000000000000", "dc000000000000000000000000000000", "b8000000000000000000000000000000",
"70000000000000000000000000000000", "e0000000000000000000000000000000", "c0000000000000000000000000000000", "80000000000000000000000000000000",
]

for i in 0 ..< 128:
doAssert rshifted[i] == toHex(e shr i)
doAssert lshifted[i] == toHex(e shl i)
3 changes: 2 additions & 1 deletion tools/tester.nim
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,8 @@ if file.len == 0:
# XXX: parallel execution of tests is still missing
for (dir, runner) in dirs.items:
for it in walkDir(dir, relative=false):
if it.path.endsWith(".test"):
if it.path.endsWith(".test") or
(it.path.endsWith(".nim") and it.path.extractFilename.startsWith("t")):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other option would be to only do it for the unittest directory, treating it as a specific runner accommodation. This is fine though, just thinking out loud.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the longer term, I think every directory should specify (via some sort of configuration file) what files to consider tests itself, though there's also the real risk of turning the tester into some overly general and complex tool.

inc total
if runTest(runner, it.path.relativePath(currDir)):
inc success
Expand Down
Loading