diff --git a/VERSION b/VERSION index 2003b63..5712157 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.2 +0.10.1 diff --git a/src/nexus/math/LehmerGenerator.as b/src/nexus/math/LehmerGenerator.as index 8503c35..b8ad168 100644 --- a/src/nexus/math/LehmerGenerator.as +++ b/src/nexus/math/LehmerGenerator.as @@ -18,34 +18,24 @@ public final class LehmerGenerator implements ISeededPRNG private var m_currentState:uint; private var m_numbersGenerated:int; - public function LehmerGenerator(seed:int=1) + public function LehmerGenerator(seed:uint=1) { this.seed = seed; } - [Inline] public final function get seed():uint { return m_seed; } public final function set seed(value:uint):void { - //if(value > 0 && value < 2147483647) - //{ m_seed = value; m_currentState = m_seed; m_numbersGenerated = 0; - //} - //else - //{ - //throw new ArgumentError("Seed must be between 0 and 2147483647"); - //} } - [Inline] public final function get currentState():uint { return m_currentState; } [Inline] public function get period():uint { return 2147483647 /*int.MAX_VALUE*/; } - [Inline] public final function get numbersGenerated():int { return m_numbersGenerated; } public function next():uint diff --git a/src/nexus/math/NativeRandomGenerator.as b/src/nexus/math/NativeRandomGenerator.as index 1c6804b..33579c1 100644 --- a/src/nexus/math/NativeRandomGenerator.as +++ b/src/nexus/math/NativeRandomGenerator.as @@ -23,7 +23,6 @@ public final class NativeRandomGenerator implements IPRNG [Inline] public function get period():uint { return 2147483647 /*int.MAX_VALUE*/; } - [Inline] public final function get currentState():uint { return m_currentState; } public function next():uint diff --git a/src/nexus/math/Random.as b/src/nexus/math/Random.as index c35a09f..b96f080 100644 --- a/src/nexus/math/Random.as +++ b/src/nexus/math/Random.as @@ -18,7 +18,8 @@ public class Random // CLASS CONSTANTS //-------------------------------------- - private static const s_random : Random = new Random(new NativeRandomGenerator()); + private static const s_random:Random = new Random(new NativeRandomGenerator()); + public static function get instance():Random { return s_random; @@ -28,7 +29,7 @@ public class Random // INSTANCE VARIABLES //-------------------------------------- - private var m_generator : IPRNG; + private var m_generator:IPRNG; //-------------------------------------- // CONSTRUCTOR @@ -48,37 +49,51 @@ public class Random //-------------------------------------- /** - * Generates a random floating point in the range [min, max). + * Generates a random floating point in the range [min, max) which is [0, 1) if neither + * argument is given. + * * If max is NaN, Infinity, or -Infinity, a number in the range [min, 1) is returned * If min is NaN, Infinity, or -Infinity, a number in the range [0, max) is returned * @param min The lowest value to return, inclusive * @param max The highest value to return, exclusive * @return A number in the range [min, max) */ - public function float( min : Number = NaN, max : Number = NaN ) : Number + public function float(min:Number = NaN, max:Number = NaN):Number { min = isNaN(min) || !isFinite(min) ? 0 : min; max = isNaN(max) || !isFinite(max) ? 1 : max; - return ((m_generator.next() / m_generator.period) * (max - min)) + min; + var p:Number = m_generator.next() / m_generator.period; + return (p * (max - min)) + min; } /** - * Generates a random integer in the range [min, max] + * Generates a random integer in the range [min, max) * @param min The lowest value to return, inclusive - * @param max The highest value to return, inclusive - * @return A number in the range [min, max] + * @param max The highest value to return, exclusive + * @return An int in the range [min, max) + */ + public function integer(min:uint = 0, max:int = int.MAX_VALUE):int + { + return m_generator.next() % (max - min) + min; + } + + /** + * Generates a random unsigned integer in the range [min, max) + * @param min The lowest value to return, inclusive + * @param max The highest value to return, exclusive + * @return A uint in the range [min, max) */ - public function integer( min : int = 1, max : int = int.MAX_VALUE ) : int + public function unsignedInteger(min:uint = 0, max:uint = uint.MAX_VALUE):uint { - return Math.floor((m_generator.next() / m_generator.period) * (max - min + 1)) + min; + return m_generator.next() % (max - min) + min; } /** * Returns a random true/false value, with a 50% chance of either */ - public function boolean() : Boolean + public function boolean():Boolean { - return (m_generator.next() / m_generator.period) < 0.5; + return(m_generator.next() / m_generator.period) < 0.5; } /** @@ -88,10 +103,10 @@ public class Random * @example randomRound(4.3) should return 4 70% of the time * and 5 30% of the time. */ - public function weightedRound( value : Number ) : int + public function weightedRound(value:Number):int { - var floor : int = Math.floor(value); - return (m_generator.next() / m_generator.period) > (value - floor) ? floor : floor + 1; + var floor:int = Math.floor(value); + return(m_generator.next() / m_generator.period) > (value - floor) ? floor : floor + 1; } /** @@ -100,9 +115,9 @@ public class Random * is assumed to be a Vector or Array (or otherwise have a length property and * be able to be accessed with the index operators). */ - public function choice(...items):Object + public function choice(... items):Object { - var choice : int; + var choice:int; if(items.length == 1) { choice = integer(0, items[0].length - 1); @@ -115,11 +130,25 @@ public class Random } } - public function toString(verbose:Boolean=false):String + /** + * Destructively shuffles the container using the Fisher-Yates algorithm. + */ + public function shuffle(container:Object):void { - return "[Random" + m_generator + "]"; + for(var x:int = container.length - 1; x > 0; x--) + { + var j:int = integer(0, x + 1); + var tmp:* = container[x]; + container[x] = container[j]; + container[j] = tmp; + } } + public function toString(verbose:Boolean = false):String + { + return "[Random" + m_generator + "]"; + } + //-------------------------------------- // PRIVATE & PROTECTED INSTANCE METHODS //-------------------------------------- diff --git a/src/nexus/math/TinyMersenneTwisterGenerator.as b/src/nexus/math/TinyMersenneTwisterGenerator.as new file mode 100644 index 0000000..9f2c73f --- /dev/null +++ b/src/nexus/math/TinyMersenneTwisterGenerator.as @@ -0,0 +1,130 @@ +// Copyright (c) 2013 Robert Zubek and SomaSim LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +package nexus.math +{ + +import flash.utils.*; + +/** + * Actionscript port of the TinyMT random number generator, a small-footprint + * variant of the Mersenne Twister. + * + *

Original algorithm by Mutsuo Saito and Makoto Matsumoto. + * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/TINYMT/index.html + */ +public class TinyMersenneTwisterGenerator implements ISeededPRNG +{ + // initialization constants (in the MT reference implementation + // they're specified by the user during initialization, but they don't change) + private static const MAT1:uint = 0x70707070; + private static const MAT2:uint = 0x07070707; + private static const TMAT:uint = 0x55555555; + + protected static const MIN_LOOP:int = 8; + protected static const PRE_LOOP:int = 8; + + private var m_seed:uint; + private var m_currentState:uint; + private var m_numbersGenerated:int; + + public function TinyMersenneTwisterGenerator(seed:uint = 1) + { + this.seed = seed; + } + + public final function get seed():uint { return m_seed; } + public final function set seed(value:uint):void + { + m_seed = value; + m_currentState = m_seed; + m_numbersGenerated = 0; + + stateVars[0] = m_seed; + stateVars[1] = MAT1; + stateVars[2] = MAT2; + stateVars[3] = TMAT; + + for(var i:int = 1; i < MIN_LOOP; i++) + { + stateVars[i & 3] ^= i + 1812433253 * (stateVars[(i - 1) & 3] ^ (stateVars[(i - 1) & 3] >>> 30)); + } + + for(var j:int = 0; j < PRE_LOOP; j++) + { + nextState(); + } + } + + public final function get currentState():uint { return m_currentState; } + + [Inline] + public function get period():uint { return 4294967295; } + + public final function get numbersGenerated():int + { + return m_numbersGenerated; + } + + public function next():uint + { + nextState(); + return temper(); + } + + // state variables + private const stateVars:Vector. = new Vector.(4, true); + + /** + * Advances internal state + */ + [Inline] + private final function nextState():void + { + var x:uint; + var y:uint; + + y = stateVars[3]; + x = (stateVars[0] & 0x7fffffff) ^ stateVars[1] ^ stateVars[2]; + x ^= (x << 1); + y ^= (y >>> 1) ^ x; + stateVars[0] = stateVars[1]; + stateVars[1] = stateVars[2]; + stateVars[2] = x ^ (y << 10); + stateVars[3] = y; + stateVars[1] ^= -(y & 1) & MAT1; + stateVars[2] ^= -(y & 1) & MAT2; + + m_numbersGenerated++; + } + + /** + * Outputs an unsigned int from the current internal stats + */ + [Inline] + private final function temper():uint + { + var t0:uint = stateVars[3]; + var t1:uint = stateVars[0] ^ (stateVars[2] >>> 8); + t0 ^= t1; + t0 ^= -(t1 & 1) & TMAT; + return t0; + } +} + +} \ No newline at end of file diff --git a/test/src/MainTestSuite.as b/test/src/MainTestSuite.as index d00141a..45968df 100644 --- a/test/src/MainTestSuite.as +++ b/test/src/MainTestSuite.as @@ -29,6 +29,7 @@ public class MainTestSuite public var json : JsonSerializerTest; public var rngLehmer : LehmerGeneratorTest; + public var rngTinymt : TinyMTGeneratorTest; public var rngNative : NativeRandomGeneratorTest; public var hmac : HMACTest; diff --git a/test/src/test/nexus/math/AbstractIPRNGTest.as b/test/src/test/nexus/math/AbstractIPRNGTest.as index 5047cb3..432af87 100644 --- a/test/src/test/nexus/math/AbstractIPRNGTest.as +++ b/test/src/test/nexus/math/AbstractIPRNGTest.as @@ -81,52 +81,99 @@ public class AbstractIPRNGTest extends TestCase { var rand : Random = new Random(m_generator); var highHit : Boolean = false; + var midHit : Boolean = false; var lowHit : Boolean = false; const low : int = 1; const high : int = 100; + const mid :int = low + high / 2; for(var x : int = 0; x < DISTRIBUTION_ITERATIONS; ++x) { var num : int = rand.integer(low, high); - assertTrue(num + " is not <= " + high, num <= high); + assertTrue(num + " is not < " + high, num < high); assertTrue(num + " is not >= " + low, num >= low); if(!lowHit && num == low) { lowHit = true; } - if(!highHit && num == high) + if(!midHit && num == mid) + { + midHit = true; + } + if(!highHit && num == high - 1) { highHit = true; } } - assertTrue(high + " never generated", highHit); assertTrue(low + " never generated", lowHit); + assertTrue(mid + " never generated", midHit); + assertTrue((high - 1) + " never generated", highHit); + } + + public function test_randomUnsignedInteger():void + { + var rand : Random = new Random(m_generator); + var highHit : Boolean = false; + var midHit : Boolean = false; + var lowHit : Boolean = false; + const low : int = 1; + const high : int = 100; + const mid :int = low + high / 2; + for(var x : int = 0; x < DISTRIBUTION_ITERATIONS; ++x) + { + var num : uint = rand.unsignedInteger(low, high); + assertTrue(num + " is not < " + high, num < high); + assertTrue(num + " is not >= " + low, num >= low); + if(!lowHit && num == low) + { + lowHit = true; + } + if(!midHit && num == mid) + { + midHit = true; + } + if(!highHit && num == high - 1) + { + highHit = true; + } + } + + assertTrue(low + " never generated", lowHit); + assertTrue(mid + " never generated", midHit); + assertTrue((high - 1) + " never generated", highHit); } public function test_randomFloat():void { var rand : Random = new Random(m_generator); var highHit : Boolean = false; + var midHit : Boolean = false; var lowHit : Boolean = false; const low : Number = 1.0; const high : Number = 100.0; + const mid :Number = low + high / 2; for(var x : int = 0; x < DISTRIBUTION_ITERATIONS; ++x) { var num : int = rand.float(low, high); - assertTrue(num + " is not <= " + high, num <= high); + assertTrue(num + " is not < " + high, num < high); assertTrue(num + " is not >= " + low, num >= low); if(!lowHit && num == low) { lowHit = true; } + if(!midHit && num == mid) + { + midHit = true; + } if(!highHit && num == high - 1) { highHit = true; } } - assertTrue((high - 1).toFixed(1) + " never generated", highHit); assertTrue(low.toFixed(1) + " never generated", lowHit); + assertTrue(mid.toFixed(1) + " never generated", midHit); + assertTrue((high - 1).toFixed(1) + " never generated", highHit); } public function test_boolean():void @@ -146,11 +193,12 @@ public class AbstractIPRNGTest extends TestCase } } var diff : int = Math.abs(trueCount - falseCount); - trace("test_boolean", m_generator, diff, trueCount, falseCount, diff / DISTRIBUTION_ITERATIONS * 100); - assertTrue((diff / DISTRIBUTION_ITERATIONS * 100) < 2.0); + //trace("test_boolean", m_generator, diff, trueCount, falseCount, diff / DISTRIBUTION_ITERATIONS * 100); + trace("test_boolean", m_generator, "variance", diff / DISTRIBUTION_ITERATIONS); + assertTrue(diff / DISTRIBUTION_ITERATIONS < .02); } - public function test_round1():void + public function test_round():void { var rand : Random = new Random(m_generator); var upCount : int = 0; @@ -158,7 +206,9 @@ public class AbstractIPRNGTest extends TestCase const num : Number = 4.5; for(var x : int = 0; x < DISTRIBUTION_ITERATIONS; ++x) { - if(rand.weightedRound(num) == 4) + var result : int = rand.weightedRound(num); + assertTrue("weightedRound(" + num + ") returned " + result, result == 4 || result == 5); + if(result == 4) { downCount++; } @@ -168,8 +218,9 @@ public class AbstractIPRNGTest extends TestCase } } var diff : int = Math.abs(upCount - downCount); - trace("test_round1", m_generator, diff, upCount, downCount, diff / DISTRIBUTION_ITERATIONS * 100); - assertTrue((diff / DISTRIBUTION_ITERATIONS * 100) < 2.0); + //trace("test_round", m_generator, diff, upCount, downCount, diff / DISTRIBUTION_ITERATIONS * 100); + trace("test_round", m_generator, "variance", diff / DISTRIBUTION_ITERATIONS); + assertTrue("Variance > .02 => " + (diff / DISTRIBUTION_ITERATIONS), diff / DISTRIBUTION_ITERATIONS < .02); } //-------------------------------------- diff --git a/test/src/test/nexus/math/TinyMTGeneratorTest.as b/test/src/test/nexus/math/TinyMTGeneratorTest.as new file mode 100644 index 0000000..a7a9187 --- /dev/null +++ b/test/src/test/nexus/math/TinyMTGeneratorTest.as @@ -0,0 +1,56 @@ +// Copyright 2012 Malachi Griffie +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +package test.nexus.math +{ + +import asunit.framework.TestCase; +import nexus.math.TinyMersenneTwisterGenerator; + +/** + * ... + */ +public class TinyMTGeneratorTest extends AbstractISeededPRNGTest +{ + //-------------------------------------- + // CLASS CONSTANTS + //-------------------------------------- + + //-------------------------------------- + // INSTANCE VARIABLES + //-------------------------------------- + + //-------------------------------------- + // CONSTRUCTOR + //-------------------------------------- + + public function TinyMTGeneratorTest(testMethod:String = null) + { + super(testMethod); + + } + + //-------------------------------------- + // SETUP & TEARDOWN + //-------------------------------------- + + override protected function setUp():void + { + m_algorithm = TinyMersenneTwisterGenerator; + + super.setUp(); + } + + override protected function tearDown():void + { + super.tearDown(); + } + + //-------------------------------------- + // TESTS + //-------------------------------------- +} + +} \ No newline at end of file