From 658e3fe9beea20eda0e31b7ca4d63b59f4ea81e4 Mon Sep 17 00:00:00 2001 From: Danny Hurlburt Date: Thu, 4 May 2017 10:56:36 -0600 Subject: [PATCH 1/3] Add Code and Tests --- .gitignore | 9 +++ package.json | 55 +++++++++++++++++ src/index.test.ts | 148 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 84 ++++++++++++++++++++++++++ tsconfig.json | 17 ++++++ 5 files changed, 313 insertions(+) create mode 100644 .gitignore create mode 100644 package.json create mode 100644 src/index.test.ts create mode 100644 src/index.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f55f273 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +/coverage +/dist + +npm-debug.log diff --git a/package.json b/package.json new file mode 100644 index 0000000..8d51d2d --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "qc-round", + "version": "0.0.0", + "author": { + "name": "Danny Hurlburt", + "url": "https://github.com/dhurlburtusa" + }, + "description": "A more useful alternative to `Math.round`", + "keywords": [ + "javascript", + "Math", + "round", + "typescript" + ], + "homepage": "https://github.com/hypersoftllc/qc-round#readme", + "bugs": { + "url": "https://github.com/hypersoftllc/qc-round/issues" + }, + "license": "ISC", + "files": [ + "src/index.ts" + ], + "main": "src/index.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/hypersoftllc/qc-round.git" + }, + "scripts": { + "build": "tsc", + "test": "jest --color --coverage", + "travisci": "npm test" + }, + "jest": { + "transform": { + "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" + }, + "testPathIgnorePatterns": [ + "/dist/", + "/node_modules/" + ], + "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", + "moduleFileExtensions": [ + "js", + "json", + "ts", + "tsx" + ] + }, + "devDependencies": { + "@types/jest": "^19.2.2", + "jest": "^19.0.2", + "ts-jest": "^19.0.14", + "typescript": "^2.3.2" + } +} diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..b8d3cef --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,148 @@ + +import { round } from './index'; + +describe('qc-round', () => { + + describe('`round`', () => { + + it('should be a function', () => { + expect(typeof round).toBe('function'); + }); + + it('called with various values should return expected value', () => { + expect(round(-2.6)).toBe(-2.6); + expect(round(-2.5)).toBe(-2.5); + expect(round(-1.2)).toBe(-1.2); + expect(round(1.2)).toBe(1.2); + expect(round(2.5)).toBe(2.5); + expect(round(2.6)).toBe(2.6); + + expect(round(0, 1)).toBe(0); + expect(round(0, 0)).toBe(0); + expect(round(0, -1)).toBe(0); + + // 1.005 is known to be a problematic number when it comes to rounding. + expect(round(1.005, 2)).toBe(0); + expect(round(1.005, 1)).toBe(0); + expect(round(1.005, 0)).toBe(1); + expect(round(1.005, -1)).toBe(1); + expect(round(1.005, -2)).toBe(1.01); + expect(round(1.005, -3)).toBe(1.005); + expect(round(1.005)).toBe(1.005); + + expect(round(1234.5678, 5)).toBe(0); + expect(round(1234.5678, 4)).toBe(0); + expect(round(1234.5678, 3)).toBe(1000); + expect(round(1234.5678, 2)).toBe(1200); + expect(round(1234.5678, 1)).toBe(1230); + expect(round(1234.5678, 0)).toBe(1235); + expect(round(1234.5678, -1)).toBe(1234.6); + expect(round(1234.5678, -2)).toBe(1234.57); + expect(round(1234.5678, -3)).toBe(1234.568); + expect(round(1234.5678, -4)).toBe(1234.5678); + expect(round(1234.5678, -5)).toBe(1234.5678); + expect(round(1234.5678)).toBe(1234.5678); + + // Use a value that contains an 'e' when toString is called on it. This ensures a branch in the code is covered. + expect(round(1.23e81, 10)).toBe(1.23e81); + }); + + it('called with no arguments should return `undefined`', () => { + expect(round()).toBeUndefined(); + }); + + it('called with `undefined` should return `undefined`', () => { + expect(round(undefined)).toBeUndefined(); + }); + + it('called with a single number argument should return the first argument', () => { + expect(round(Number.NEGATIVE_INFINITY)).toBe(Number.NEGATIVE_INFINITY); + expect(round(-Number.MAX_VALUE)).toBe(-Number.MAX_VALUE); + expect(round(-Number.MIN_VALUE)).toBe(-Number.MIN_VALUE); + expect(round(0)).toBe(0); + expect(round(0.0)).toBe(0); + expect(round(Number.MIN_VALUE)).toBe(Number.MIN_VALUE); + expect(round(1234.5678)).toBe(1234.5678); + expect(round(Number.MAX_VALUE)).toBe(Number.MAX_VALUE); + expect(round(Infinity)).toBe(Infinity); + expect(round(Number.POSITIVE_INFINITY)).toBe(Number.POSITIVE_INFINITY); + }); + + it('called with an `undefined` second argument should return the first argument', () => { + expect(round(Number.NEGATIVE_INFINITY, undefined)).toBe(Number.NEGATIVE_INFINITY); + expect(round(-Number.MAX_VALUE, undefined)).toBe(-Number.MAX_VALUE); + expect(round(-Number.MIN_VALUE, undefined)).toBe(-Number.MIN_VALUE); + expect(round(0, undefined)).toBe(0); + expect(round(0.0, undefined)).toBe(0); + expect(round(Number.MIN_VALUE, undefined)).toBe(Number.MIN_VALUE); + expect(round(1234.5678, undefined)).toBe(1234.5678); + expect(round(Number.MAX_VALUE, undefined)).toBe(Number.MAX_VALUE); + expect(round(Infinity, undefined)).toBe(Infinity); + expect(round(Number.POSITIVE_INFINITY, undefined)).toBe(Number.POSITIVE_INFINITY); + }); + + it('called with a `null` second argument should return the first argument', () => { + expect(round(Number.NEGATIVE_INFINITY, null)).toBe(Number.NEGATIVE_INFINITY); + expect(round(-Number.MAX_VALUE, null)).toBe(-Number.MAX_VALUE); + expect(round(-Number.MIN_VALUE, null)).toBe(-Number.MIN_VALUE); + expect(round(0, null)).toBe(0); + expect(round(0.0, null)).toBe(0); + expect(round(Number.MIN_VALUE, null)).toBe(Number.MIN_VALUE); + expect(round(1234.5678, null)).toBe(1234.5678); + expect(round(Number.MAX_VALUE, null)).toBe(Number.MAX_VALUE); + expect(round(Infinity, null)).toBe(Infinity); + expect(round(Number.POSITIVE_INFINITY, null)).toBe(Number.POSITIVE_INFINITY); + }); + + it('called with a non-integral second argument should return the first argument', () => { + expect(round(Number.NEGATIVE_INFINITY, 1.2)).toBe(Number.NEGATIVE_INFINITY); + expect(round(-Number.MAX_VALUE, 1.2)).toBe(-Number.MAX_VALUE); + expect(round(-Number.MIN_VALUE, 1.2)).toBe(-Number.MIN_VALUE); + expect(round(0, 1.2)).toBe(0); + expect(round(0.0, 1.2)).toBe(0); + expect(round(Number.MIN_VALUE, 1.2)).toBe(Number.MIN_VALUE); + expect(round(1234.5678, 1.2)).toBe(1234.5678); + expect(round(Number.MAX_VALUE, 1.2)).toBe(Number.MAX_VALUE); + expect(round(Infinity, 1.2)).toBe(Infinity); + expect(round(Number.POSITIVE_INFINITY, 1.2)).toBe(Number.POSITIVE_INFINITY); + }); + + it('called with `Number.NEGATIVE_INFINITY` and `-Infinity` should return `Number.NEGATIVE_INFINITY`', () => { + expect(round(Number.NEGATIVE_INFINITY, -Infinity)).toBe(Number.NEGATIVE_INFINITY); + }); + + it('called with `Infinity` and `-Infinity` should return `Infinity`', () => { + expect(round(Infinity, -Infinity)).toBe(Infinity); + }); + + it('called with `Number.POSITIVE_INFINITY` and `-Infinity` should return `Number.POSITIVE_INFINITY`', () => { + expect(round(Number.POSITIVE_INFINITY, -Infinity)).toBe(Number.POSITIVE_INFINITY); + }); + + it('called with `Number.NEGATIVE_INFINITY` and `0` should return `Number.NEGATIVE_INFINITY`', () => { + expect(round(Number.NEGATIVE_INFINITY, 0)).toBe(Number.NEGATIVE_INFINITY); + }); + + it('called with `Infinity` and `0` should return `Infinity`', () => { + expect(round(Infinity, 0)).toBe(Infinity); + }); + + it('called with `Number.POSITIVE_INFINITY` and `0` should return `Number.POSITIVE_INFINITY`', () => { + expect(round(Number.POSITIVE_INFINITY, 0)).toBe(Number.POSITIVE_INFINITY); + }); + + it('called with `Number.NEGATIVE_INFINITY` and `Infinity` should return `Number.NEGATIVE_INFINITY`', () => { + expect(round(Number.NEGATIVE_INFINITY, Infinity)).toBe(Number.NEGATIVE_INFINITY); + }); + + it('called with `Infinity` and `Infinity` should return `Infinity`', () => { + expect(round(Infinity, Infinity)).toBe(Infinity); + }); + + it('called with `Number.POSITIVE_INFINITY` and `Infinity` should return `Number.POSITIVE_INFINITY`', () => { + expect(round(Number.POSITIVE_INFINITY, Infinity)).toBe(Number.POSITIVE_INFINITY); + }); + + }); + +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..17b67f4 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,84 @@ + +const CEIL: string = 'ceil'; +const FLOOR: string = 'floor'; +const ROUND: string = 'round'; + +// ========================================================================== +// The following is modified from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round. +/** + * Decimal adjustment of a number. + * + * @private + * + * @param {string} type - The type of adjustment. Must be one of `'ceil'`, `'floor'`, or `'round'`. + * @param {number} value - The number. + * @param {number} exp - The exponent (the 10 logarithm of the adjustment base). + * + * @returns {number} The adjusted value. + */ +function _decimalAdjust(type: string, value: any, exp: number): any { + let val: string, vals: string[]; + + if (typeof value !== 'number') { + return value; + } + value = +value; + + if (typeof exp !== 'number' || exp != exp || exp === Number.POSITIVE_INFINITY || exp === Number.NEGATIVE_INFINITY) { + return value; + } + exp = +exp; + if (exp === 0) { + return (Math)[type](value); + } + // If the exp is not an integer... + if (exp % 1 !== 0) { + return value; + } + + // Shift + val = value.toString(); + vals = val.split('e'); + value = (Math)[type](+(`${vals[0]}e${(vals[1] ? +vals[1] - exp : -exp)}`)); + + // Shift back + val = value.toString(); + vals = val.split('e'); + return +(`${vals[0]}e${(vals[1] ? +vals[1] + exp : exp)}`); +} + + +// ========================================================================== +/** + * Similar to `Math.round` but is capable of rounding to different decimal places. `Math.round` will only round a + * number to the nearest integer which is effectively rounding to zero decimal places. + * + * round(20.49); // 20 + * round(20.5); // 21 + * round(-20.5); // -20 + * round(-20.51); // -21 + * Math.round(1.005 * 100)/100; // 1. Due to inaccurate floating point arithmetic, this rounds incorrectly. + * round(1.005, -2) // 1.01. But this doesn't. + * round(1234.5678, -5) // 1234.5678 + * round(1234.5678, -4) // 1234.5678 + * round(1234.5678, -3) // 1234.568 + * round(1234.5678, -2) // 1234.57 + * round(1234.5678, -1) // 1234.6 + * round(1234.5678, 0) // 1235 + * round(1234.5678) // 1235 + * round(1234.5678, 1) // 1230 + * round(1234.5678, 2) // 1200 + * round(1234.5678, 3) // 1000 + * round(1234.5678, 4) // 0 + * + * @param {number} value - The number. + * @param {number} [exp=0] - The exponent (the 10 logarithm of the adjustment base). May be positive or negative. + * + * @returns {number} The rounded value. + */ +function round(value?: any, exp?: number): any { + return _decimalAdjust(ROUND, value, exp); +} + +// ========================================================================== +export { round }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1c56730 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "es6" + ], + "module": "commonjs", + "newLine": "CRLF", + "noImplicitAny": true, + "outDir": "./dist/", + "pretty": true, + "target": "es3" + }, + "include": [ + "./src/**/*" + ] +} From 28ac2f6914101f817f47755cd5847c9119a4c1fa Mon Sep 17 00:00:00 2001 From: Danny Hurlburt Date: Thu, 4 May 2017 11:01:28 -0600 Subject: [PATCH 2/3] Update README File --- README.md | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/README.md b/README.md index 417a535..5cb595d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,130 @@ # qc-round + Rounds a number to a specified number of decimal places. + +**Note**: This package is written in [TypeScript] and so will need to be transpiled into JavaScript. This does not +mean that it can't be used with a [JavaScript] or [CoffeeScript] -based project. + +## Usage + +```sh +npm install --save qc-round +``` + +## Transpiling + +There are many ways to transpile your project's code and its dependencies. In all cases, you will need the TypeScript +compiler when this package is a dependency. + +**Add TypeScript Compiler as a Dependency to Your Project** + +```sh +npm install --save-dev typescript +``` +You will also need a TypeScript configuration tailored to your project. + +**An Example TypeScript Configuration** + +**Note**: This configuration is highly dependent on the needs of your particular project. The following is only an +example. See http://www.typescriptlang.org/docs/handbook/tsconfig-json.html for further help. + +**tsconfig.js** +```json +{ + "compilerOptions": { + "lib": [ + "dom", + "es6" + ], + "module": "commonjs", + "noImplicitAny": true, + "outDir": "./dist/", + "pretty": true, + "target": "es3" + }, + "include": [ + "./src/**/*" + ] +} +``` + +The following demonstrates how to use [webpack] to transpile your project's code. + +### webpack + +webpack can bundle files written in JavaScript, CoffeeScript, TypeScript, et al, all into a single (or multiple) +JavaScript bundle. + +**Add webpack as a Dependency to Your Project** + +```sh +npm install --save-dev webpack +``` + +Since your package depends on `qc-round` which is written in TypeScript, you will need a webpack loader that knows +how to transpile TypeScript to JavaScript. One such loader is [awesome-typescript-loader]. + +**Add a TypeScript webpack loader Dependency to Your Project** + +```sh +npm install --save-dev awesome-typescript-loader +``` + +Your package will also need configuration for webpack and the TypeScript compiler. + +**An Example webpack Configuration** + +Use the following example or merge with an existing webpack configuration. + +```js +module.exports = { + + entry: '/path/to/your/package/entry/point', + + output: { + filename: 'your-package.js', + path: __dirname + '/dist' + }, + + resolve: { + // Add '.ts' as a resolvable extension along with any other extensions necessary for your package. + extensions: ['.js', '.json', '.ts'] + }, + + module: { + rules: [ + // All files with a '.ts' extension will be handled by 'awesome-typescript-loader'. + { test: /\.ts$/, loader: 'awesome-typescript-loader' } + ] + } + +}; +``` + +Once all of this is in place, you just need to run webpack. One way to run webpack is to add or tweak a build script +in your NPM package.json file. + +**Add/Update the package.json build Script** + +```js +... + "scripts": [ + ... + "build": "webpack" + ... + ], +... +``` + +Now you can run the following + +```sh +npm run build +``` + + +[awesome-typescript-loader]: https://www.npmjs.com/package/awesome-typescript-loader +[CoffeeScript]: http://coffeescript.org/ +[JavaScript]: https://developer.mozilla.org/en-US/docs/Web/JavaScript +[TypeScript]: https://www.typescriptlang.org/ "TypeScript - JavaScript that scales" +[webpack]: https://webpack.github.io/ "webpack module bundler" From 81bf99cfb9e7249441fe754199938eb97e59c40b Mon Sep 17 00:00:00 2001 From: Danny Hurlburt Date: Thu, 4 May 2017 11:05:23 -0600 Subject: [PATCH 3/3] Setup Travis CI to Run --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4035dc6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - "6" + - "5" + - "4" +script: npm run travisci