-
Notifications
You must be signed in to change notification settings - Fork 32
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
Add and test initRandomBigInt for issue #101 #112
base: master
Are you sure you want to change the base?
Conversation
… a fixed number of limbs
… add tests for gcd
I added an enum type. You may find a less generic name than the one I chose. All these changes will be really useful for another benchmarking PR I am working on. It also closes issue #101. |
src/bigints.nim
Outdated
SizeDescriptor* = enum | ||
Limbs, Bits | ||
|
||
proc initRandomBigInt*(number: Natural, unit: SizeDescriptor = Limbs): BigInt = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's unit
supposed to mean here? That doesn't seem to fit at all. Maybe mode: RandomMode
?
src/bigints.nim
Outdated
## Initializes a standalone BigInt whose value is chosen randomly with exactly | ||
## `number` bits or limbs, depending on the value of `unit`. By default, the | ||
## BigInt is chosen with `number` limbs chosen randomly. | ||
if unit == Limbs: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should rather be an exhaustive case
, in case a new variant is added.
src/bigints.nim
Outdated
## BigInt is chosen with `number` limbs chosen randomly. | ||
if unit == Limbs: | ||
if number == 0: | ||
raise newException(ValueError, "A Bigint must have at least one limb !") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it doesn't, no limbs means the value is 0, although that may change in the future ig?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a weird and problematic edge case here :
- with 0 limbs, there is only one value possible, @[]. It is not really "random".
- It means that zeros will appear with more probability than other values if we try to select a random bigint amongst output of random bigints with fixed size limb. Imagine you try to generate a random bigint with r limbs, with r from zero up to 10.
Since there are multiple definitions of zeros. @[], @[0] (isNegative=true), and @[0] (isNegative=false), and two of them are potentially generated, ... - An empty seq @[] does not permit type inference on the type inside the seq, while @[0] does. In fact, this argument is limited it is not clear, if it is an int, a Natural or a Bigint. At least we can guess it is a SomeInt type.
The message is incorrect, I will change it for now, I do not think we should try to generate an empty seq anyway.
src/bigints.nim
Outdated
remainder = number mod 32 | ||
n_limbs = (if remainder == 0: number shr 5 else: number shr 5 + 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remainder = number mod 32 | |
n_limbs = (if remainder == 0: number shr 5 else: number shr 5 + 1) | |
remainder = number shr 5 | |
n_limbs = (if remainder == 0: remainder else: remainder + 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may add a variable for number shr 5 called quotient.
number mod 32 and number shr 5 does not do the same thing!
You changed the remainder definition but not the equality assertion with remainder.
I think you misunderstood these lines, I didn't get your changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right, number shr 5
is number div 32
. But number mod 32
can be turned into number and 31
(which may or may not help, depending on if that optimization is already done).
@@ -1198,3 +1242,4 @@ func powmod*(base, exponent, modulus: BigInt): BigInt = | |||
result = (result * basePow) mod modulus | |||
basePow = (basePow * basePow) mod modulus | |||
exponent = exponent shr 1 | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There already is a trailing newline, isn't there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No there is not!
I added one, It makes the code fits better in the buffer.
Let me start this by saying that I don't think that this should be part of the public API. The proposed function seems to be designed specifically for testing. For this purpose, it looks good to me and I'd be happy to use it for some randomized tests. However, there are a number of problems for the general use case:
A small note: You need to write something like "closes #101" (in the description or a comment, not the title) so that GitHub actually closes the issue when merging the PR. |
Remove an intermediate variable Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
Changed the description of initRandomBigInt Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
Thank you for this review !
That's important to keep in mind for the release of Nim v2, which is promised to be threads on by default.
I totally agree with you on that. Actually, I think this can be implemented on top of this current random function and some rejection sampling. I do not think I should close the issue now, as more random functions for testing have to be added. mratsim proposed two other test functions. |
Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
The easiest solution is to simply move the random stuff out of |
I need the function both in the benchmarks and in the tests which are in two separated folders. I will need to export it anyway.
Currently, it does. I am not sure if I can avoid private membership access. We make the function slower by proceding in two steps, and takes twice as much memory (since the same seq is created twice, one for the generation of the random number and one for the initialisation of bigint). I think the code needs refactoring anyway: everything is stored in a single monolithic file. |
It can just be a separate module though (e.g. in
It shouldn't copy the
Agreed. |
@konsumlamm I have moved away the new function initRandomBigInt in a new file in bigints folder called utilities.nim, as requested. |
How about to add |
I did my own implementation (below), before I realized you already wrote one.
For optimization purposes, if it's undesirable to construct ancillary
|
No problem here, it is always good to have a comparison.
You missed my code motivation here. I wanted to have a helper function for the library development and unit testing first and foremost. I need to generate both, there are slight variations in the generation between the two, and I don't expose it in the public API (unless there is an error).
Quick comments on the code: Why do you move out the conditional from the while loop ? while true:
...
if result <= spread:
break I still have to look closely, but the code looks simpler than mine, and I have carefully tried many problematic edge cases. @demotomohiro I forgot! Glad to learn about thread safety, I have an applied mathematics degree and don't know much yet about thread safety. Sorry, but I have very few time to spare on the library. Finally, I think that the function should have at least some test for the shape of sample's distribution (to check if its uniform). |
Aha! Yes, I did miss your intention :-).
I've since discovered
I'm not sure I understand the question. I would use a do-while loop, but Nim doesn't support them. The loop body generates a uniformly distributed random number in the range import bigints
import std/sequtils
import std/options
import std/random
proc rand(r: var Rand, x: Slice[BigInt]): BigInt =
assert(x.a <= x.b, "empty slice")
let spread = x.b - x.a
if spread == 0'bi:
return x.a
let
nbits = spread.fastLog2
nFullLimbs = nbits div 32
maxHighDigit = (spread shr (nFullLimbs*32)).toInt[:uint32].get()
while true:
var digits = newSeqWith(nFullLimbs, r.rand(uint32.low..uint32.high))
digits.add(r.rand(uint32.low..maxHighDigit))
result = initBigInt(digits)
if result <= spread:
break
result += x.a
proc rand(x: Slice[BigInt]): BigInt = rand(randState(), x) |
Oh, I see. First, excuse me, my message was rude. I think your code would actually be a great addition to the lib. I thought the slice would be harder, but like you said, you just use rejection sampling. If you do another PR with a bit of testing and make your function public, I'll review and approve it. |
I added random as std dependency.
It is now possible to generate a BigInt which is exactly nbits long.
I also added a file for tests specific for probabilistic tests.