diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d10bbf5..dc32e5b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,19 +14,48 @@ jobs: - run: echo "commit message doesn't contain '[skip ci]'" test: + needs: before runs-on: ubuntu-latest strategy: + fail-fast: false matrix: nim-version: - '1.4.8' - - '1.6.12' + - '1.6.16' + - '2.0.0' - 'devel' - needs: before steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: jiro4989/setup-nim-action@v1 with: nim-version: ${{ matrix.nim-version }} - run: nimble install -y - run: nimble test - run: nimble checkExamples + + i386: + needs: before + runs-on: ubuntu-latest + container: + image: i386/ubuntu:bionic + strategy: + fail-fast: false + matrix: + nim-version: + - '1.4.8' + - '1.6.16' + - '2.0.0' + steps: + - uses: actions/checkout@v1 + - name: Setup + run: | + apt-get update -y + apt-get install -y curl gcc g++ git xz-utils + git config --global --add safe.directory /__w/bigints/bigints + - name: Install Nim + run: | + curl -sSf https://nim-lang.org/download/nim-${{ matrix.nim-version }}-linux_x32.tar.xz | tar -xJ + realpath nim-${{ matrix.nim-version }}/bin >> $GITHUB_PATH + - run: nimble install -y + - run: nimble test + - run: nimble checkExamples diff --git a/readme.md b/readme.md index ed1d387..00271b6 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,6 @@ nimble install https://github.com/nim-lang/bigints ## Current limitations and possible enhancements -* not expected to work on 32 bit * arithmetic operations such as addition, multiplication and division are not optimized for performance (e.g. [Karatsuba multiplication](https://en.wikipedia.org/wiki/Karatsuba_algorithm) is not implemented) diff --git a/src/bigints.nim b/src/bigints.nim index a50e9b0..333ab1a 100644 --- a/src/bigints.nim +++ b/src/bigints.nim @@ -31,7 +31,7 @@ func initBigInt*(vals: sink seq[uint32], isNegative = false): BigInt = ## Initializes a `BigInt` from a sequence of `uint32` values. runnableExamples: let a = @[10'u32, 2'u32].initBigInt - let b = 10 + 2 shl 32 + let b = 10 + 2'u64 shl 32 assert $a == $b result.limbs = vals result.isNegative = isNegative diff --git a/src/bigints/random.nim b/src/bigints/random.nim index e4c7058..d49a79c 100644 --- a/src/bigints/random.nim +++ b/src/bigints/random.nim @@ -3,9 +3,27 @@ import std/sequtils import std/options import std/random +# workaround for https://github.com/nim-lang/Nim/issues/16360 +proc rand(r: var Rand, max: uint64): uint64 = + if max == 0: + return 0 + elif max == uint64.high: + return r.next() + else: + var iters = 0 + while true: + let x = next(r) + # avoid `mod` bias + if x <= uint64.high - (uint64.high mod max) or iters > 20: + return x mod (max + 1) + else: + inc iters + func rand*(r: var Rand, x: Slice[BigInt]): BigInt = ## Return a random `BigInt`, within the given range, using the given state. assert(x.a <= x.b, "invalid range") + if x.a == x.b: + return x.a let spread = x.b - x.a # number of bits *not* including leading bit @@ -13,15 +31,15 @@ func rand*(r: var Rand, x: Slice[BigInt]): BigInt = # number of limbs to generate completely randomly nFullLimbs = max(nbits div 32 - 1, 0) # highest possible value of the top two limbs. - hi64Max = (spread shr (nFullLimbs*32)).toInt[:uint64].get() + hi64Max = (spread shr (nFullLimbs * 32)).toInt[:uint64].get() while true: # these limbs can be generated completely arbitrarily - var limbs = newSeqWith(nFullLimbs, r.rand(uint32.low..uint32.high)) + var limbs = newSeqWith(nFullLimbs, uint32(r.next() and uint32.high)) # work around https://github.com/nim-lang/Nim/issues/16360 # generate the top two limbs more carefully. This all but guarantees # that the entire number is in the correct range - let hi64 = r.rand(uint64.low..hi64Max) - limbs.add(cast[uint32](hi64)) - limbs.add(cast[uint32](hi64 shr 32)) + let hi64 = r.rand(hi64Max) + limbs.add(uint32(hi64 and uint32.high)) + limbs.add(uint32(hi64 shr 32)) result = initBigInt(limbs) if result <= spread: break diff --git a/tests/trandom.nim b/tests/trandom.nim index c7f2b7e..f34070e 100644 --- a/tests/trandom.nim +++ b/tests/trandom.nim @@ -14,9 +14,13 @@ block: # check uniformity doAssert(lo <= r) doAssert(r <= hi) total += r - let iBucket = (r-lo) div ((hi-lo) div initBigInt(nbuckets)) + let iBucket = (r - lo) div ((hi - lo) div initBigInt(nbuckets)) buckets[iBucket.toInt[:int]().get()] += 1 for x in buckets: - doAssert(trials/nbuckets*0.5 < float(x)) - doAssert(float(x) < trials/nbuckets*1.5) + doAssert(trials / nbuckets * 0.5 < float(x)) + doAssert(float(x) < trials / nbuckets * 1.5) +block: # single element range + let x = 1234567890.initBigInt + for _ in 1..100: + doAssert rand(x..x) == x