nim-lang / bigints Goto Github PK
View Code? Open in Web Editor NEWBigInts for Nim
License: MIT License
BigInts for Nim
License: MIT License
The internal functions currently take a var BigInt
argument as first parameter, which is used to store the result (essentially an out parameter). I suppose that is so that a buffer can be preallocated, however, that's never done afaict. In this light, it would be more ergonomic to just return a new BigInt
(using the result
variable).
Another possibility is instead of defining functions like func addition(a: var BigInt, b, c: BigInt)
to do a = b + c
, use func addition(a: var BigInt, b: BigInt)
to do a += b
. That would avoid copying the first argument for +=
. a + b
would then be implemented as result = a; result += b
, but I'm not sure if that avoids a copy (I think it should, at least with ARC/ORC). (I'm just using addition as an example, the same applies to other operations).
If there is no flaw in my analysis, I'd prefer the last possibility, as I think it's the most efficient and ergonomic.
Currently, BigInt
is defined as follows:
type
BigInt* = object
limbs: seq[uint32]
isNegative: bool
It has the invariant that limbs.len >= 1
.
It might be worth it changing that to
type
BigInt* = object
limbs: seq[uint32]
first: uint32
isNegative: bool
This has several advantages:
BigInt
s require no allocation & pointer indirection.Disadvantages:
The readme states this under Current limitations and possible enhancements, but I don't see any obvious reason why this is the case. Could anyone elaborate on what exactly breaks on 32 bit platforms?
It seems that bigints, as implemented here, are unsigned. I tried to do
let x = -initBigInt(1)
but it does not compile. Implementing unary negation as
proc `-`*(a: BigInt): BigInt = initBigInt(0) - a
leads to SIGSEGV: Illegal storage access
.
Is there any reason why BigInts are implemented as positive numbers?
It has been almost a year since last release of BigInts. I would like to prioritize issues, and know which features are expected for the next release (next tag) of BigInt. There are no labels put on issues right now.
I noticed there is almost no information for new developer to know what the developers are currently working on. I fear we might be multiple people working on the same thing in the future.
Here I will manage a list, that presents some features in other multiprecision libraries that is not yet implemented here, and I will try to link the current issues to that list. Please add a comment if the list is not up-to-date or you want another feature to implement (in addition to a new issue concerning that feature). Could you tell me which one you expect to see in BigInts 0.6.0, in BigInts 0.7.0, or in BigInts 1.0.0 ?
not
The latest commit does not pass due to to an error with the arc garbage collector and the modular inverse function invmod.
It fails at the very first test of the function line 388.
How can I debug the invmod function to fix it ?
I would like a function to generate a uniformly (as much as possible) random BigInt inferior to some upper bound.
This would enable to randomize tests.
import bigints
var a = initBigint("6277101735386680763835789123314955362437298222279840143829")
var b = initBigint("1461501637330902918203684832716283019655932313743")
var r = a.div(b)
Output:
Error: unhandled exception: /home/jhg/.nimble/pkgs/bigints-1.0.0/bigints.nim(652, 14) `q1 <= uint32.high`
Using latest repository version of bigints
, Nim 1.6.10 on Linux x64.
Miller-Rabin test is a polynomial probabilistic test to determine wheter an integer is prime. It is way faster than deterministic algorithm (even if they are polynomial) primality test.
Here are some implementations of what I would like :
Miller-Rabin test in Nim, by @mratsim :
https://github.com/mratsim/nim-project-euler/blob/master/src/lib/primes.nim
GMP optimized version :
https://gmplib.org/repo/gmp/file/tip/mpz/millerrabin.c
A primality test can then lead to algorithms for factoring integers, finding the next prime, improve prime sieving …
Another question this issue raises, is what do we want from this library to do ? Should it also be an arithmetic library ?
Since the limbs
attribute of BigInt
type is private, it might be complex to make another arithmetic library on top of this one (less optimizations on big integers due to less access to the internal representation).
when isMainModule:
a = "1780983279228119273110576463639172624".initBigInt
b = "1843917749452418885995463656480858321".initBigInt
echo a.limbs.len
echo b.limbs.len
echo a*b
outputs :
4
4
3283986680046702618742503890385314117448805445290098330749803441805804304
It should output :
4
4
3171901440890145063107180402349133639481893332927709969425239467271045376
Result obtained with SageMath, with Pari/GP and Wolfram/Alpha.
There are no tests for multiplication with more than two or three limbs.
Currently, e.g. +=
is defined as
template `+=`*(a: var BigInt, b: BigInt) =
a = a + b
This causes a
to be evaluated twice, which can produce unexpected results, when it's an expression that can cause side effects. For example
proc lol(x: var BigInt): var BigInt =
echo "hello ", x
x
var a = 42.initBigInt
lol(a) += 1.initBigInt
prints hello 42
twice, instead of just once.
The solution would be to just make it a func
instead. The same applies to -=
and *=
.
Hello, I wanted to give this repo a quick heads-up that nimdocs.com, which @treeform and I have been running, is going away in favor of using Github Actions + Github Pages.
nimdocs.com is currently linked to from this repo's readme for documentation.
My suggestion:
Add this Github Actions workflow file: https://github.com/treeform/nimtemplate/blob/master/.github/workflows/docs.yml (no need to change anything) to automatically generate docs in a branch called gh-pages. This branch will have the Docs content updated each push.
In the repo settings Page tab, you can then select that branch to serve (once the job has run once).
After that, https://nim-lang.github.io/bigints/ should be serving the most recently generated docs.
This is simpler than us running a server and all of that stuff and just as easy for repo maintainers so I think it is a better model.
I'll keep the server up for a bit yet but it is going away.
Currently, there are inc
, dec
, succ
, pred
for adding/subtracting an int
. Internally, there are also functions for comparing with int32
.
We should add more functions for working with BigInt
s and SomeInteger
s, to avoid having to construct BigInt
s (and thus allocating) for small numbers. This would make sense for at least ==
, <
, +
, -
, *
, div
(in the second argument), mod
(in the second argument).
Apparently CircleCI no longer supports projects with a 1.0 configuration.
Service alert: Your project references CircleCI 1.0 or it has no configuration. CircleCI 1.0 and projects without configuration files are no longer supported. You must update your project to use CircleCI 2.0 configuration to continue.
For reasons to do with investigating testing Nim code I am writing factorial with a number of different algorithms. For the external iterative version, I have the statement:
for i in two .. n:
result *= i
where two and n are BigInt values, but the compiler tells me:
factorial.nim(13, 15) Info: template/generic instantiation from here
lib/system.nim(1581, 4) Error: type mismatch: got (BigInt)
but expected one of:
system.inc(x: var T, y: int)
the for statement is line 13. Does this mean ranges of BigInt are not supported out of the box?
Hi, thanks for this library, its great!
While playing with factorials though, I think I've found an odd bug. Here's the reduced case:
import bigints
let nums = [ "68855123440532288245010625",
"201850901852714536181760000",
"435980903974422631450250625",
"824199001261152424427520000",
"11527258048987096618327125",
"18960243520191483654144000" ]
var total = 1.initBigInt
for e in items(nums):
let bigInt = e.initBigInt
stdout.write bigInt, " * ", total
total *= bigInt
echo " = ", total
output:
68855123440532288245010625 * 1 = 68855123440532288245010625
201850901852714536181760000 * 68855123440532288245010625 = 13898468763651426950178424411236763123358131200000000
435980903974422631450250625 * 13898468763651426950178424411236763123358131200000000 = 6059466975437025204783660349002445834010410572229490978416137871632000000000000
824199001261152424427520000 * 6059466975437025204783660349002445834010410572229490978416137871632000000000000 = 0
11527258048987096618327125 * 0 = 0
18960243520191483654144000 * 0 = 0
Obviously multiplying two positive integers shouldn't result in zero. The following works fine:
echo "824199001261152424427520000".initBigInt * "6059466975437025204783660349002445834010410572229490978416137871632000000000000".initBigInt
So i assume this is the result of consecutive mutations to total
.
It would be useful to add some benchmarks, so that we can measure performance improvements/degradations for changes.
The compiler doesn't seem to be able to find some functions such as invmod
, toInt
or powmod
.
An example from the documentation:
import pkg/"bigints"
assert invmod(3.initBigInt, 7.initBigInt) == 5.initBigInt
Throws an error when compiling...
Error: undeclared identifier: 'invmod'
Everything looks fine in the source , does anybody knows what is going on?
As written in the Readme.md file, we need to implement fast multiplication to improve many different algorithms (at once).
I prefer to write a Github issue, as it is easier to gather good implementations and documentation of fast multiplication algorithms and issue enables to track progress with PRs.
The gmp section 15 algorithms describes fast multiplication algorithms : https://gmplib.org/gmp-man-6.2.1.pdf
I guess we might want to it differently than gmp and take into account other multi-precision arithmetic library . Especially the tresholds at which we switch from one algorithm to another might be different than GMP (because internal representation is different).
Squaring is faster than generic multiplication. We might want to take this into account for pow and powmod multiplication (e.g. montgomery squaring algorithm).
I am also surprised in-place multiplication is realized with a = a * b. Can we optimize this case as to reduce memory and copy operations ?
Analogous to -
for negative numbers, we could also support a leading +
, which would be ignored. Another thing that would be nice is supporting _
(which also get ignored), akin to normal int literals:
let a = initBigInt("+10_000_000_000")
Example:
import bigints
echo toSignedInt[int64](-initBigInt(0xFFFFFFFF_00000000'u64))
This currently prints some(-9223372036854775808)
, but the expected result is none(int64)
.
The reason is the following lines:
Lines 817 to 819 in 18e3899
x.limbs[1] == uint32.high shr 1 + 1
, otherwise x
is out of range.Hi,
Thanks for a great library. I'm running into the following issue:
import bigints
proc factorial(accum: BigInt, i : int) : BigInt =
if i == 1: accum
else: factorial(accum * initBigInt(i), i - 1)
const fact1000 = factorial(initBigInt(1),1000)
echo fact1000
generates the following compilation error:
Verifying dependencies for [email protected]
Reading official package list
Checking for bigints@>= 0.4.1
Info: Dependency on bigints@>= 0.4.1 already satisfied
Verifying dependencies for [email protected]
Building factorial/main using c backend
Error: Build failed for package: factorial
... Details:
... Execution failed with exit code 1
... Command: "/home/deech/Downloads/Nim/bin/nim" c --noBabelPath --path:"/home/deech/.nimble/pkgs/bigints-0.4.1" -o:"/home/deech/Nim/factorial/main" "/home/deech/Nim/factorial/main.nim"
... Output: Hint: used config file '/home/deech/Downloads/Nim/config/nim.cfg' [Conf]
... Hint: system [Processing]
... Hint: main [Processing]
... Hint: bigints [Processing]
... Hint: strutils [Processing]
... Hint: parseutils [Processing]
... Hint: math [Processing]
... Hint: algorithm [Processing]
... SIGSEGV: Illegal storage access. (Attempt to read from nil?)
But change the const
to a var
and everything works fine. Is compile time evaluation with this library supported?
Thanks!
Since BigInt's limb parameter has become private, thus we can not easily cast BigInts with absolute value less than 2^32
to an int
or even a uint32
.
The only workaround yet is to use the keyword cast[int]
. But this does not always do what we want.
To get only the smallest limb, we also have to define a mask like 2^32 - 1
and to apply an ineffective and
.
Can we implement some functions like … ?:
toUint32(a: BigInt): uint32
returns the only limb for BigInts between 1-2^32 and +2^32-1 (small BigInts).toInt(a: BigInt): int
cast the BigInt if it is small enough, into an int, according to the sign of BigInt, with correct representation (2's complement) and raises a ValueError if the cast can not be done (if int is stored on 32 bits, it may not fit if the heavy weight bit of the uint32 corresponding to first limb is 1).toUint64(a: BigInt): uint64
returns the uint64 made up of one to two limbs, if the BigInt is small enough.toFloat
like described in the similar issue #34.I need to convert BigInt
to float64
and I'm not sure I'm qualified to implement it.
I'm using this:
proc toBiggestFloat(a: BigInt): BiggestFloat =
for i in countdown(a.limbs.high, 0):
result = result * BiggestFloat(1 shl 32) + a.limbs[i].BiggestFloat
if Negative in a.flags:
result = - result
But it gives me different results from a Haskell implementation as it get close to saturating the float at 179769313486231560835325876058105298516207002341652166261661174625869553267292326574530099287946549246750631490335877017522087105926987962906277604735569213290190919152394180476217125334960946356387261286640198029037799514183602981511756283727771403830521483963923935633133642802139091669457927874464075218945.
As far as I can tell, all proc
s could be turned into func
s, providing the guarantee that they don't have side effects. Is there anything or anyone opposing this?
I have tested the examples but could not compile some of them probably due to some optional dependencies like unsigned in the pidigits example.
Is it an old file of the library that has been removed, or an external library ?
In the latter case, it should be added to nimble optional dependencies list if nimble has such an option.
Furthermore, some syntax has become obsolete like .. <
that is now ..<
.
In order to improve modular exponentiation or to write other higher-order functions (functions that do not rely on BigInts internal representation), we might want to count the number of significant bits (bits after leading zeros, without the sign bit in complement representation of signed integers) or at least the logarithm in base 2 of the number.
We can determine the number of significant bits from the logarithm in base 2 as:
significantBits(n) = floor(log_2(n)) + 1
func significantBits*(n: BigInt): int =
if n < 0:
return significantBits(-n)
fastLog2(n.limbs.high) + 32*(n.limbs.len-1)
How would you optimize the negative case ? Can we just take the two’s complement of the highest limb and then compute the fastLog2(n.limbs.high) ?
func significantBits*(n: BigInt): int =
var highest_limb = n.limbs.high
if n < 0:
highest_limb = not n.limbs.high + 1
fastLog2(highest_limb) + 32*(n.limbs.len-1)
I experimented a bit with running the tests on the JS backend, but I came to the conclusion that this is currently not viable. The problem is nim-lang/Nim#4714: 64 bit integers are broken on the JS backend, since they're implemented as floating point values.
We could use std/jsbigints
on the JS backend, but I'd rather not do that, since it has a different API, which means we'd have to implement some functions twice. Apart from that, this library wouldn't be a pure Nim implementation anymore then.
bigints.nimble does not conform ini file format
it should use '=' not ':' as name-value seperator
Calls like initBigInt("2", 2)
succeed.
It seems to me that BigInt
s are always normalized (i.e. the highest limb is never 0, except if the number is 0), however, this isn't documented.
If this is the case, I think at least isZero(n)
could be optimized to just check n.limbs.len == 1 and n.limbs[0]
(it currently uses a loop).
In the README, there is a mention about a bitwise not
operator. I would like to understand the use case of such an operator.
Is it not sufficient to just invert the bits of each limb ?
Should it modify the sign of the BigInt ? The previously implemented operators raises an exception if at least one of the inputs is negative.
Here is how I would code the operator not
:
func `not`*(a : BigInt): BigInt =
## Bitwise `not` for `BigInt`s.
runnableExamples:
let
a = 7.initBigInt
assert not a == 8.initBigInt
assert (not a.isNegative)
result.limbs.setLen(a.limbs.len)
for i in 0 ..< a.limbs.len:
result.limbs[i] = not result.limbs[i]
the fix for #27 done in #35 introduced a copy in setXLen
that has some impact in the performance in some cases (see benchmark on chainAddition in this comment).
In principle it is possible to fix the issue in self addition without introducing a new copy but it requires more effort.
Note that legacy usage of setXLen
remains accessible through a compile time switch bigintsLegacySetXLen
.
The behaviour of div
and mod
is currently inconsistent with the respective operators for int
s. For example:
import bigints
func divmod(a, b: int): tuple[q, r: int] =
(q: a div b, r: a mod b)
echo "int:"
echo " divmod(1, 2): ", divmod(1, 2)
echo " divmod(1, -2): ", divmod(1, -2)
echo " divmod(-1, 2): ", divmod(-1, 2)
echo " divmod(-1, -2): ", divmod(-1, -2)
echo "BigInt:"
echo " divmod(1, 2): ", divmod(1'bi, 2'bi)
echo " divmod(1, -2): ", divmod(1'bi, -2'bi)
echo " divmod(-1, 2): ", divmod(-1'bi, 2'bi)
echo " divmod(-1, -2): ", divmod(-1'bi, -2'bi)
prints the following:
int:
divmod(1, 2): (q: 0, r: 1)
divmod(1, -2): (q: 0, r: 1)
divmod(-1, 2): (q: 0, r: -1)
divmod(-1, -2): (q: 0, r: -1)
BigInt:
divmod(1, 2): (q: 0, r: 1)
divmod(1, -2): (q: -1, r: -1)
divmod(-1, 2): (q: -1, r: 1)
divmod(-1, -2): (q: 0, r: -1)
For int
s, division rounds towards 0 and the sign of a mod b
is the sign of a
. Meanwhile, for BigInt
s, division rounds towards negative infinity and the sign of a mod b
is the sign of b
.
I suggest we change the behavior of BigInt
s to match that of int
s.
@def- since you implemented this, did you have a specific reason for implementing it this way?
is there a reason and
and or
are not implemented? If this library is fast enough, I have a use-case, but I would need those operations.
template/generic instantiation of `toInt` from here
Error: cannot instantiate: 'T'
# 示例
var charset = "abc"
let length = 2
let startFrom = "ab"
let endAt = "bc"
let startNumber: BigInt = getStartNumber(startFrom, charset, length)
let endNumber: BigInt = getEndNumber(endAt, charset, length)
for i in startNumber..endNumber:
echo fromDecimal(charset, i.toInt, length)
My Nim version is nim-1.6.4 and the bigints library version is bigints-1.1.0.
Originally, reported in FedericoCeratto/nim-package-directory#50, the package directory incorrectly lists this package as 0.5.0. As per Nimble's documentation, the version number needs to be bumped via git tag.
Hi Dennis,
Forgive me if I have misunderstood a reason for this, but I noticed when using subtraction to zero (or addition on equal magnitude but opposite signed BigInts), the resulting zero value always has Negative flags. EDIT: I added an example using multiplication, as I also noticed the same behaviour.
My expectation was that a zero value would normally be given as a positive zero by default, as that is typically how I would expect a native integer type to behave.
The negative flag can be worked around if you are aware of it, but I am not sure it is obvious or expected behaviour for the end user.
For example:
var
a = initBigInt("828478292990482")
b = initBigInt(9283)
c = initBigInt("-828478292990482")
d = initBigInt(-9283)
e = initBigInt(0)
echo b + d
echo a + c
echo d + b
echo c + a
echo b - b
echo d - d
echo d * e
# Outputs:
-0
-0
-0
-0
-0
-0
-0
Is there any reason for the result to always be given a negative sign, or are you happy for this to default to positive (un-flagged) zero?
Thank you
expected to work:
import bigints
var
a = 0.initBigInt
b = "359097073186387306".initBigInt
a = a + b # Error: unhandled exception: index out of bounds, the container is empty [IndexError]
The above seems to work if b < uint32.high
.
With a temporary variable it works:
import bigints
var
a = 0.initBigInt
b = "359097073186387306".initBigInt
t: BigInt
# with a temporary variable t it works
t = a + b
a = t
echo a
for context where this came from, see https://forum.nim-lang.org/t/6327
If you convert a string consisting of a single minus sign to a BigInt you get zero.
import bigints
let x = "-".initBigInt
echo x
This program prints
0
BigInt objects don't work with the rationals module, because initRational expects SomeInteger which BigInt isn't. Looking through the code for rationals, it looks like the only function it needs that bigints doesn't have is lcd. What would be the best way to go about using bigints with rational numbers? Could BigInt be marked as SomeInteger with some tweaking?
We wanted to use this library in a project, but it doesn't have a LICENSE.
Currently, the bitwise operators (and
, or
, xor
) only work for non-negative numbers. I suggest also supporting negative numbers, treating them as if they were represented using 2's complement (the standard representation for signed integers). This would also allow implementing not x
, by simply doing -x - 1
.
Consider the following:
var
a: BigInt
b: BigInt = 12.initBigInt
echo a*b
This results in the following stack trace:
[...]/scratch.nim(7) scratch
[...].nimble/pkgs/bigints-0.4.3/bigints.nim(484) *
[...].nimble/pkgs/bigints-0.4.3/bigints.nim(394) multiplication
[...].choosenim/toolchains/nim-#devel/lib/system/fatal.nim(39) sysFatal
Error: unhandled exception: index out of bounds, the container is empty [IndexError]
This is because a.limbs
is @[]
. Would it be possible for an uninitialized BigInt
to default to limbs = @[0]
, to be consistent with nim integers? Otherwise, maybe there could be a flag initialized
that is checked before trying to operate on a BigInt
, so we get slightly more readable errors. I'm not super proud about it, but tbh this took me a while to debug. To be fair, my actual code was slightly more complex and resulted in a zero BigInt not being initialized in a corner case.
Running nimble test
using nim 0.20.2 results in multiple RangeErrors.
Hint: /Users/me/Development/mpc/nim-bigints/tests/tester [Exec]
/Users/me/Development/mpc/nim-bigints/tests/tester.nim(43) tester
/Users/me/Development/mpc/nim-bigints/tests/tester.nim(32) chk
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(30) initBigInt
/Users/me/.choosenim/toolchains/nim-0.20.2/lib/system/fatal.nim(39) sysFatal
Unhandled exception: value out of range [RangeError]
[FAILED] initBigInt
[OK] range of bigint (https://github.com/def-/nim-bigints/issues/1)
[OK] multiple multiplications (https://github.com/def-/nim-bigints/issues/3)
[OK] negative bigint (https://github.com/def-/nim-bigints/issues/4)
/Users/me/.choosenim/toolchains/nim-0.20.2/lib/pure/unittest.nim(622) tester
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(742) div
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(681) division
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(636) unsignedDivRem
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(308) addition
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(249) unsignedSubtraction
/Users/me/.choosenim/toolchains/nim-0.20.2/lib/system/fatal.nim(39) sysFatal
Unhandled exception: value out of range [RangeError]
[FAILED] off by one in division (https://github.com/def-/nim-bigints/issues/5)
/Users/me/.choosenim/toolchains/nim-0.20.2/lib/pure/unittest.nim(622) tester
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(321) +
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(311) addition
/Users/me/Development/mpc/nim-bigints/src/bigints.nim(249) unsignedSubtraction
/Users/me/.choosenim/toolchains/nim-0.20.2/lib/system/fatal.nim(39) sysFatal
Unhandled exception: value out of range [RangeError]
[FAILED] negative zero flags (https://github.com/def-/nim-bigints/issues/16)
To showcase what can be done with the library, as well to benchmark it, I propose to add examples for factorization with simple exponential algorithms like rho-pollard, p-1 or p+1.
We would need for these gcd and powmod algorithms currently in PR #65 and #66.
We could also improve/adapt the algorithm published on Rosetta Code for Shanks-Tonelli . Some examples could not be computed with only nim's integer native library and have a need for multi-precision arithmetic.
Too much examples might make the library size big, but they help to define a good API, to understand which feature are essential and which one needs optimization.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.