diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8e74bf3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..3163c22 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,14 @@ + +# Directories # +############### +build/ +reports/ +dist/ + +# Node.js # +########### +/node_modules/ + +# Git # +####### +.git* diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..d09f1fa --- /dev/null +++ b/.jshintrc @@ -0,0 +1,71 @@ +{ + "bitwise": false, + "camelcase": false, + "curly": true, + "eqeqeq": true, + "es3": false, + "forin": true, + "freeze": true, + "immed": true, + "indent": 4, + "latedef": "nofunc", + "newcap": true, + "noarg": true, + "noempty": false, + "nonbsp": true, + "nonew": true, + "plusplus": false, + "quotmark": "single", + "undef": true, + "unused": true, + "strict": true, + "maxparams": 10, + "maxdepth": 5, + "maxstatements": 100, + "maxcomplexity": false, + "maxlen": 1000, + "asi": false, + "boss": false, + "debug": false, + "eqnull": false, + "esnext": false, + "evil": false, + "expr": false, + "funcscope": false, + "globalstrict": false, + "iterator": false, + "lastsemic": false, + "laxbreak": false, + "laxcomma": false, + "loopfunc": false, + "maxerr": 1000, + "moz": false, + "multistr": false, + "notypeof": false, + "proto": false, + "scripturl": false, + "shadow": false, + "sub": true, + "supernew": false, + "validthis": false, + "noyield": false, + "browser": true, + "browserify": true, + "couch": false, + "devel": true, + "dojo": false, + "jasmine": false, + "jquery": false, + "mocha": true, + "mootools": false, + "node": true, + "nonstandard": false, + "prototypejs": false, + "qunit": false, + "rhino": false, + "shelljs": false, + "worker": false, + "wsh": false, + "yui": false, + "globals": {} +} \ No newline at end of file diff --git a/.npmignore b/.npmignore index 42781b3..9db298d 100644 --- a/.npmignore +++ b/.npmignore @@ -13,6 +13,7 @@ examples/ reports/ support/ test/ +benchmark/ # Node.js # ########### @@ -46,4 +47,6 @@ Desktop.ini # Utilities # ############# .jshintrc -.travis.yml \ No newline at end of file +.jshintignore +.travis.yml +.editorconfig diff --git a/.travis.yml b/.travis.yml index 7c16620..29cff27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ language: node_js node_js: - - "0.10" + - '0.12' + - '0.11' + - '0.10' + - '0.8' + - 'iojs' +before_install: + - npm update -g npm after_script: - - npm run coveralls \ No newline at end of file + - npm run coveralls + diff --git a/LICENSE b/LICENSE index 2fe3939..d8731b6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Athan Reines. +Copyright (c) 2014-2015 Athan Reines. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ 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. \ No newline at end of file +SOFTWARE. diff --git a/Makefile b/Makefile index e3cad30..8f4ec98 100644 --- a/Makefile +++ b/Makefile @@ -8,22 +8,18 @@ NODE_ENV ?= test # NOTES # -NOTES ?= 'TODO|FIXME' +NOTES ?= 'TODO|FIXME|WARNING|HACK|NOTE' # MOCHA # -# Specify the test framework bin locations: MOCHA ?= ./node_modules/.bin/mocha _MOCHA ?= ./node_modules/.bin/_mocha - -# Specify the mocha reporter: MOCHA_REPORTER ?= spec # ISTANBUL # -# Istanbul configuration: ISTANBUL ?= ./node_modules/.bin/istanbul ISTANBUL_OUT ?= ./reports/coverage ISTANBUL_REPORT ?= lcov @@ -31,6 +27,12 @@ ISTANBUL_LCOV_INFO_PATH ?= $(ISTANBUL_OUT)/lcov.info ISTANBUL_HTML_REPORT_PATH ?= $(ISTANBUL_OUT)/lcov-report/index.html +# JSHINT # + +JSHINT ?= ./node_modules/.bin/jshint +JSHINT_REPORTER ?= ./node_modules/jshint-stylish/stylish.js + + # FILES # @@ -81,7 +83,8 @@ test-istanbul-mocha: node_modules NODE_ENV=$(NODE_ENV) \ NODE_PATH=$(NODE_PATH_TEST) \ $(ISTANBUL) cover \ - --dir $(ISTANBUL_OUT) --report $(ISTANBUL_REPORT) \ + --dir $(ISTANBUL_OUT) \ + --report $(ISTANBUL_REPORT) \ $(_MOCHA) -- \ --reporter $(MOCHA_REPORTER) \ $(TESTS) @@ -99,6 +102,18 @@ view-istanbul-report: +# LINT # + +.PHONY: lint lint-jshint + +lint: lint-jshint + +lint-jshint: node_modules + $(JSHINT) \ + --reporter $(JSHINT_REPORTER) \ + ./ + + # NODE # # Installing node_modules: @@ -116,7 +131,6 @@ clean-node: # CLEAN # - .PHONY: clean clean: diff --git a/README.md b/README.md index 26885ef..5f4be04 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ signum === [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Dependencies][dependencies-image]][dependencies-url] -> Signum function. +> [Signum](http://en.wikipedia.org/wiki/Sign_function) function. ## Installation @@ -16,44 +16,61 @@ For use in the browser, use [browserify](https://github.com/substack/node-browse ## Usage -To use the module, - ``` javascript var signum = require( 'compute-signum' ); ``` -#### signum( x ) +#### signum( x[, options] ) -Evaluates the signum function. The method accepts a single argument: either a single `numeric` value or an `array` of numeric values, which may include `NaN`, `+infinity`, and `-infinity`. For an input `array`, the signum function is evaluated for each value. +Evaluates the [signum](http://en.wikipedia.org/wiki/Sign_function) function. The method accepts as its first argument either a single `numeric` value or an `array` of numeric values, which may include `NaN`, `+infinity`, and `-infinity`. For an input `array`, the [signum](http://en.wikipedia.org/wiki/Sign_function) function is evaluated for each value. ``` javascript -signum( -10 ); +var sgn = signum( -10 ); // returns -1 -signum( [ -10, -1, -0, 0, 1, 10 ] ); -// returns [...] +var sgns = signum( [ -10, -1, -0, 0, 1, 10 ] ); +// returns [ -1, -1, 0, 0, 1, 1 ] ``` +When provided an input `array`, the function accepts two `options`: -## Examples +* __copy__: `boolean` indicating whether to return a new `array` containing the signum values. Default: `true`. +* __accessor__: accessor `function` for accessing numeric values in object `arrays`. + +To mutate the input `array` (e.g. when input values can be discarded or when optimizing memory usage), set the `copy` option to `false`. ``` javascript -var signum = require( 'compute-signum' ); +var arr = [ -10, -1, -0, 0, 1, 10 ]; -var data = new Array( 100 ); -for ( var i = 0; i < data.length; i++ ) { - data[ i ] = Math.random() - 0.5; -} +var sgns = signum( arr ); +// returns [ -1, -1, -0, 0, 1, 1 ] -console.log( signum( data ) ); +console.log( arr === sgns ); +// returns true ``` -To run the example code from the top-level application directory, +For object `arrays`, provide an accessor `function` for accessing `array` values. -``` bash -$ node ./examples/index.js +``` javascript +var data = [ + ['beep', -10], + ['boop', -1], + ['bap', 1], + ['baz', 10] +]; + +function getValue( d, i ) { + return d[ 1 ]; +} + +var sgns = signum( data, getValue ); +// returns [ -1, -1, 1, 1 ] ``` +__Note__: the function returns an `array` with a length equal to the original input `array`. + + + ## Notes @@ -70,11 +87,33 @@ Value | Sign The above results are compatible with an ECMAScript 6 [proposal](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-math.sign) from Mozilla, which would extend the `Math` object to include the method `Math.sign()`. Currently, only [Mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign) implements this proposal. + +## Examples + +``` javascript +var signum = require( 'compute-signum' ); + +var data = new Array( 100 ); +for ( var i = 0; i < data.length; i++ ) { + data[ i ] = Math.random() - 0.5; +} + +console.log( signum( data ) ); +``` + +To run the example code from the top-level application directory, + +``` bash +$ node ./examples/index.js +``` + + + ## Tests ### Unit -Unit tests use the [Mocha](http://visionmedia.github.io/mocha) test framework with [Chai](http://chaijs.com) assertions. To run the tests, execute the following command in the top-level application directory: +Unit tests use the [Mocha](http://mochajs.org) test framework with [Chai](http://chaijs.com) assertions. To run the tests, execute the following command in the top-level application directory: ``` bash $ make test @@ -98,15 +137,15 @@ $ make view-cov ``` +--- ## License -[MIT license](http://opensource.org/licenses/MIT). +[MIT license](http://opensource.org/licenses/MIT). ---- ## Copyright -Copyright © 2014. Athan Reines. +Copyright © 2014-2015. Athan Reines. [npm-image]: http://img.shields.io/npm/v/compute-signum.svg @@ -125,4 +164,4 @@ Copyright © 2014. Athan Reines. [dev-dependencies-url]: https://david-dm.org/dev/compute-io/signum [github-issues-image]: http://img.shields.io/github/issues/compute-io/signum.svg -[github-issues-url]: https://github.com/compute-io/signum/issues \ No newline at end of file +[github-issues-url]: https://github.com/compute-io/signum/issues diff --git a/examples/index.js b/examples/index.js index 5a27353..1a94bed 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,3 +1,5 @@ +'use strict'; + var signum = require( './../lib' ); var data = new Array( 100 ); @@ -5,4 +7,4 @@ for ( var i = 0; i < data.length; i++ ) { data[ i ] = Math.random() - 0.5; } -console.log( signum( data ) ); \ No newline at end of file +console.log( signum( data ) ); diff --git a/lib/index.js b/lib/index.js index 8367e0c..0de68e9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,79 +1,104 @@ +'use strict'; + +// MODULES + +var isArray = require( 'validate.io-array' ), + isObject = require( 'validate.io-object' ), + isBoolean = require( 'validate.io-boolean-primitive' ), + isFunction = require( 'validate.io-function' ); + + +// SIGNUM // + /** +* FUNCTION: signum( x ) +* Determines the sign of a numeric value. * -* COMPUTE: signum -* -* -* DESCRIPTION: -* - Signum function. -* -* -* NOTES: -* [1] -* -* -* TODO: -* [1] -* -* -* LICENSE: -* MIT -* -* Copyright (c) 2014. Athan Reines. -* -* -* AUTHOR: -* Athan Reines. kgryte@gmail.com. 2014. -* +* @private +* @param {Number} x - input value */ +function signum( x ) { + // [0] NaN + if ( x !== x ) { + return NaN; + } + // [1] +-0 + if ( !x ) { + return x; + } + // [2] x < 0 + if ( x < 0 ) { + return -1; + } + // [3] x > 0 + return 1; +} // end FUNCTION signum() -(function() { - 'use strict'; - // SIGNUM // +// EXPORTS // - /** - * FUNCTION: signum(x ) - * Determines the sign of a numeric value. - */ - function signum( x ) { - if ( typeof x !== 'number' ) { - throw new TypeError( 'signum()::invalid input argument. Must provide a numeric value.' ); - } - // Cases... +/** +* FUNCTION: signum( x[, options] ) +* Evaluates the signum function. +* +* @param {Number[]|Number} x - numeric array or single number +* @param {Object} [options] - function options +* @param {Function} [options.accessor] - accessor function for accessing array values +* @param {Boolean} [options.copy=true] - boolean indicating whether to return a new array +* @returns {Number[]|Number} signum values +*/ +module.exports = function( x, opts ) { + var copy = true, + clbk, + len, + arr, + v, i; - // [0] NaN - if ( x !== x ) { - return NaN; + if ( typeof x === 'number' ) { + return signum( x ); + } + if ( !isArray( x ) ) { + throw new TypeError( 'signum()::invalid input argument. Must provide either a single number primitive or an array of number primitives. Value: `' + x + '`.' ); + } + if ( arguments.length > 1 ) { + if ( !isObject( opts ) ) { + throw new TypeError( 'signum()::invalid input argument. Options argument must be an object. Value: `' + opts + '`.' ); } - // [1] +-0 - if ( !x ) { - return x; + if ( opts.hasOwnProperty( 'accessor' ) ) { + clbk = opts.accessor; + if ( !isFunction ( clbk ) ) { + throw new TypeError( 'signum()::invalid option. Accessor must be a function. Option: `' + clbk + '`.' ); + } } - // [2] x < 0 - if ( x < 0 ) { - return -1; + if ( opts.hasOwnProperty( 'copy' ) ) { + copy = opts.copy; + if ( !isBoolean( copy ) ) { + throw new TypeError( 'signum()::invalid option. Copy option must be a boolean primitive. Option: `' + copy + '`.' ); + } } - // [3] x > 0 - return 1; - } // end FUNCTION signum() - - - // EXPORTS // - - module.exports = function( x ) { - if ( typeof x === 'number' ) { - return signum( x ); + } + len = x.length; + if ( copy === true ) { + arr = new Array( len ); + } else { + arr = x; + } + if ( clbk ) { + for ( i = 0; i < len; i++ ) { + v = clbk( x[ i ], i ); + if ( typeof v !== 'number' ) { + throw new TypeError( 'signum()::invalid input argument. Must provide an array of number primitives. Value: `' + x + '`.' ); + } + arr[ i ] = signum( v ); } - if ( !Array.isArray( x ) ) { - throw new TypeError( 'signum()::invalid input argument. Must provide an array.' ); + } else { + for ( i = 0; i < len; i++ ) { + v = x[ i ]; + if ( typeof v !== 'number' ) { + throw new TypeError( 'signum()::invalid input argument. Must provide an array of number primitives. Value: `' + x + '`.' ); + } + arr[ i ] = signum( v ); } - var len = x.length, - arr = new Array( len ); - - for ( var i = 0; i < len; i++ ) { - arr[ i ] = signum( x[ i ] ); - } - return arr; - }; - -})(); \ No newline at end of file + } + return arr; +}; // end FUNCTION signum() diff --git a/package.json b/package.json index fd55b6a..52f5f14 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ { "name": "Athan Reines", "email": "kgryte@gmail.com" + }, + { + "name": "Philipp Burckhardt", + "email": "pburckhardt@outlook.com" } ], "scripts": { @@ -29,17 +33,27 @@ "sign", "signum", "mathematics", - "math" + "math", + "array", + "positive", + "negative" ], "bugs": { "url": "https://github.com/compute-io/signum/issues" }, - "dependencies": {}, + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-boolean-primitive": "^1.0.0", + "validate.io-function": "^1.0.2", + "validate.io-object": "^1.0.3" + }, "devDependencies": { - "chai": "1.x.x", - "mocha": "1.x.x", + "chai": "2.x.x", + "mocha": "2.x.x", "coveralls": "^2.11.1", - "istanbul": "^0.3.0" + "istanbul": "^0.3.0", + "jshint": "2.x.x", + "jshint-stylish": "^1.0.0" }, "licenses": [ { diff --git a/test/test.js b/test/test.js index 9e8fcff..ffb1b30 100644 --- a/test/test.js +++ b/test/test.js @@ -1,3 +1,5 @@ +/* global require, describe, it */ +'use strict'; // MODULES // @@ -17,21 +19,22 @@ var expect = chai.expect, // TESTS // describe( 'compute-signum', function tests() { - 'use strict'; it( 'should export a function', function test() { expect( signum ).to.be.a( 'function' ); }); - it( 'should throw an error if not provided a numeric value or an array', function test() { + it( 'should throw an error if not provided a numeric value or a numeric array', function test() { var values = [ - '5', - true, - undefined, - null, - {}, - function(){} - ]; + '5', + new Number( 5 ), + true, + undefined, + null, + {}, + function(){}, + [1,'2',3] + ]; for ( var i = 0; i < values.length; i++ ) { expect( badValue( values[i] ) ).to.throw( TypeError ); @@ -44,28 +47,117 @@ describe( 'compute-signum', function tests() { } }); - it( 'should throw an error if a data array contains non-numeric values', function test() { + it( 'should throw an error if an input array contains non-numeric values', function test() { var values = [ - '5', - true, - undefined, - null, - [], - {}, - function(){} - ]; + '5', + true, + undefined, + null, + [], + {}, + function(){} + ]; for ( var i = 0; i < values.length; i++ ) { - expect( badValue( [ values[i] ] ) ).to.throw( TypeError ); + expect( badValue( values[i] ) ).to.throw( TypeError ); } function badValue( value ) { return function() { - signum( value ); + signum( [ value ] ); + }; + } + }); + + it( 'should throw an error if an accessed value is not numeric', function test() { + expect( badValue ).to.throw( TypeError ); + + function badValue() { + signum( [{'x':'beep'}], { + 'accessor': getValue + }); + } + function getValue( d ) { + return d.x; + } + }); + + it( 'should throw an error if provided an options argument which is not an object', function test() { + var values = [ + '5', + 5, + true, + undefined, + null, + NaN, + function(){}, + [] + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( TypeError ); + } + function badValue( value ) { + return function() { + signum( [1,2,3], value ); + }; + } + }); + + it( 'should throw an error if provided an accessor which is not a function', function test() { + var values = [ + '5', + 5, + true, + undefined, + null, + NaN, + [], + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[ i ] ) ).to.throw( TypeError ); + } + function badValue( value ) { + return function() { + signum( [1,2,3], { + 'accessor': value + }); }; } }); + it( 'should throw an error if provided a `copy` option which is not a boolean primitive', function test() { + var values = [ + '5', + 5, + new Boolean( true ), + function(){}, + undefined, + null, + NaN, + [], + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[ i ] ) ).to.throw( TypeError ); + } + + function badValue( value ) { + return function() { + signum( [1,-2,3,-4,5], { + 'copy': value + }); + }; + } + }); + + it( 'should return a numeric value if provided a numeric value', function test() { + assert.isNumber( signum( 1 ) ); + }); + it( 'should return NaN if provided a NaN', function test() { var val = signum( NaN ); assert.isNumber( val ); @@ -82,42 +174,95 @@ describe( 'compute-signum', function tests() { assert.strictEqual( val, -1 ); }); - it( 'should return 0 if provide zero', function test() { + it( 'should return 0 if provided zero', function test() { var val = signum( 0 ); assert.strictEqual( val, 0 ); }); - it( 'should return -0 if provide negative zero', function test() { + it( 'should return -0 if provided negative zero', function test() { var val = signum( -0 ); assert.strictEqual( val, -0 ); assert.strictEqual( 1/val, Number.NEGATIVE_INFINITY ); }); - it( 'should return a numeric value if provided a numeric value', function test() { - assert.isNumber( signum( 1 ) ); - }); - it( 'should return an array of numbers if provided an array', function test() { var values = [ 10, -1, 234, -0.344, 5 ], - val; + arr; - val = signum( values ); - assert.isArray( val ); - for ( var i = 0; i < val.length; i++ ) { - assert.isNumber( val[ i ] ); + arr = signum( values ); + assert.isArray( arr ); + for ( var i = 0; i < arr.length; i++ ) { + assert.isNumber( arr[ i ] ); } }); + it( 'should return an empty array if provided an empty array', function test() { + var actual = signum( [] ); + assert.ok( actual.length === 0 ); + }); + + it( 'should not mutate the input array by default', function test() { + var data, expected, actual; + + data = [ 1, -2, 3 ]; + + actual = signum( data ); + expected = [ 1, -1, 1 ]; + + assert.deepEqual( actual, expected ); + assert.ok( actual !== data ); + }); + it( 'should evaluate the signum function', function test() { var values, expected, actual; values = [ 10, -1, 234, -0.344, 5, 0, -0 ]; + actual = signum( values ); expected = [ 1, -1, 1, -1, 1, 0, -0 ]; - actual = signum( values ); + assert.deepEqual( actual, expected ); + }); + + it( 'should evaluate the signum function using an accessor function', function test() { + var data, expected, actual; + + data = [ + {'x':10}, + {'x':-1}, + {'x':234}, + {'x':-0.344}, + {'x':5}, + {'x':0}, + {'x':-0} + ]; + + actual = signum( data, { + 'accessor': getValue + }); + expected = [ 1, -1, 1, -1, 1, 0, -0 ]; + + assert.strictEqual( actual.length, data.length ); + assert.deepEqual( actual, expected ); + + function getValue( d ) { + return d.x; + } + }); + + it( 'should evaluate the signum function and mutate the input array', function test() { + + var values, expected, actual; + + values = [ 10, -1, 234, -0.344, 5, 0, -0 ]; + + actual = signum( values, { + 'copy':false + }); + expected = [ 1, -1, 1, -1, 1, 0, -0 ]; assert.deepEqual( actual, expected ); + assert.ok( actual === values ); }); -}); \ No newline at end of file +});