Wednesday, November 20, 2013

AB muscles cheap and small exerciser


Friday, April 26, 2013

Xincrol - unique and random number generation algorithm.


The challenge.


Every serious programmer writing serious code at least once in his life has been stuck with this problem.
How to generate random numbers, but in a way so each number will appear only once.
Regular Random Number Generators (RNG) don't guaranty any uniqueness of provided numbers.
Programmers were using large random numbers like UUID(MD5) to hope that the same number will not appear twice in their life . But this does happen, I have seen it many times with large and dynamically changing databases, where object IDs were collided and this caused applications to crash, reports to fail, data lost etc.
The other common practice is to create huge array, fill it with serial numbers and shuffle this array for long time to have more-less acceptable level of randomness.
Those solutions are very CPU and memory consuming.
At the same time there are millions of SW applications which are seriously require URNGs.
Some examples of such applications:
  • Video and Image Effects
  • Encryption 
  • Steganography
  • All kinds of unique but secure (not serial) IDs (ex. any website with big DB)
  • Random serial numbers for money bills, car license plates, airplane part numbers etc. 
  • MP3 player playlist shuffling
  • Playing Cards shuffling for Casinos
  • Absolutely every Computer game needs unique randomization
  • ASCII to ASCII encryption/decryption 
  • Random memory or disk space addressing
  • more...
We all needed a function which will do the job.
I discovered it!


The Discovery.


If your input is serial numbers stream (like 0,1,2,3...) and you will perform cycling and directional bitwise operations in some predefined order you will get the same set of numbers but placed in different positions.
Magical operations are XOR, INC and ROL (the name of algorithm).


private int XOR(int iA, int iB, int iBaseMask) {
  return ((iA ^ iB) & iBaseMask);
 }



private int INC(int iA, int iBaseMask) {
  return ((++iA) & iBaseMask);
 }


private int ROL(int iA, int iBaseMask) {
  if ((iA & ((iBaseMask >>> 1) + 1)) != 0) {
   iA = ((iA << 1) & iBaseMask) | 1;
  } else {
   iA = ((iA << 1) & iBaseMask);
  }
  return (iA);
 }

There are also DEC and ROR operations which could be used instead of INC and ROL.
There are 4 possible groups of operations:

  • XOR, INC, ROL 
  • XOR, DEC, ROL
  • XOR, INC, ROR
  • XOR, DEC, ROR

where...
XOR - exclusive disjunction,
INC - cyclic incrementation,
DEC - cycling decrementation,
ROL - rotation left,
ROR - rotation right.

Why is this happening? Well, because those operations are cyclic and directional. When you increment a variable, it grows up to biggest number in the range and then drops to 0. So, regardless on how many times you will perform this operation, but if you will always repeat it the same number of times, you will get the unique numbers output if your input is unique. One such operation doesn't really shuffle the input stream but if combined with other two, shuffling and randomness becomes really strong.

Important: Don't use INC and DEC  in the same cycle, don't use ROR and ROL in the same cycle!

Here I am publishing simple Java code of one of my Xincrol implementations.
This implementation also has ability to set a different range for generated unique numbers.
There is main() method below that demonstrates how it works. 
Have it! Enjoy it! 
Stop working with huge arrays, hashcodes and UUIDs!

If you need some help to integrate Xincrol into your project please leave me a comment here or write me an email. 

Don't forget to click ads here if you like Xincrol.






/**
 * Xincrol - Unique and Random Number Generator
 * Xincrol.java
 * Purpose: Generating unique random numbers in specified range.
 *
 * @author Tofig Kareemov
 * @version 1.7 2013.05.03
 * 
 * v1.7 notes: improved randomness for big numbers
 * 
 * v1.6 notes: made ROL and ROR functions to support big masks
 * 
 * v1.5 notes: Fixed bug in ROL and ROR functions
 * 
 * v1.4 notes: added REFLECT and NOT methods
 */
package androphic.estereos.lib.algs;

public class Xincrol {

// Private Data...
private int iUniqueSeed = 0;
private int[] iKey = new int[31];
private int iBaseRange = 0;
private int iBase = 0;
private int iBaseMask = 0;
private int iBaseBitSize = 0;

// Constructor
public Xincrol(int iRange) {
randomize(iRange);
}

// .

// Private Methods...
private void reset(boolean bNew) {
iUniqueSeed = 0;
hashKey(System.getProperties().toString(), bNew);
hashKey("" + System.currentTimeMillis(), false);
for (int i = 0; i < iKey.length; ++i) {
hashKey("" + System.nanoTime(), false);
}
}

private void hashKey(String sKey, boolean bNew) {
if ((sKey == null) || ("".equals(sKey))) {
return;
}
if (bNew) {
for (int i = 0; i < iKey.length; ++i) {
int iKeyValue = 0;
for (int i1 = 0; i1 < 4; ++i1) {
int iBit = (i >>> i1) & 1;
if (iBit == 1) {
iKeyValue = (iKeyValue ^ 0x5a);
} else {
iKeyValue = (iKeyValue ^ 0xa5);
}
if (i1 < 3) {
iKeyValue <<= 8;
}
}
iKey[i] = iKeyValue;
}
}
int iGlue = iKey[iKey.length - 1];
int iKeyIndex = 0;
for (int i1 = 0; i1 < iKey.length; ++i1) {
for (int i = 0; i < sKey.length(); ++i) {
iKey[iKeyIndex] ^= sKey.charAt(i);
iKey[iKeyIndex] = (iKey[iKeyIndex] << 1)
| (iKey[iKeyIndex] >>> 31);
iKey[iKeyIndex] ^= iGlue;
iKey[iKeyIndex] = (iKey[iKeyIndex] << 1)
| (iKey[iKeyIndex] >>> 31);
iGlue = iKey[iKeyIndex];
iKeyIndex = (++iKeyIndex) % iKey.length;
}
}
}

// Main Function
private int XOR(int iA, int iB) {
return ((iA ^ iB) & iBaseMask);
}

// private int INC(int iA) {
// return ((++iA) & iBaseMask);
// }

private int ADD(int iA, int iB) {
return ((iA + iB) & iBaseMask);
}

private int ROL(int iA, int iPowerDistance) {
if (iBaseBitSize <= 0) {
return iA;
}
iPowerDistance = iPowerDistance % iBaseBitSize;
int iBaseBit = ((iBaseMask >>> 1) + 1);
for (int i = 0; i < iPowerDistance; ++i) {
if ((iA & iBaseBit) != 0) {
iA = ((iA << 1) & iBaseMask) | 1;
} else {
iA = ((iA << 1) & iBaseMask);
}
}
return (iA);
}

// .

// Additional Functions
// private int DEC(int iA) {
// return ((--iA) & iBaseMask);
// }
//
// private int ROR(int iA) {
// if ((iA & 1) != 0) {
// return ((iA & iBaseMask) >>> 1) | ((iBaseMask >>> 1) + 1);
// } else {
// return ((iA & iBaseMask) >>> 1);
// }
// }

private int REFLECT(int iA) {
int iB = 0;
int iBaseBit = ((iBaseMask >>> 1) + 1);
for (; (iA > 0) && (iBaseBit > 0);) {
if ((iA & 1) != 0) {
iB |= iBaseBit;
}
iBaseBit >>>= 1;
iA >>>= 1;
}
return iB;
}

// .

private int generate(boolean bUp) {
int iResult = iBaseRange;
for (int i = 0; (i < iBase) && (iResult >= iBaseRange); ++i) {
if (bUp) {
iUniqueSeed = (++iUniqueSeed) % iBase;
} else {
iUniqueSeed = (--iUniqueSeed) % iBase;
}
iResult = iUniqueSeed;
for (int i1 = 0; i1 < iKey.length; ++i1) {
iResult = ROL(iResult, 1);
int iCommand = iKey[i1];
for (int i2 = 0; i2 < iBaseBitSize; ++i2) {
int iOperand = (iCommand + i1 + i2);
switch (iCommand & 3) {
case 0:
iResult = REFLECT(iResult);
break;
case 1:
iResult = ROL(iResult, iOperand);
break;
case 2:
iResult = ADD(iResult, iOperand);
break;
case 3:
iResult = XOR(iResult, iOperand);
break;
}
iCommand >>>= 1;
}
}
}
return iResult;
}

// Public Methods

public synchronized int getRange() {
return iBaseRange;
}

public synchronized void randomize(int iRange) {
iBaseRange = iRange;
iBase = 1;
if (iBaseRange <= 0) {
iBaseRange = 0;
return;
}
if (iBaseRange > Integer.MAX_VALUE / 2) {
iBaseRange = Integer.MAX_VALUE / 2;
}
for (iBaseBitSize = 0; iBase < iBaseRange; iBase <<= 1) {
++iBaseBitSize;
}
if (iBaseBitSize > 1) {
--iBaseBitSize;
}
iBaseMask = iBase - 1;
if ((iKey == null) || (iUniqueSeed >= iBase)) {
reset(false);
}
reset(false);
}

public synchronized void setKey(String sKey, int iRange) {
iBaseRange = iRange;
setKey(sKey);
}

public synchronized void setKey(String sKey) {
iBase = 1;
if (iBaseRange <= 0) {
iBaseRange = 0;
return;
}
if (iBaseRange > Integer.MAX_VALUE / 2) {
iBaseRange = Integer.MAX_VALUE / 2;
}
for (iBaseBitSize = 0; iBase < iBaseRange; iBase <<= 1) {
++iBaseBitSize;
}
if (iBaseBitSize > 0) {
--iBaseBitSize;
}
iBaseMask = iBase - 1;
if ((iKey == null) || (iUniqueSeed >= iBase)) {
reset(false);
}
iUniqueSeed = 0;
hashKey(sKey, true);
}

public synchronized int next() {
return generate(true);
}

public synchronized int prev() {
return generate(false);
}

public synchronized int encode(int iInput) {
int iResult = (iInput + generate(true));
if (iResult >= iBaseRange) {
iResult -= iBaseRange;
}
hashKey("" + iInput, false);
return iResult;
}

public synchronized int decode(int iInput) {
int iResult = (iInput - generate(true));
if (iResult < 0) {
iResult += iBaseRange;
}
hashKey("" + iResult, false);
return iResult;
}

public synchronized int random(int iRange) {
int iBase = 1;
if (iRange <= 0) {
return 0;
}
for (; (iBase < iRange) && (iBase < Integer.MAX_VALUE / 2); iBase <<= 1) {
}
randomize(iRange);
return (iKey[iKey.length - 1] & (iBase - 1));
}

// .

// ****************************************************************************************
// TESTS!
// ****************************************************************************************
public static void randomnessAndUniquenessTest(String[] args) {
long iStartTS = System.currentTimeMillis();
int iRange = 100;
int iLines = 10000;
Xincrol oXincrol = new Xincrol(iRange);
int iValueStringLength = ("" + (iRange - 1)).length();
int[] iTest = new int[iRange];
int iRandomnessTestNumber = (int) (Math.random() * iRange);
int iRandomnessTestNumberPosition = (int) (Math.random() * iRange);
int iRandomnessTestNumberCount = 0;
int iRandomnessTestNumberDistance = -1;
int iRandomnessTestNumberDistanceMax = 0;
int iRandomnessTestNumberDistanceMin = Integer.MAX_VALUE;
LengthLimitedAverage oRandomnessTestNumberDistanceAvg = new LengthLimitedAverage(
Integer.MAX_VALUE);
AveragedStandardDeviation oRandomnessTestNumberDistanceMSD = new AveragedStandardDeviation(
iRange);

int iPrevPercent = -1;
for (int i1 = 0; i1 < iLines; ++i1) {
for (int i2 = 0; i2 < iRange; ++i2) {
// Generating
iTest[i2] = oXincrol.next();
// Stats calculation
if (i2 == iRandomnessTestNumberPosition) {
if (iTest[i2] == iRandomnessTestNumber) {
++iRandomnessTestNumberCount;
if (iRandomnessTestNumberDistance > 0) {
if (iRandomnessTestNumberDistance > iRandomnessTestNumberDistanceMax) {
iRandomnessTestNumberDistanceMax = iRandomnessTestNumberDistance;
}
if (iRandomnessTestNumberDistance < iRandomnessTestNumberDistanceMin) {
iRandomnessTestNumberDistanceMin = iRandomnessTestNumberDistance;
}
oRandomnessTestNumberDistanceAvg
.set(iRandomnessTestNumberDistance);
oRandomnessTestNumberDistanceMSD
.setAny(iRandomnessTestNumberDistance);
}
iRandomnessTestNumberDistance = 0;
} else {
if (iRandomnessTestNumberDistance >= 0) {
++iRandomnessTestNumberDistance;
}
}
}
if (i2 == iRange - 1) {
// Testing for successful uniqueness generation
boolean bSuccess = false;
for (int i3 = 0; i3 < iTest.length; ++i3) {
bSuccess = false;
for (int i4 = 0; i4 < iTest.length; ++i4) {
if (iTest[i4] == i3) {
bSuccess = true;
}
}
if (!bSuccess) {
System.out.println("Error!!!");
System.exit(1);
}
}
oXincrol.randomize(iRange);
}
int iPercent = i1 * 100 / iLines;
if (iPrevPercent != iPercent) {
// Printing progress
if (i2 == 0) {
System.out.print("" + (i1 * 100 / iLines) + "%, " + i1
+ ": ");
}
// Printing values
String s = "" + iTest[i2];
for (; s.length() < iValueStringLength; s = " " + s) {
}
System.out.print(s);
// Printing end of line
if (i2 == iRange - 1) {
System.out.println();
System.out.flush();
iPrevPercent = iPercent;
} else {
System.out.print(",");
}
}
}
}

System.out.println("Success!!!");
System.out
.println("--------------------------------------------------------------");
System.out.println("Integer max range is " + Integer.MAX_VALUE / 2);
System.out
.println("--------------------------------------------------------------");
System.out.println("Stats:");
System.out.println("Range = " + iRange);
System.out.println("Lines = " + iLines);
System.out.println("Test Number = " + iRandomnessTestNumber);
System.out.println("Test Number Position = "
+ iRandomnessTestNumberPosition);
System.out.println("Test Number Count = " + iRandomnessTestNumberCount);
System.out.println("Test Number Distance Min = "
+ iRandomnessTestNumberDistanceMin);
System.out.println("Test Number Distance Max = "
+ iRandomnessTestNumberDistanceMax);
System.out.println("Test Number Distance Avg = "
+ oRandomnessTestNumberDistanceAvg.getAny());
System.out.println("Test Number Distance MSD = "
+ oRandomnessTestNumberDistanceMSD.getAny());
System.out.println("Expected Probability = "
+ String.format("%.6f", (float) (1.0d / iRange)));
System.out.println("Actual   Probability = "
+ String.format("%.6f", (float) (iRandomnessTestNumberCount)
/ iLines));
System.out.println("Time Lapsed = "
+ Tools.timeToString(System.currentTimeMillis() - iStartTS));
System.out
.println("--------------------------------------------------------------");

}

public static void rangeTest(String[] args) {
// int iRange = Integer.MAX_VALUE / 4 + 1;
int iRange = 100;
Xincrol oXincrol = new Xincrol(iRange);
System.out
.println("--------------------------------------------------------------");
System.out.println("Generating 100 numbers of range " + iRange);
System.out
.println("--------------------------------------------------------------");
for (int i = 0; i < 100; ++i) {
if ((i % iRange) == 0) {
oXincrol = new Xincrol(iRange);
}
System.out.println("" + i + ": " + oXincrol.next());
// System.out.println(oXincrol.random(iRange));
}
}

public static String encypherTest(String sKey, String sPlain) {
String sResult = "";
// sPlain = sPlain.replace(" ", "");

Xincrol oXincrolDigits = new Xincrol('9' - '0' + 1);
Xincrol oXincrolCapitals = new Xincrol('Z' - 'A' + 1);
Xincrol oXincrolLetters = new Xincrol('z' - 'a' + 1);

oXincrolDigits.setKey(sKey);
oXincrolCapitals.setKey(sKey);
oXincrolLetters.setKey(sKey);

for (int i = 0; i < sPlain.length(); ++i) {
char c = sPlain.charAt(i);
if ((c >= '0') && (c <= '9')) {
c = (char) ('0' + oXincrolDigits.encode(c - '0'));
} else if ((c >= 'A') && (c <= 'Z')) {
c = (char) ('A' + oXincrolCapitals.encode(c - 'A'));
} else if ((c >= 'a') && (c <= 'z')) {
c = (char) ('a' + oXincrolLetters.encode(c - 'a'));
}
sResult += c;
}
System.out.println("Encrypting");
System.out.println("Password  : " + sKey);
System.out.println("Plain Text: " + sPlain);
System.out.println("Crypt Text: " + sResult);

return sResult;
}

public static String decypherTest(String sKey, String sCrypt) {
String sResult = "";
Xincrol oXincrolDigits = new Xincrol('9' - '0' + 1);
Xincrol oXincrolCapitals = new Xincrol('Z' - 'A' + 1);
Xincrol oXincrolLetters = new Xincrol('z' - 'a' + 1);

oXincrolDigits.setKey(sKey);
oXincrolCapitals.setKey(sKey);
oXincrolLetters.setKey(sKey);

for (int i = 0; i < sCrypt.length(); ++i) {
char c = sCrypt.charAt(i);
if ((c >= '0') && (c <= '9')) {
c = (char) ('0' + oXincrolDigits.decode(c - '0'));
} else if ((c >= 'A') && (c <= 'Z')) {
c = (char) ('A' + oXincrolCapitals.decode(c - 'A'));
} else if ((c >= 'a') && (c <= 'z')) {
c = (char) ('a' + oXincrolLetters.decode(c - 'a'));
}
sResult += c;
}
System.out.println("Decrypting");
System.out.println("Password  : " + sKey);
System.out.println("Crypt Text: " + sCrypt);
System.out.println("Plain Text: " + sResult);

return sResult;
}

public static void encryptionTest(String[] args) {
System.out
.println("--------------------------------------------------------------");
System.out.println("Encrypting some text");
System.out
.println("--------------------------------------------------------------");
for (int i = 0; i < 10; ++i) {
decypherTest(
"Tofig is genius!",
encypherTest(
"Tofig is genius!",
""
+ (char) ('A' + (int) (Math.random() * ('Z' - 'A')))
+ (char) ('a' + (int) (Math.random() * ('z' - 'a')))
+ (char) ('a' + (int) (Math.random() * ('z' - 'a')))
+ (char) ('a' + (int) (Math.random() * ('z' - 'a')))
+ " "
+ (char) ('0' + (int) (Math.random() * ('9' - '0')))
+ (char) ('0' + (int) (Math.random() * ('9' - '0')))
+ ". "
+ "I like to play poker. I like to play poker. I like to play poker very much!"));
}
}

public static void main(String[] args) {
randomnessAndUniquenessTest(args);
rangeTest(args);
encryptionTest(args);
}

}





Program Output:



0%, 0: 0,7,5,6,3,2,4,8,9,1
1%, 1: 8,2,4,7,9,5,3,1,6,0
2%, 2: 9,8,2,6,0,7,1,3,5,4
3%, 3: 4,7,8,0,9,6,2,5,3,1
4%, 4: 6,0,2,1,7,4,8,9,5,3
5%, 5: 6,0,7,4,9,2,3,1,8,5
6%, 6: 5,2,8,9,4,1,7,3,6,0
7%, 7: 4,8,9,5,3,1,7,6,0,2
8%, 8: 9,3,2,1,6,0,7,5,8,4
9%, 9: 2,1,3,0,8,6,5,4,7,9
10%, 10: 2,1,5,9,6,4,0,7,3,8
11%, 11: 1,6,0,7,5,4,3,9,2,8
12%, 12: 6,8,0,2,3,4,9,1,7,5
13%, 13: 2,6,9,4,5,7,8,3,1,0
14%, 14: 3,0,1,5,9,8,2,6,4,7
15%, 15: 4,7,5,9,2,1,8,0,3,6
16%, 16: 4,6,9,3,7,5,8,1,2,0
17%, 17: 7,9,8,1,6,0,4,3,5,2
18%, 18: 1,2,4,7,9,0,5,3,6,8
19%, 19: 6,9,8,1,0,5,7,2,3,4
20%, 20: 5,4,7,8,1,2,9,0,3,6
21%, 21: 0,3,6,4,1,8,5,9,2,7
22%, 22: 2,4,0,1,3,7,9,6,8,5
23%, 23: 9,4,7,6,8,1,5,0,2,3
24%, 24: 4,6,1,5,7,3,0,2,8,9
25%, 25: 9,3,8,4,6,0,7,1,5,2
26%, 26: 8,4,5,9,7,6,0,3,2,1
27%, 27: 4,1,0,3,7,5,6,8,2,9
28%, 28: 3,2,1,5,6,8,7,9,0,4
29%, 29: 3,1,5,0,4,9,6,8,2,7
30%, 30: 2,8,1,9,0,6,3,4,5,7
31%, 31: 7,3,2,0,6,1,5,8,4,9
32%, 32: 4,1,7,5,3,2,0,8,9,6
33%, 33: 1,7,9,5,8,4,2,6,3,0
34%, 34: 8,1,0,2,4,9,7,5,6,3
35%, 35: 2,5,0,3,9,7,1,4,8,6
36%, 36: 6,0,9,5,7,2,8,4,1,3
37%, 37: 9,4,1,6,3,5,0,8,7,2
38%, 38: 8,6,0,1,3,7,4,2,5,9
39%, 39: 3,6,5,1,8,0,4,2,9,7
40%, 40: 7,1,0,2,6,5,9,3,4,8
41%, 41: 9,5,7,2,8,4,1,3,0,6
42%, 42: 4,1,6,9,8,3,5,7,2,0
43%, 43: 8,3,5,6,2,1,4,7,0,9
44%, 44: 4,6,9,2,1,7,5,0,3,8
45%, 45: 4,7,2,6,0,3,5,8,9,1
46%, 46: 4,2,5,1,8,7,6,0,9,3
47%, 47: 3,9,7,8,5,6,2,0,4,1
48%, 48: 8,6,5,1,0,2,4,9,7,3
49%, 49: 3,2,8,5,0,7,1,9,6,4
50%, 50: 1,6,4,7,3,8,2,0,9,5
51%, 51: 6,4,9,5,8,0,7,3,1,2
52%, 52: 6,9,4,0,1,5,8,2,7,3
53%, 53: 5,8,3,6,9,0,1,7,4,2
54%, 54: 8,4,7,3,0,6,1,9,5,2
55%, 55: 9,6,0,7,1,2,5,4,3,8
56%, 56: 8,6,1,2,4,3,9,5,7,0
57%, 57: 3,6,9,2,1,5,4,8,7,0
58%, 58: 5,4,9,8,1,2,0,7,6,3
59%, 59: 8,6,3,9,0,5,4,1,7,2
60%, 60: 6,2,8,5,1,3,7,4,9,0
61%, 61: 2,0,5,3,4,1,8,6,9,7
62%, 62: 3,0,9,5,6,7,1,2,8,4
63%, 63: 7,1,9,2,3,8,4,5,0,6
64%, 64: 3,2,4,5,6,0,9,8,7,1
65%, 65: 3,5,6,8,4,1,0,9,7,2
66%, 66: 2,9,3,0,5,7,4,8,6,1
67%, 67: 3,4,6,0,5,1,2,7,8,9
68%, 68: 4,0,2,7,3,6,1,5,8,9
69%, 69: 5,1,7,6,4,9,8,0,3,2
70%, 70: 3,8,1,9,6,5,0,7,2,4
71%, 71: 2,1,0,8,6,5,4,3,9,7
72%, 72: 0,7,8,9,6,3,4,5,2,1
73%, 73: 5,3,7,1,0,4,2,9,8,6
74%, 74: 8,2,0,7,5,9,6,4,3,1
75%, 75: 4,3,8,6,5,1,7,2,9,0
76%, 76: 1,0,5,7,8,3,6,4,9,2
77%, 77: 5,6,4,0,8,9,7,1,2,3
78%, 78: 6,1,7,3,9,0,5,2,4,8
79%, 79: 6,1,9,3,5,7,4,0,2,8
80%, 80: 6,5,4,7,3,1,9,0,8,2
81%, 81: 2,6,4,3,0,8,1,9,5,7
82%, 82: 2,4,7,3,8,6,1,9,5,0
83%, 83: 0,7,3,2,1,6,4,8,5,9
84%, 84: 7,5,1,8,0,4,9,6,3,2
85%, 85: 0,1,7,2,4,5,8,6,3,9
86%, 86: 1,8,6,5,7,2,9,0,3,4
87%, 87: 5,3,0,8,2,1,6,4,7,9
88%, 88: 7,0,3,6,2,9,8,1,4,5
89%, 89: 4,8,0,1,6,3,9,7,2,5
90%, 90: 8,5,7,6,1,9,2,4,3,0
91%, 91: 6,5,8,7,2,0,3,9,1,4
92%, 92: 4,0,6,7,9,1,3,5,2,8
93%, 93: 6,1,8,5,4,9,0,2,7,3
94%, 94: 5,0,6,4,7,8,9,1,3,2
95%, 95: 0,8,7,9,5,1,2,4,3,6
96%, 96: 3,2,0,5,1,8,4,6,7,9
97%, 97: 1,8,5,2,3,7,4,9,0,6
98%, 98: 2,0,6,8,4,7,5,9,3,1
99%, 99: 1,5,2,4,7,6,9,3,0,8
Success!!!