[amx-netlinx-common] r33 committed - - re-wrote math_is_whole_number() to function over all possible inputs...

6 views
Skip to first unread message

amx-netli...@googlecode.com

unread,
May 25, 2010, 1:56:35 AM5/25/10
to netlinx-comm...@googlegroups.com
Revision: 33
Author: kim.john.burgess
Date: Mon May 24 22:55:23 2010
Log: - re-wrote math_is_whole_number() to function over all possible inputs
rather than relying on type_casting to a non-floating point type
- added special condition early escape for sqrt()
- re-wrote test framework for allow for more flexibility and easier use
- re-wrote test_math.axi to use new test suite (currently tests
math_is_whole_number(), random(), sqrt(), fast_inv_sqrt(), fast_sqrt() )
- misc comment typo updates / whitespace removal
http://code.google.com/p/amx-netlinx-common/source/detail?r=33

Modified:
/trunk/math.axi
/trunk/test/test_math.axi
/trunk/test_utils.axi

=======================================
--- /trunk/math.axi Thu May 20 01:02:36 2010
+++ /trunk/math.axi Mon May 24 22:55:23 2010
@@ -180,19 +180,49 @@
}

/**
- * Returns TRUE if the argument has no decimal component, otherwise returns
- * FALSE.
+ * Returns true if the argument has no decimal component, otherwise returns
+ * false. +/-Inf and 0 will return true, subnormal and NaN's will return
+ * false.
*
- * @todo look up the exponent of the number as stored in IEEE754
- * format to all it to function over all values
* @param x the double to check
* @return a boolean representing the number's 'wholeness'
*/
define_function char math_is_whole_number(double x)
{
- stack_var slong wholeComponent
- wholeComponent = type_cast(x)
- return wholeComponent == x
+ stack_var char tmp
+ stack_var sinteger exp
+ stack_var long hi
+ stack_var long m_hi
+ stack_var long m_low
+ stack_var long mask
+ hi = math_double_high_to_bits(x)
+ exp = type_cast(((hi & $7FF00000) >> 20) - 1023)
+ m_hi = hi & $FFFFF
+ select {
+ active (exp == -1023 || exp == 1024): {
+ m_low = math_double_low_to_bits(x)
+ return (m_hi == 0 && m_low == 0)
+ }
+ active (exp < 0): {
+ return false
+ }
+ active (exp > 52): {
+ return true
+ }
+ active (exp > 20): {
+ for (tmp = type_cast(52 - exp); tmp; tmp--) {
+ mask = mask + (1 << (tmp - 1))
+ }
+ return (m_low & mask == 0)
+ }
+ active (1): {
+ m_low = math_double_low_to_bits(x)
+ for (tmp = type_cast(20 - exp); tmp; tmp--) {
+ mask = mask + (1 << (tmp - 1))
+ }
+ return (m_hi & mask == 0)
+ }
+ }
}

/**
@@ -252,10 +282,10 @@
}

/**
- * Returns a double value with a positive sign, greater than or equal to
0.0
+ * Returns a double value with a positive sign, greater than or equal to
0.0
* and less than 1.0.
*
- * @return a pseudorandom double greater than or equal to 0.0 and
+ * @return a pseudorandom double greater than or equal to 0.0 and
* less than 1.0
*/
define_function double random()
@@ -263,18 +293,13 @@
stack_var char i
stack_var long hi
stack_var long low
-
- // Create a (psuedo) random mantissa
for (i = 32; i; i--) {
low = low + (random_number(2) << (i - 1))
}
for (i = 20; i; i--) {
hi = hi + (random_number(2) << (i - 1))
}
-
- // Add in an exponent of 0 to make sure we get full resolution
hi = hi + (1023 << 20)
-
return math_build_double(hi, low) - 1
}

@@ -292,6 +317,16 @@
stack_var long hi
stack_var long low
stack_var double tmp
+ if (x < 0) {
+ return MATH_NaN
+ }
+ if (x = 0 ||
+ x == 1 ||
+ //x == MATH_NaN ||
+ x == MATH_NEGATIVE_INFINITY ||
+ x == MATH_POSITIVE_INFINITY) {
+ return x
+ }
tmp = math_rshift_double(x)
hi = (1 << 29) + math_double_high_to_bits(tmp) - (1 << 19)
low = math_double_low_to_bits(tmp)
@@ -322,7 +357,7 @@

/**
* Approximate the square root of the passed number based on the inverse
square
- * root algorithm in mathInvSqrt(x). This is MUCH faster than sqrt(x) and
+ * root algorithm in fast_inv_sqrt(x). This is MUCH faster than sqrt(x) and
* recommended over sqrt() for use anywhere a precise square root is not
* required. Error is approx +/-0.17%.
*
=======================================
--- /trunk/test/test_math.axi Thu May 20 01:02:36 2010
+++ /trunk/test/test_math.axi Mon May 24 22:55:23 2010
@@ -36,90 +36,224 @@
define_constant

long TEST_MATH_ITERATIONS = 1000 // number of times to execute each
- // test
-
+ // test for speed testing
+
+
+/**
+ * Test functionality and execution speed of the math_is_whole_number()
+ * function.
+ *
+ * @return a boolean reflecting success
+ */
+define_function char test_math_is_whole_number()
+{
+ stack_var double test_data[TEST_MATH_ITERATIONS]
+ stack_var long i
+ stack_var double avg
+
+ test_start('math_is_whole_number()')
+
+ // Build some random test data
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
+ test_data[i] = random() * 100000.0
+ }
+
+ // Check execution speed
+ test_timer_start()
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
+ math_is_whole_number(test_data[i])
+ }
+ test_timer_stop(TEST_MATH_ITERATIONS)
+
+
+ // Check special cases
+ test_check(math_is_whole_number(-1) == true, 'breaks on negative input')
+ test_check(math_is_whole_number(-1.5) == false, 'breaks on negative
input')
+ test_check(math_is_whole_number(0) == true, 'breaks with 0 input')
+ test_check(math_is_whole_number(MATH_NaN) == false, 'breaks with NaN')
+ test_check(math_is_whole_number(MATH_NEGATIVE_INFINITY) == true,
+ 'breaks with -inf')
+ test_check(math_is_whole_number(MATH_POSITIVE_INFINITY) == true,
+ 'breaks with +inf')
+
+ return test_end()
+}

/**
* Test functionality and execution speed of the random() function.
*
- * @return a boolean reflecting success (speed and functionality)
+ * @return a boolean reflecting success
*/
define_function char test_random()
{
stack_var double res[TEST_MATH_ITERATIONS]
stack_var long i
- stack_var long err_cnt
- stack_var long total_time
-
- // Test for execution speed
+ stack_var double avg
+
+ test_start('random()')
+
+ // Check execution speed
test_timer_start()
- for (i = max_length_array(res); i; i--) {
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
res[i] = random()
}
- total_time = test_timer_stop()
-
- return test_report_double_range('random()', res, 0.0, 1.0, total_time)
+ test_timer_stop(TEST_MATH_ITERATIONS)
+
+
+ // Check for correct functionality
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
+ avg = avg + res[i]
+ test_check(res[i] > 0.0, 'exceeds lower bound')
+ test_check(res[i] <= 1.0, 'exceeds upper bound')
+ }
+ avg = avg / TEST_MATH_ITERATIONS
+ test_add_stat('avg. value', format('%1.5f', avg), '')
+
+ return test_end()
}

/**
* Test functionality and execution speed of the sqrt() function.
*
- * @return a boolean reflecting success (speed and functionality)
+ * @return a boolean reflecting success
*/
define_function char test_sqrt()
{
stack_var double test_data[TEST_MATH_ITERATIONS]
stack_var double res[TEST_MATH_ITERATIONS]
- stack_var double tmp[TEST_MATH_ITERATIONS]
stack_var long i
- stack_var long total_time
-
- for (i = max_length_array(test_data); i; i--) {
+ stack_var double error
+ stack_var double avg_err
+ stack_var double max_err
+
+ test_start('sqrt()')
+
+ // Build some random test data
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
test_data[i] = random() * 100000.0
}

+ // Test for execution speed
test_timer_start()
- for (i = max_length_array(res); i; i--) {
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
res[i] = sqrt(test_data[i])
}
- total_time = test_timer_stop()
-
- for (i = max_length_array(res); i; i--) {
- tmp[i] = res[i] * res[i]
- }
-
- test_report_double('sqrt()', tmp, test_data, 0.001, total_time)
+ test_timer_stop(TEST_MATH_ITERATIONS)
+
+ // Check for correct functionality
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
+ error = test_error(res[i] * res[i], test_data[i])
+ test_check(error <= 0.00001, 'exceeds maximum error')
+ if (error > max_err) {
+ max_err = error
+ }
+ avg_err = avg_err + error
+ }
+ avg_err = avg_err / TEST_MATH_ITERATIONS
+ test_add_stat('max error', format('%1.3f', max_err), '%')
+ test_add_stat('avg. error', format('%1.3f', avg_err), '%')
+
+ // Check special cases
+ test_check(sqrt(-1) == -1, 'breaks with negative input')
+ test_check(sqrt(0) == 0, 'breaks with 0 input')
+ test_check(sqrt(MATH_NaN) == MATH_NaN, 'breaks with NaN')
+ test_check(sqrt(MATH_NEGATIVE_INFINITY) == MATH_NEGATIVE_INFINITY,
+ 'breaks with -inf')
+ test_check(sqrt(MATH_POSITIVE_INFINITY) == MATH_POSITIVE_INFINITY,
+ 'breaks with +inf')
+
+ return test_end()
}

+/**
+ * Test functionality and execution speed of the fast_inv_sqrt() function.
+ *
+ * @return a boolean reflecting success
+ */
+define_function char test_fast_inv_sqrt()
+{
+ stack_var float test_data[TEST_MATH_ITERATIONS]
+ stack_var float res[TEST_MATH_ITERATIONS]
+ stack_var long i
+ stack_var double error
+ stack_var double avg_err
+ stack_var double max_err
+
+ test_start('fast_inv_sqrt()')
+
+ // Build some random test data
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
+ test_data[i] = type_cast(random() * 100000.0)
+ }
+
+ // Test for execution speed
+ test_timer_start()
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
+ res[i] = fast_inv_sqrt(test_data[i])
+ }
+ test_timer_stop(TEST_MATH_ITERATIONS)
+
+
+ // Check for correct functionality
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
+ error = test_error(test_data[i] * test_data[i] * res[i] * res[i],
+ test_data[i])
+ test_check(error <= 0.35, 'exceeds maximum error')
+ if (error > max_err) {
+ max_err = error
+ }
+ avg_err = avg_err + error
+ }
+ avg_err = avg_err / TEST_MATH_ITERATIONS
+ test_add_stat('max error', format('%1.3f', max_err), '%')
+ test_add_stat('avg. error', format('%1.3f', avg_err), '%')
+
+ return test_end()
+}

/**
* Test functionality and execution speed of the fast_sqrt() function.
*
- * @return a boolean reflecting success (speed and functionality)
+ * @return a boolean reflecting success
*/
define_function char test_fast_sqrt()
{
stack_var float test_data[TEST_MATH_ITERATIONS]
- stack_var double res[TEST_MATH_ITERATIONS]
- stack_var double tmp[TEST_MATH_ITERATIONS]
+ stack_var float res[TEST_MATH_ITERATIONS]
stack_var long i
- stack_var long total_time
-
- for (i = max_length_array(test_data); i; i--) {
+ stack_var double error
+ stack_var double avg_err
+ stack_var double max_err
+
+ test_start('fast_sqrt()')
+
+ // Build some random test data
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
test_data[i] = type_cast(random() * 100000.0)
}

+ // Test for execution speed
test_timer_start()
- for (i = max_length_array(res); i; i--) {
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
res[i] = fast_sqrt(test_data[i])
}
- total_time = test_timer_stop()
-
- for (i = max_length_array(res); i; i--) {
- tmp[i] = res[i] * res[i]
- }
-
- return test_report_double('fast_sqrt()', tmp, test_data, 0.2, total_time)
+ test_timer_stop(TEST_MATH_ITERATIONS)
+
+
+ // Check for correct functionality
+ for (i = TEST_MATH_ITERATIONS; i; i--) {
+ error = test_error(res[i] * res[i], test_data[i])
+ test_check(error <= 0.35, 'exceeds maximum error')
+ if (error > max_err) {
+ max_err = error
+ }
+ avg_err = avg_err + error
+ }
+ avg_err = avg_err / TEST_MATH_ITERATIONS
+ test_add_stat('max error', format('%1.3f', max_err), '%')
+ test_add_stat('avg. error', format('%1.3f', avg_err), '%')
+
+ return test_end()
}

/**
@@ -127,13 +261,17 @@
*/
define_function test_math()
{
- println("'.............................................................'")
+ println("':::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::'")
println("'Running math library test suite. This may take a while...'")
+ println("' '")
+ test_math_is_whole_number()
test_random()
test_sqrt()
+ test_fast_inv_sqrt()
test_fast_sqrt()
+ println("' '")
println("'Math library testing complete.'")
- println("'.............................................................'")
+ println("':::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::'")
}

#end_if
=======================================
--- /trunk/test_utils.axi Thu May 20 01:02:36 2010
+++ /trunk/test_utils.axi Mon May 24 22:55:23 2010
@@ -22,7 +22,7 @@
* $Id: Math.axi 23 2010-05-12 16:29:43Z trueamx $
* tab-width: 4 columns: 80
*/
-
+
program_name='test_utils'
#if_not_defined __NCL_LIB_TEST_UTILS
#define __NCL_LIB_TEST_UTILS
@@ -37,6 +37,21 @@
// speed test timer


+define_type
+
+structure test_info {
+ char is_active // boolean containing test state
+ char name[32] // name of test
+ long num_tests // number of test cases checked
+ long num_passed // number test cases passed
+ char stats[1024] // misc stats associate with the test
+}
+
+define_variable
+
+volatile test_info ncl_test
+
+
/**
* Start a timer for use in execution speed tests.
*
@@ -60,141 +75,117 @@
}

/**
- * Stops the speed execution test timer.
+ * Stops the speed execution test timer and add the averge unit execution
+ * speed to the test stats.
*
- * @return long containing the time (ms) the timer was run for
+ * @param iterations long containing the number of individual tests run
+ * @return double containing the avg test run time
*/
-define_function long test_timer_stop()
+define_function double test_timer_stop(long iterations)
{
stack_var long elapsed
+ stack_var double avg_speed
+
if (timeline_active(TEST_TL)) {
+ timeline_pause(TEST_TL)
elapsed = timeline_get(TEST_TL)
timeline_kill(TEST_TL)
}
- return elapsed
+
+ avg_speed = 1.0 * elapsed / iterations
+
+ test_add_stat('avg. exec speed', format('%1.3f', avg_speed), "'ms (over ',
+ itoa(iterations), ' runs)'")
+
+ return avg_speed
}

/**
- * Calculates the percentage error.
+ * Init everything required when starting a test.
*
- * @return the percentage error (0.0 <= x <= 100.0)
+ * @param name the name of the test
*/
-define_function double test_error(double estimate, double actual)
-{
- return abs_value(estimate - actual) / actual * 100.0
+define_function test_start(char name[])
+{
+ if (ncl_test.is_active == false) {
+ ncl_test.is_active = true
+ ncl_test.name = name
+ ncl_test.num_tests = 0
+ ncl_test.num_passed = 0
+ ncl_test.stats = ""
+ println("'Running test: ', name")
+ } else {
+ println("'Error starting ', name, ' test. ', ncl_test.name,
+ ' already running.'")
+ }
}

/**
- * Generate a test report for a group of data which should lay between the
- * bounds of min and max.
+ * Add a stat to be printed on test completion
*
- * @param name name of the function undergoing testing
- * @param results array of doubles containing the test results
- * @param min minimum result value for pass condition (inclusive)
- * @param max maximum result value for pass condition (exclusive)
- * @param total_time total time (ms) it took to run all iterations
- * @return boolean representing test pass status
+ * @param name a string containing the name of the test statistic
+ * @param value a stirng containing the stat result
+ * @param units a string containing the units the value uses
*/
-define_function char test_report_double_range(char name[], double
results[],
- double min, double max, long total_time)
-{
- stack_var long i
- stack_var long iterations
- stack_var long errs
-
- iterations = max_length_array(results)
-
- for (i = iterations; i; i--) {
- if (results[i] < min || results[i] > max) {
- errs++
+define_function char test_add_stat(char name[], char value[], char units[])
+{
+ ncl_test.stats = "ncl_test.stats, $0D, $0A, name, ': ', value, units"
+}
+
+/**
+ * End the test and print results.
+ *
+ * @return boolean reflecting success status
+ */
+define_function char test_end()
+{
+ stack_var char tmp[256]
+
+ println("' ', itoa(ncl_test.num_passed), ' of ',
+ itoa(ncl_test.num_tests), ' tests passed'")
+ while (ncl_test.stats <> "") {
+ tmp = remove_string(ncl_test.stats, "$0D, $0A", 1)
+ if (tmp <> "$0D, $0A") {
+ if (tmp == "") {
+ tmp = ncl_test.stats
+ ncl_test.stats = ""
+ } else {
+ tmp = left_string(tmp, length_string(tmp) - 2)
+ }
+ println("' ', tmp")
}
}
-
- return test_report(name, (errs == 0), iterations, total_time,
- "itoa(errs), ' errors'", "", "")
+ println("' '")
+
+ ncl_test.is_active = false
+
+ return (ncl_test.num_passed == ncl_test.num_tests)
}

/**
- * Generates a test report for a doubles that should site within max_error
of
- * the expected results.
+ * Init everything required when starting a test.
*
- * @param name name of the function undergoing testing
- * @param results array of doubles containing the actual results
- * @param expected array of doubles containing the expected results
- * @param max_error maximum % err for pass condition
- * @param total_time total time (ms) it took to run all iterations
+ * @param pass boolean statement to check
+ * @param msg alert message if failure
*/
-define_function char test_report_double(char name[], double results[],
- double expected[], float max_error, long total_time)
-{
- stack_var long i
- stack_var long iterations
- stack_var double avg_speed
- stack_var double avg_error
-
- iterations = max_length_array(results)
-
- for (i = iterations; i; i--) {
- avg_error = avg_error + test_error(results[i], expected[i])
- }
- avg_error = avg_error / iterations
-
- return test_report(name, (max_error > avg_error), iterations, total_time,
- "'avg. error ', format('%1.3f', avg_error), '%'", "", "")
+define_function test_check(char condition, char msg[])
+{
+ ncl_test.num_tests++
+ if (condition == true) {
+ ncl_test.num_passed++
+ } else {
+ println("' ', msg")
+ }
}

/**
- * Outputs a test report and returns success / failure status.
+ * Calculates the percentage error.
*
- * @param name name of the function undergoing testing
- * @param pass boolean indication success
- * @param iterations long containing the number of iterations tested
- * @param total_time long containing time (ms) the test ran for
- * @param param1 misc result info
- * @param param2 misc result info
- * @param param3 misc result info
- * @return boolean indicating success
+ * @return the percentage error (0.0 <= x <= 100.0)
*/
-define_function char test_report(char name[], char pass, long iterations,
- long total_time, char param1[], char param2[], char param3[])
-{
- stack_var char result[4]
- stack_var char params[200]
- stack_var double avg_speed
-
- switch (pass) {
- case true: result = "'PASS'"
- case false: result = "'FAIL'"
- }
-
- avg_speed = 1.0 * total_time / iterations
-
- if (param1 <> "") {
- params = "', ', param1"
- }
-
- select {
- active (params <> "" && param2 <> ""): {
- params = "params, ', ', param2"
- }
- active (param2 <> ""): {
- params = param2
- }
- }
-
- select {
- active (params <> "" && param3 <> ""): {
- params = "params, ', ', param3"
- }
- active (param3 <> ""): {
- params = param3
- }
- }
-
- println("result, ': ', name, ' - avg. speed ', format('%1.3f', avg_speed),
- 'ms', params, ' (', itoa(iterations), ' iterations)'")
-
- return pass
+define_function double test_error(double estimate, double actual)
+{
+ return abs_value(estimate - actual) / actual * 100.0
}

#end_if
Reply all
Reply to author
Forward
0 new messages