Generating a repeatable pseudo-random number based coordinates - algorithm

I need to generate repeatable pseudo random numbers based on a set of coordinates, so that with a given seed, I will always generate the same value for a specific coordinate.
I figured I'd use something like this for the seed:
/* 64bit seed value*/
struct seed_cord {
uint16 seed;
uint16 coord_x_int;
uint16 coord_y_int;
uint8 coord_x_frac;
uint8 coord_y_frac;
}
Where coord_x_int is the integer part of the coordinate, and the fraction part is given by coord_x_frac / 0xFF. seed is a randomly pre-determined value.
But I must admit, trying to understand all the intricacies of PRNGs is a little overwhelming. What would be a good generator for what I'm attempting?
I tested out Java's PRNG using using this scheme in a quick groovy script, with the following result:
Obviously, this is hardly decent randomness.
The script I used was:
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
short shortSeed = new Random().next(16) as short
def image = new BufferedImage(512, 512, BufferedImage.TYPE_BYTE_GRAY)
def raster = image.getRaster()
//x
(0..1).each{ x ->
(0..255).each{ xFrac ->
//y
(0..1).each{ y ->
(0..255).each{ yFrac ->
long seed = (shortSeed as long) << 48 |
(x as long) << 32 |
(y as long) << 16 |
(xFrac as long) << 8 |
(yFrac as long)
def value = new Random(seed).next(8)
raster.setSample( (x? xFrac+256 : xFrac), (y? yFrac+256 : yFrac), 0 , value)
}}}}
ImageIO.write(image, "PNG", new File("randomCoord.png"))

If you're really only looking at 512x512, then that's uh... 218 pixels you're interested in.
There's plenty of space for that kind of population with good ole MD5 (128 bit output).
You can just take the lowest 32 bits for an integer if that's the kind of output you need. Really, any sort of hashing algorithm that has an output space at least as large as an int will do.
Now, you can do all sorts of fun stuff if you're paranoid. Start with a hash of your coordinates, then feed the result into a secure random number generator (java.security.SecureRandom). Then hash it 1000 times with a salt that's your birthday concatenated (x+y) times.
Joking aside, random number generators don't necessarily have wildly varying results based on small variations of the seed. They're designed to have a really, super duper long chain of generated numbers before they start repeating, while having those chains pretty evenly distributed among the number space.
On the other hand, the SecureRandom is designed to have the additional feature of being chaotic in regard to the seed.

Most languages have a PRNG package (or two) that lets you initialize the generator with a specific seed. PRNGs can also often be found as part of a larger cryptographic package; they tend to be a bit stronger than those found in general-purpose libraries.

I would take a program like this one I have created and then modify it to pick coordinates:
REM $DYNAMIC
COMMON SHARED n%, rbuf%, sz%, sw%, p1$
DECLARE SUB initialize ()
DECLARE SUB filbuf ()
DECLARE SUB setup ()
DECLARE FUNCTION Drnd# ()
DECLARE SUB core ()
DECLARE SUB modify ()
DIM SHARED pad1(340) AS STRING * 1
DIM SHARED trnsltr(66) AS STRING * 1 ' translates a 0-67 value into a pad character
DIM SHARED trnslt(255) AS INTEGER 'translates a pad value to 0-67 value -1 if error
DIM SHARED moders(26) AS INTEGER 'modding function prim number array
DIM SHARED moders2(26) AS INTEGER 'modding function prim number array
DIM SHARED ranbuf(1 TO 42) AS DOUBLE 'random number buffer if this is full and rbuf %>0
REM then this buffer is used to get new random values
REM rbuf% holds the index of the next random number to be used
REM subroutine setup loads the prime number table
REM from the data statements to be used
REM as modifiers in two different ways (or more)
REM subroutine initialize primes the pad array with initial values
REM transfering the values from a string into an array then
REM makes the first initial scrambling of this array
REM initializing pad user input phase:
CLS
INPUT "full name of file to be encrypted"; nam1$
INPUT "full name of output file"; nam2$
INPUT "enter password"; p2$
rbuf% = 0
n% = 0: sw% = 0
p3$ = STRING$(341, "Y")
p1$ = "Tfwd+-$wiHEbeMN<wjUHEgwBEGwyIEGWYrg3uehrnnqbwurt+>Hdgefrywre"
p1$ = p2$ + p1$ + p3$
PRINT "hit any key to continue any time after a display and after the graphic display"
p1$ = LEFT$(p1$, 341)
sz% = LEN(p1$)
CALL setup
CALL initialize
CLS
ibfr$ = STRING$(512, 32)
postn& = 1
OPEN nam1$ FOR BINARY AS #1
OPEN nam2$ FOR BINARY AS #2
g& = LOF(1)
max& = g&
sbtrct% = 512
WHILE g& > 0
LOCATE 1, 1
PRINT INT(1000 * ((max& - g&) / max&)) / 10; "% done";
IF g& < 512 THEN
ibfr$ = STRING$(g&, 32)
sbtrct% = g&
END IF
GET #1, postn&, ibfr$
FOR ste% = 1 TO LEN(ibfr$)
geh% = INT(Drnd# * 256)
MID$(ibfr$, ste%, 1) = CHR$(geh% XOR ASC(MID$(ibfr$, ste%, 1)))
NEXT ste%
PUT #2, postn&, ibfr$
postn& = postn& + sbtrct%
g& = g& - sbtrct%
WEND
CLOSE #2
CLOSE #1
PRINT "hit any key to exit"
i$ = ""
WHILE i$ = "": i$ = INKEY$: WEND
SYSTEM
END
DATA 3,5,7,9,11,13,17,19
DATA 23,29,33,37,43,47
DATA 53,59,67,71,73,79,83
DATA 89,91,97,101,107,109
DATA 43,45,60,62,36
REM $STATIC
SUB core
REM shuffling algorythinm
FOR a% = 0 TO 339
m% = (a% + 340) MOD 341: bez% = trnslt(ASC(pad1(340)))
IF n% MOD 3 = 0 THEN pad1(340) = trnsltr((2 * trnslt(ASC(pad1(a%))) + 67 - trnslt(ASC(pad1(m%)))) MOD 67)
IF n% MOD 3 = 1 THEN pad1(340) = trnsltr((2 * (67 - trnslt(ASC(pad1(a%)))) + 67 - trnslt(ASC(pad1(m%)))) MOD 67)
IF n% MOD 3 = 2 THEN pad1(340) = trnsltr(((2 * trnslt(ASC(pad1(a%))) + 67 - trnslt(ASC(pad1(m%)))) + moders(n% MOD 27)) MOD 67)
pad1(a% + 1) = pad1(m%): n% = (n% + 1) MOD 32767
pad1(a%) = trnsltr((bez% + trnslt(ASC(pad1(m%)))) MOD 67)
NEXT a%
sw% = (sw% + 1) MOD 32767
END SUB
FUNCTION Drnd#
IF rbuf% = 0 THEN
CALL core
CALL filbuf
IF sw% = 32767 THEN CALL modify
END IF
IF rbuf% > 0 THEN yut# = ranbuf(rbuf%)
rbuf% = rbuf% - 1
Drnd# = yut#
END FUNCTION
SUB filbuf
q% = 42: temp# = 0
WHILE q% > 0
FOR p% = 1 TO 42
k% = (p% - 1) * 8
FOR e% = k% TO k% + 7
temp# = temp# * 67: hug# = ABS(trnslt(ASC(pad1(e%)))): temp# = temp# + hug#
NEXT e%
IF temp# / (67 ^ 8) >= 0 AND q% < 43 THEN
ranbuf(q%) = temp# / (67 ^ 8): q% = q% - 1
END IF
temp# = 0
NEXT p%
WEND
rbuf% = 42
END SUB
SUB initialize
FOR a% = 0 TO 340
pad1(a%) = MID$(p1$, a% + 1, 1)
NEXT a%
FOR a% = 0 TO 340
LOCATE 1, 1
IF a% MOD 26 = 0 THEN PRINT INT((340 - a%) / 26)
sum% = 0
FOR b% = 0 TO 340
qn% = INT(Drnd# * 81)
op% = INT(qn% / 3)
qn% = qn% MOD 3
IF qn% = 0 THEN sum% = sum% + trnslt(ASC(pad1(b%)))
IF qn% = 1 THEN sum% = sum% + (67 + 66 - trnslt(ASC(pad1(b%)))) MOD 67
IF qn% = 2 THEN sum% = sum% + trnslt(ASC(pad1(b%))) + moders(op%)
NEXT b%
pad1(a%) = trnsltr(sum% MOD 67)
NEXT a%
n% = n% + 1
END SUB
SUB modify
REM modifier shuffling routine
q% = 26
temp# = 0
WHILE q% > -1
FOR p% = 1 TO 27
k% = (p% - 1) * 4 + 3
FOR e% = k% TO k% + 3
temp# = temp# * 67
hug# = ABS(trnslt(ASC(pad1(e%))))
temp# = temp# + hug#
NEXT e%
IF (temp# / (67 ^ 4)) >= 0 AND q% > -1 THEN
SWAP moders(q%), moders(INT(27 * (temp# / (67 ^ 4))))
q% = q% - 1
END IF
temp# = 0
NEXT p%
WEND
END SUB
SUB setup
FOR a% = 0 TO 26
READ moders(a%)
moders2(a%) = moders(a%)
NEXT a%
REM setting up tables and modder functions
FOR a% = 0 TO 25
trnsltr(a%) = CHR$(a% + 97)
trnsltr(a% + 26) = CHR$(a% + 65)
NEXT a%
FOR a% = 52 TO 61
trnsltr(a%) = CHR$(a% - 4)
NEXT a%
FOR a% = 62 TO 66
READ b%
trnsltr(a%) = CHR$(b%)
NEXT a%
FOR a% = 0 TO 255
trnslt(a%) = -1
NEXT a%
FOR a% = 0 TO 66
trnslt(ASC(trnsltr(a%))) = a%
NEXT a%
RESTORE
END SUB
the call to drand# gives you random numbers from 0 to 1 simply multiply that by your needed range for each vector needed p2$ is the password that is passed to the password handler which combines it with some other random characters and then caps the size to a certain limit p1$ is where the final modified password is contained
drand# itself calls another sub which is actually a clone of itself with some shuffling of
sorts that works to ensure that the numbers being produced are truly random there is also a table of values that are added in to the values being added all this in total makes the RNG many many more times random with than without.
this RNG has a very high sensitivity to slight differences in password initially set you must however make an intial call to setup and initialize to "bootstrap" the random number generator this RNG will produce Truly random numbers that will pass all tests of randomness
more random even then shuffling a deck of cards by hand , more random than rolling a dice..
hope this helps using the same password will result in the same sequnece of vectors
This program would have to be modified a bit though to pick random vectors rather than
it's current use as a secure encryption random number generator...

You can use encryption for this task, for example AES. Use your seed as the password, struct with coordinates as the data block and encrypt it. The encrypted block will be your random number (you can actually use any part of it). This approach is used in the Fortuna PRNG. The same approach can be used for disk encryption where random access of data is needed (see Random access encryption with AES In Counter mode using Fortuna PRNG:)

Related

Caesar's cypher encryption algorithm

Caesar's cypher is the simplest encryption algorithm. It adds a fixed value to the ASCII (unicode) value of each character of a text. In other words, it shifts the characters. Decrypting a text is simply shifting it back by the same amount, that is, it substract the same value from the characters.
My task is to write a function that:
accepts two arguments: the first is the character vector to be encrypted, and the second is the shift amount.
returns one output, which is the encrypted text.
needs to work with all the visible ASCII characters from space to ~ (ASCII codes of 32 through 126). If the shifted code goes outside of this range, it should wrap around. For example, if we shift ~ by 1, the result should be space. If we shift space by -1, the result should be ~.
This is my MATLAB code:
function [coded] = caesar(input_text, shift)
x = double(input_text); %converts char symbols to double format
for ii = 1:length(x) %go through each element
if (x(ii) + shift > 126) & (mod(x(ii) + shift, 127) < 32)
x(ii) = mod(x(ii) + shift, 127) + 32; %if the symbol + shift > 126, I make it 32
elseif (x(ii) + shift > 126) & (mod(x(ii) + shift, 127) >= 32)
x(ii) = mod(x(ii) + shift, 127);
elseif (x(ii) + shift < 32) & (126 + (x(ii) + shift - 32 + 1) >= 32)
x(ii) = 126 + (x(ii) + shift - 32 + 1);
elseif (x(ii) + shift < 32) & (126 + (x(ii) + shift - 32 + 1) < 32)
x(ii) = abs(x(ii) - 32 + shift - 32);
else x(ii) = x(ii) + shift;
end
end
coded = char(x); % converts double format back to char
end
I can't seem to make the wrapping conversions correctly (e.g. from 31 to 126, 30 to 125, 127 to 32, and so on). How should I change my code to do that?
Before you even start coding something like this, you should have a firm grasp of how to approach the problem.
The main obstacle you encountered is how to apply the modulus operation to your data, seeing how mod "wraps" inputs to the range of [0 modPeriod-1], while your own data is in the range [32 126]. To make mod useful in this case we perform an intermediate step of shifting of the input to the range that mod "likes", i.e. from some [minVal maxVal] to [0 modPeriod-1].
So we need to find two things: the size of the required shift, and the size of the period of the mod. The first one is easy, since this is just -minVal, which is the negative of the ASCII value of the first character, which is space (written as ' ' in MATLAB). As for the period of the mod, this is just the size of your "alphabet", which happens to be "1 larger than the maximum value, after shifting", or in other words - maxVal-minVal+1. Essentially, what we're doing is the following
input -> shift to 0-based ("mod") domain -> apply mod() -> shift back -> output
Now take a look how this can be written using MATLAB's vectorized notation:
function [coded] = caesar(input_text, shift)
FIRST_PRINTABLE = ' ';
LAST_PRINTABLE = '~';
N_PRINTABLE_CHARS = LAST_PRINTABLE - FIRST_PRINTABLE + 1;
coded = char(mod(input_text - FIRST_PRINTABLE + shift, N_PRINTABLE_CHARS) + FIRST_PRINTABLE);
Here are some tests:
>> caesar('blabla', 1)
ans =
'cmbcmb'
>> caesar('cmbcmb', -1)
ans =
'blabla'
>> caesar('blabla', 1000)
ans =
'5?45?4'
>> caesar('5?45?4', -1000)
ans =
'blabla'
We can solve it using the idea of periodic functions :
periodic function repeats itself every cycle and every cycle is equal to 2π ...
like periodic functions ,we have a function that repeats itself every 95 values
the cycle = 126-32+1 ;
we add one because the '32' is also in the cycle ...
So if the value of the character exceeds '126' we subtract 95 ,
i.e. if the value =127(bigger than 126) then it is equivalent to
127-95=32 .
&if the value is less than 32 we subtract 95.
i.e. if the value= 31 (less than 32) then it is equivalent to 31+95
=126..
Now we will translate that into codes :
function out= caesar(string,shift)
value=string+shift;
for i=1:length(value)
while value(i)<32
value(i)=value(i)+95;
end
while value(i)>126
value(i)=value(i)-95;
end
end
out=char(value);
First i converted the output(shift+ text_input) to char.
function coded= caesar(text_input,shift)
coded=char(text_input+shift);
for i=1:length(coded)
while coded(i)<32
coded(i)=coded(i)+95;
end
while coded(i)>126
coded(i)=coded(i)-95;
end
end
Here Is one short code:
function coded = caesar(v,n)
C = 32:126;
v = double(v);
for i = 1:length(v)
x = find(C==v(i));
C = circshift(C,-n);
v(i) = C(x);
C = 32:126;
end
coded = char(v);
end

How can I convert numbers to letters in VBS?

I want to take a number and convert it into lowercase a-z letters using VBScript.
For example:
1 converts to a
2 converts to b
27 converts to aa
28 converts to ab
and so on...
In particular I am having trouble converting numbers after 26 when converting to 2 letter cell names. (aa, ab, ac, etc.)
You should have a look at the Chr(n) function.
This would fit your needs from a to z:
wscript.echo Chr(number+96)
To represent multiple letters for numbers, (like excel would do it) you'll have to check your number for ranges and use the Mod operator for modulo.
EDIT:
There is a fast food Copy&Paste example on the web: How to convert Excel column numbers into alphabetical characters
Quoted example from microsoft:
For example: The column number is 30.
The column number is divided by 27: 30 / 27 = 1.1111, rounded down by the Int function to "1".
i = 1
Next Column number - (i * 26) = 30 -(1 * 26) = 30 - 26 = 4.
j = 4
Convert the values to alphabetical characters separately,
i = 1 = "A"
j = 4 = "D"
Combined together, they form the column designator "AD".
And its code:
Function ConvertToLetter(iCol As Integer) As String
Dim iAlpha As Integer
Dim iRemainder As Integer
iAlpha = Int(iCol / 27)
iRemainder = iCol - (iAlpha * 26)
If iAlpha > 0 Then
ConvertToLetter = Chr(iAlpha + 64)
End If
If iRemainder > 0 Then
ConvertToLetter = ConvertToLetter & Chr(iRemainder + 64)
End If
End Function
Neither of the solutions above work for the full Excel range from A to XFD. The first example only works up to ZZ. The second example has boundry problems explained in the code comments below.
//
Function ColumnNumberToLetter(ColumnNumber As Integer) As String
' convert a column number to the Excel letter representation
Dim Div As Double
Dim iMostSignificant As Integer
Dim iLeastSignificant As Integer
Dim Base As Integer
Base = 26
' Column letters are base 26 starting at A=1 and ending at Z=26
' For base 26 math to work we need to adjust the input value to
' base 26 starting at 0
Div = (ColumnNumber - 1) / Base
iMostSignificant = Int(Div)
' The addition of 1 is needed to restore the 0 to 25 result value to
' align with A to Z
iLeastSignificant = 1 + (Div - iMostSignificant) * Base
' convert number to letter
ColumnNumberToLetter = Chr(64 + iLeastSignificant)
' if the input number is larger than the base then the conversion we
' just did is the least significant letter
' Call the function again with the remaining most significant letters
If ColumnNumber > Base Then
ColumnNumberToLetter = ColumnNumberToLetter(iMostSignificant) & ColumnNumberToLetter
End If
End Function
//
try this
function converts(n)
Dim i, c, m
i = n
c = ""
While i > 26
m = (i mod 26)
c = Chr(m+96) & c
i = (i - m) / 26
Wend
c = Chr(i+96) & c
converts = c
end function
WScript.Echo converts(1000)

An algorithm for iteration over all values in a column of a table

I am looking for a simple algorithm which works on the following table:
In the first column you see the constraints. The second column should be used by the algorithm to output the iterations, which should be done like this:
0 0 0
0 0 1
........
0 0 29
0 1 0
........
0 1 29
0 2 0
0 2 1
........
........
27 9 29
28 0 0
........
........
28 9 29
Currently I have the following code:
Dim wksSourceSheet As Worksheet
Set wksSourceSheet = Worksheets("Solver")
Dim lngLastRow As Long
Dim lngLastColumn As Long
With wksSourceSheet
lngLastRow = IIf(IsEmpty(.Cells(.Rows.Count, 1)), _
.Cells(.Rows.Count, 1).End(xlUp).Row, .Rows.Count)
lngLastColumn = IIf(IsEmpty(.Cells(1, .Columns.Count)), _
.Cells(1, .Columns.Count).End(xlToLeft).Column, .Columns.Count)
Dim intRowOuter As Integer
Dim intRowInner As Integer
For intRowOuter = 2 To lngLastRow
.Cells(intRowOuter, lngLastColumn).Value = 0
Next intRowOuter
For intRowOuter = lngLastRow To 2 Step -1
For intRowInner = lngLastRow To intRowOuter Step -1
Dim constraint As Integer
Dim intConstraintCounter As Integer
intConstraint = .Cells(intRowInner, 1)
For intConstraintCounter = 1 To intConstraint
.Cells(intRowInner, lngLastColumn).Value = intConstraintCounter
Next intStampCounter
Next intRowInner
Next intRowOuter
End With
This might be a right approach but something is incorrect. I'm unfortunately stuck so I would appreciate some help on fixing this.
Solution
I would suggest using one array to store the constraints and one to represent the counter.
Dim MaxNum() As Long
Dim myCounter() As Long
ReDim MaxNum(1 To NumDigits)
ReDim myCounter(1 To NumDigits)
Next you need to initialize MaxNum. This will probably involve looping through the cells containing the constraints. Something like:
Dim constraintRange As Range
Dim i As integer
Set constraintRange = wksSourceSheet.Range("A2:A4")
For i = 1 to numDigits
MaxNum(i) = constraintRange.Cells(i,1).Value
Next i
Now we just need to write an increment counter function! The idea is pretty simple we just go from the least significant digit to the most significant. We increment the LSD and, if there is overflow we set it to 0 and then add 1 to the next digit. It looks like this:
Sub IncrNum(ByRef myNum() As Long, ByRef MaxNum() As Long)
Dim i As Integer
For i = LBound(myNum) To UBound(myNum)
myNum(i) = myNum(i) + 1
If myNum(i) > MaxNum(i) Then 'Overflow!
myNum(i) = 0 'Reset digit to 0 and continue
Else
Exit For 'No overflow so we can just exit
End If
Next i
End Sub
Which is just one for-loop! I think this will be the cleanest solution :)
NOTE: To use this function you would simply do IncrNum(myCounter, MaxNum). Which would change the value of myCounter to the next in the sequence. From here you can paste to a range by doing dstRange = myCounter.
Testing
In my own tests I used a while loop to print out all of the values. It looked something like this:
Do While Not areEqual(MaxNum, myCounter)
Call IncrNum(myCounter,MaxNum)
outRange = myCounter
Set outRange = outRange.Offset(1, 0)
Loop
areEqual is just a function which returns true if the parameters contain the same values. If you like I can provide my code otherwise I will leave it out to keep my answer as on track as it can be.
Maybe something like this can be modified to fit your needs. It simulates addition with carry:
Sub Clicker(MaxNums As Variant)
Dim A As Variant
Dim i As Long, j As Long, m As Long, n As Long
Dim sum As Long, carry As Long
Dim product As Long
m = LBound(MaxNums)
n = UBound(MaxNums)
product = 1
For i = m To n
product = product * (1 + MaxNums(i))
Next i
ReDim A(1 To product, m To n)
For j = m To n
A(1, j) = 0
Next j
For i = 2 To product
carry = 1
For j = n To m Step -1
sum = A(i - 1, j) + carry
If sum > MaxNums(j) Then
A(i, j) = 0
carry = 1
Else
A(i, j) = sum
carry = 0
End If
Next j
Next i
Range(Cells(1, 1), Cells(product, n - m + 1)).Value = A
End Sub
Used like:
Sub test()
Clicker Array(3, 2, 2)
End Sub
Which produces:
x%10 or x Mod 10 give the remainder when x is divided by 10 so you will get the last digit of x.
Since your problem is specifically asking for each digit not to exceed 463857. You can have a counter incrementing from 000000 to 463857 and only output/use the numbers the fullfill the following condition:
IF(x%10 <= 7 AND x%100 <=57 AND x%1000 <= 857 AND x%10000 <=3857 AND x%100000 <= 63857 AND x <= 463857)
THEN //perform task.

Random number from 0 to 100 in QBasic

I need a totally random number from 0 to 10 or from 0 to 100 as a value "NUM" done in QBasic for a random draw program. I currently have this:
RANDOMIZE TIMER: A = INT((RND * 100)): B = INT((RND * 10)): C = (A + B)
NUM = INT(C - (RND * 10))
This is basically just a pile of random mathematical operations to get a random number from 1 to 100.
The problem is i get the same or very similar numbers quite too often. Is there a more reliable way to do this?
The code that you provide does not at all meet the requirement of a "random number from 0 to 10 or from 0 to 100". You start out setting A to a random number from 0 to 99 and B to a random number from 0 to 9. But the rest of your calculation does not perform an "OR".
How about this:
RANDOMIZE TIMER
A = INT(RND * 101) // random number from 0 to 100
B = INT(RND * 11) // random number from 0 to 10
C = INT(RND * 2) // random number from 0 to 1
IF C = 0 THEN
NUM = A
ELSE
NUM = B
END IF
or more simplified:
RANDOMIZE TIMER
NUM = INT(RND * 101)
IF INT(RND * 2) = 0 THEN
NUM = INT(RND * 11)
END IF
Try this Rand function used as
NUM = Rand(0, 100)
For getting a random number from 0 to any number , you can use the following program -
Randomize timer
Rand_num = INT(RND(1)*limit)+1
Where-
Rand_num= the random number
Limit = the last number like if you want from 0 to 100 then limit will be 100

Checking for termination when converting real to rational

Recently I found this in some code I wrote a few years ago. It was used to rationalize a real value (within a tolerance) by determining a suitable denominator and then checking if the difference between the original real and the rational was small enough.
Edit to clarify : I actually don't want to convert all real values. For instance I could choose a max denominator of 14, and a real value that equals 7/15 would stay as-is. It's not as clear that as it's an outside variable in the algorithms I wrote here.
The algorithm to get the denominator was this (pseudocode):
denominator(x)
frac = fractional part of x
recip = 1/frac
if (frac < tol)
return 1
else
return recip * denominator(recip)
end
end
Seems to be based on continued fractions although it became clear on looking at it again that it was wrong. (It worked for me because it would eventually just spit out infinity, which I handled outside, but it would be often really slow.) The value for tol doesn't really do anything except in the case of termination or for numbers that end up close. I don't think it's relatable to the tolerance for the real - rational conversion.
I've replaced it with an iterative version that is not only faster but I'm pretty sure it won't fail theoretically (d = 1 to start with and fractional part returns a positive, so recip is always >= 1) :
denom_iter(x d)
return d if d > maxd
frac = fractional part of x
recip = 1/frac
if (frac = 0)
return d
else
return denom_iter(recip d*recip)
end
end
What I'm curious to know if there's a way to pick the maxd that will ensure that it converts all values that are possible for a given tolerance. I'm assuming 1/tol but don't want to miss something. I'm also wondering if there's an way in this approach to actually limit the denominator size - this allows some denominators larger than maxd.
This can be considered a 2D minimization problem on error:
ArgMin ( r - q / p ), where r is real, q and p are integers
I suggest the use of Gradient Descent algorithm . The gradient in this objective function is:
f'(q, p) = (-1/p, q/p^2)
The initial guess r_o can be q being the closest integer to r, and p being 1.
The stopping condition can be thresholding of the error.
The pseudo-code of GD can be found in wiki: http://en.wikipedia.org/wiki/Gradient_descent
If the initial guess is close enough, the objective function should be convex.
As Jacob suggested, this problem can be better solved by minimizing the following error function:
ArgMin ( p * r - q ), where r is real, q and p are integers
This is linear programming, which can be efficiently solved by any ILP (Integer Linear Programming) solvers. GD works on non-linear cases, but lack efficiency in linear problems.
Initial guesses and stopping condition can be similar to stated above. Better choice can be obtained for individual choice of solver.
I suggest you should still assume convexity near the local minimum, which can greatly reduce cost. You can also try Simplex method, which is great on linear programming problem.
I give credit to Jacob on this.
A problem similar to this is solved in the Approximations section beginning ca. page 28 of Bill Gosper's Continued Fraction Arithmetic document. (Ref: postscript file; also see text version, from line 1984.) The general idea is to compute continued-fraction approximations of the low-end and high-end range limiting numbers, until the two fractions differ, and then choose a value in the range of those two approximations. This is guaranteed to give a simplest fraction, using Gosper's terminology.
The python code below (program "simpleden") implements a similar process. (It probably is not as good as Gosper's suggested implementation, but is good enough that you can see what kind of results the method produces.) The amount of work done is similar to that for Euclid's algorithm, ie O(n) for numbers with n bits, so the program is reasonably fast. Some example test cases (ie the program's output) are shown after the code itself. Note, function simpleratio(vlo, vhi) as shown here returns -1 if vhi is smaller than vlo.
#!/usr/bin/env python
def simpleratio(vlo, vhi):
rlo, rhi, eps = vlo, vhi, 0.0000001
if vhi < vlo: return -1
num = denp = 1
nump = den = 0
while 1:
klo, khi = int(rlo), int(rhi)
if klo != khi or rlo-klo < eps or rhi-khi < eps:
tlo = denp + klo * den
thi = denp + khi * den
if tlo < thi:
return tlo + (rlo-klo > eps)*den
elif thi < tlo:
return thi + (rhi-khi > eps)*den
else:
return tlo
nump, num = num, nump + klo * num
denp, den = den, denp + klo * den
rlo, rhi = 1/(rlo-klo), 1/(rhi-khi)
def test(vlo, vhi):
den = simpleratio(vlo, vhi);
fden = float(den)
ilo, ihi = int(vlo*den), int(vhi*den)
rlo, rhi = ilo/fden, ihi/fden;
izok = 'ok' if rlo <= vlo <= rhi <= vhi else 'wrong'
print '{:4d}/{:4d} = {:0.8f} vlo:{:0.8f} {:4d}/{:4d} = {:0.8f} vhi:{:0.8f} {}'.format(ilo,den,rlo,vlo, ihi,den,rhi,vhi, izok)
test (0.685, 0.695)
test (0.685, 0.7)
test (0.685, 0.71)
test (0.685, 0.75)
test (0.685, 0.76)
test (0.75, 0.76)
test (2.173, 2.177)
test (2.373, 2.377)
test (3.484, 3.487)
test (4.0, 4.87)
test (4.0, 8.0)
test (5.5, 5.6)
test (5.5, 6.5)
test (7.5, 7.3)
test (7.5, 7.5)
test (8.534537, 8.534538)
test (9.343221, 9.343222)
Output from program:
> ./simpleden
8/ 13 = 0.61538462 vlo:0.68500000 9/ 13 = 0.69230769 vhi:0.69500000 ok
6/ 10 = 0.60000000 vlo:0.68500000 7/ 10 = 0.70000000 vhi:0.70000000 ok
6/ 10 = 0.60000000 vlo:0.68500000 7/ 10 = 0.70000000 vhi:0.71000000 ok
2/ 4 = 0.50000000 vlo:0.68500000 3/ 4 = 0.75000000 vhi:0.75000000 ok
2/ 4 = 0.50000000 vlo:0.68500000 3/ 4 = 0.75000000 vhi:0.76000000 ok
3/ 4 = 0.75000000 vlo:0.75000000 3/ 4 = 0.75000000 vhi:0.76000000 ok
36/ 17 = 2.11764706 vlo:2.17300000 37/ 17 = 2.17647059 vhi:2.17700000 ok
18/ 8 = 2.25000000 vlo:2.37300000 19/ 8 = 2.37500000 vhi:2.37700000 ok
114/ 33 = 3.45454545 vlo:3.48400000 115/ 33 = 3.48484848 vhi:3.48700000 ok
4/ 1 = 4.00000000 vlo:4.00000000 4/ 1 = 4.00000000 vhi:4.87000000 ok
4/ 1 = 4.00000000 vlo:4.00000000 8/ 1 = 8.00000000 vhi:8.00000000 ok
11/ 2 = 5.50000000 vlo:5.50000000 11/ 2 = 5.50000000 vhi:5.60000000 ok
5/ 1 = 5.00000000 vlo:5.50000000 6/ 1 = 6.00000000 vhi:6.50000000 ok
-7/ -1 = 7.00000000 vlo:7.50000000 -7/ -1 = 7.00000000 vhi:7.30000000 wrong
15/ 2 = 7.50000000 vlo:7.50000000 15/ 2 = 7.50000000 vhi:7.50000000 ok
8030/ 941 = 8.53347503 vlo:8.53453700 8031/ 941 = 8.53453773 vhi:8.53453800 ok
24880/2663 = 9.34284641 vlo:9.34322100 24881/2663 = 9.34322193 vhi:9.34322200 ok
If, rather than the simplest fraction in a range, you seek the best approximation given some upper limit on denominator size, consider code like the following, which replaces all the code from def test(vlo, vhi) forward.
def smallden(target, maxden):
global pas
pas = 0
tol = 1/float(maxden)**2
while 1:
den = simpleratio(target-tol, target+tol);
if den <= maxden: return den
tol *= 2
pas += 1
# Test driver for smallden(target, maxden) routine
import random
totalpass, trials, passes = 0, 20, [0 for i in range(20)]
print 'Maxden Num Den Num/Den Target Error Passes'
for i in range(trials):
target = random.random()
maxden = 10 + round(10000*random.random())
den = smallden(target, maxden)
num = int(round(target*den))
got = float(num)/den
print '{:4d} {:4d}/{:4d} = {:10.8f} = {:10.8f} + {:12.9f} {:2}'.format(
int(maxden), num, den, got, target, got - target, pas)
totalpass += pas
passes[pas-1] += 1
print 'Average pass count: {:0.3}\nPass histo: {}'.format(
float(totalpass)/trials, passes)
In production code, drop out all the references to pas (etc.), ie, drop out pass-counting code.
The routine smallden is given a target value and a maximum value for allowed denominators. Given maxden possible choices of denominators, it's reasonable to suppose that a tolerance on the order of 1/maxden² can be achieved. The pass-counts shown in the following typical output (where target and maxden were set via random numbers) illustrate that such a tolerance was reached immediately more than half the time, but in other cases tolerances 2 or 4 or 8 times as large were used, requiring extra calls to simpleratio. Note, the last two lines of output from a 10000-number test run are shown following the complete output of a 20-number test run.
Maxden Num Den Num/Den Target Error Passes
1198 32/ 509 = 0.06286837 = 0.06286798 + 0.000000392 1
2136 115/ 427 = 0.26932084 = 0.26932103 + -0.000000185 1
4257 839/2670 = 0.31423221 = 0.31423223 + -0.000000025 1
2680 449/ 509 = 0.88212181 = 0.88212132 + 0.000000486 3
2935 440/1853 = 0.23745278 = 0.23745287 + -0.000000095 1
6128 347/1285 = 0.27003891 = 0.27003899 + -0.000000077 3
8041 1780/4243 = 0.41951449 = 0.41951447 + 0.000000020 2
7637 3926/7127 = 0.55086292 = 0.55086293 + -0.000000010 1
3422 27/ 469 = 0.05756930 = 0.05756918 + 0.000000113 2
1616 168/1507 = 0.11147976 = 0.11147982 + -0.000000061 1
260 62/ 123 = 0.50406504 = 0.50406378 + 0.000001264 1
3775 52/3327 = 0.01562970 = 0.01562750 + 0.000002195 6
233 6/ 13 = 0.46153846 = 0.46172772 + -0.000189254 5
3650 3151/3514 = 0.89669892 = 0.89669890 + 0.000000020 1
9307 2943/7528 = 0.39094049 = 0.39094048 + 0.000000013 2
962 206/ 225 = 0.91555556 = 0.91555496 + 0.000000594 1
2080 564/1975 = 0.28556962 = 0.28556943 + 0.000000190 1
6505 1971/2347 = 0.83979548 = 0.83979551 + -0.000000022 1
1944 472/ 833 = 0.56662665 = 0.56662696 + -0.000000305 2
3244 291/1447 = 0.20110574 = 0.20110579 + -0.000000051 1
Average pass count: 1.85
Pass histo: [12, 4, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
The last two lines of output from a 10000-number test run:
Average pass count: 1.77
Pass histo: [56659, 25227, 10020, 4146, 2072, 931, 497, 233, 125, 39, 33, 17, 1, 0, 0, 0, 0, 0, 0, 0]

Resources