├── .npmignore ├── _config.yml ├── src ├── const.ts ├── utils.ts ├── unabbreviateNumber.ts └── abbreviateNumber.ts ├── .eslintignore ├── index.ts ├── .prettierignore ├── .prettierrc ├── images └── cover.png ├── jest.config.json ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md └── workflows │ ├── continuous-integration.yml │ └── codeql-analysis.yml ├── SECURITY.md ├── test ├── utils.test.ts ├── abbreviateNumber.test.ts └── unabbreviateNumber.test.ts ├── LICENSE ├── .gitignore ├── package.json ├── .all-contributorsrc ├── tsconfig.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | jest.config.json 2 | test/ -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | export const defaultSymbols = ["", "k", "M", "G", "T", "P", "E"]; 2 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export const symbolPow = (index = 0) => Math.pow(10, index * 3); 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | babel.config.js 2 | node_modules/* 3 | README.md 4 | jest.config.json 5 | dist/ -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/abbreviateNumber"; 2 | export * from "./src/unabbreviateNumber"; 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | babel.config.js 2 | node_modules/* 3 | README.md 4 | jest.config.json 5 | dist/ 6 | .* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "trailingComma": "all", 4 | "semi": true 5 | } -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moh3n9595/js-abbreviation-number/HEAD/images/cover.png -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" 4 | }, 5 | "testRegex": "/test/.*", 6 | "testEnvironment": "node", 7 | "moduleFileExtensions": [ 8 | "ts", 9 | "js" 10 | ] 11 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: false, 5 | es6: true, 6 | node: true, 7 | jest: true 8 | }, 9 | plugins: [ 10 | "@typescript-eslint", 11 | ], 12 | extends: [ 13 | "eslint:recommended", 14 | "plugin:@typescript-eslint/recommended" 15 | ], 16 | parser: "@typescript-eslint/parser", 17 | parserOptions: { 18 | ecmaVersion: 2018, 19 | sourceType: "module" 20 | }, 21 | rules: { 22 | "arrow-body-style": 2, 23 | "semi": ["error", "always"], 24 | "quotes": ["error", "double"], 25 | "prefer-const": 1, 26 | "@typescript-eslint/no-unused-vars": "off", 27 | } 28 | }; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /src/unabbreviateNumber.ts: -------------------------------------------------------------------------------- 1 | import { defaultSymbols } from "./const"; 2 | import { symbolPow } from "./utils"; 3 | 4 | 5 | export function unabbreviateNumber(num: string, symbols = defaultSymbols) { 6 | const numberPattern = "[+-]?([0-9]*[.])?[0-9]+"; 7 | const symbolPattern = `${symbols.join("|")}`; 8 | const pattern = `^(${numberPattern})(${symbolPattern})$`; 9 | const regex = new RegExp(pattern); 10 | const match = num.match(pattern) || []; 11 | 12 | if (regex.test(num) && match.length > 3) { 13 | const symbol = match[3]; 14 | const symbolValue = symbolPow(symbols.indexOf(symbol)); 15 | const pureNum = Number(match[1]); 16 | return pureNum * symbolValue; 17 | } else { 18 | throw Error("This is not a valid input"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/utils.test.ts: -------------------------------------------------------------------------------- 1 | import {symbolPow} from "../src/utils"; 2 | 3 | test("undefined should be --> 1", () => { 4 | expect(symbolPow()).toBe(1); 5 | }); 6 | 7 | test("0 should be --> 1", () => { 8 | expect(symbolPow(0)).toBe(1); 9 | }); 10 | 11 | test("1 should be --> 1000", () => { 12 | expect(symbolPow(1)).toBe(1000); 13 | }); 14 | 15 | test("2 should be --> 1000000", () => { 16 | expect(symbolPow(2)).toBe(1000000); 17 | }); 18 | 19 | test("3 should be --> 1000000000", () => { 20 | expect(symbolPow(3)).toBe(1000000000); 21 | }); 22 | 23 | test("4 should be --> 1000000000000", () => { 24 | expect(symbolPow(4)).toBe(1000000000000); 25 | }); 26 | 27 | test("5 should be --> 1000000000000000", () => { 28 | expect(symbolPow(5)).toBe(1000000000000000); 29 | }); 30 | 31 | test("6 should be --> 1000000000000000000", () => { 32 | expect(symbolPow(6)).toBe(1000000000000000000); 33 | }); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pull request 3 | about: Add or Edit some feature 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 16 | 17 | 18 | 19 | **What**: 20 | 21 | 22 | 23 | **Why**: 24 | 25 | 26 | 27 | **How**: 28 | 29 | 30 | 31 | **Checklist**: 32 | 33 | 34 | 35 | 36 | 37 | - [ ] Tests 38 | - [ ] Ready to be merged 39 | 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mohsen Madani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | builds/ 34 | 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | dist/ 40 | coverage/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | 66 | # mac store 67 | .DS_Store 68 | 69 | # CI/CD 70 | 71 | 72 | # IDEA 73 | .vscode -------------------------------------------------------------------------------- /src/abbreviateNumber.ts: -------------------------------------------------------------------------------- 1 | import { defaultSymbols } from "./const"; 2 | 3 | interface AbbreviateOptions { 4 | padding?: boolean; 5 | symbols?: string[]; 6 | } 7 | 8 | const defaultOptions = { 9 | padding: true, 10 | symbols: defaultSymbols, 11 | }; 12 | 13 | export function abbreviateNumber( 14 | num: number, 15 | digit = 1, 16 | options?: AbbreviateOptions | AbbreviateOptions["symbols"], 17 | ): string { 18 | // Previous options style 19 | if (Array.isArray(options)) { 20 | options = { symbols: options }; 21 | } 22 | 23 | const { symbols, padding } = Object.assign({}, defaultOptions, options); 24 | 25 | // handle negatives 26 | const sign = Math.sign(num) >= 0; 27 | num = Math.abs(num); 28 | 29 | // what tier? (determines SI symbol) 30 | const tier = (Math.log10(num) / 3) | 0; 31 | 32 | // if zero, we don't need a suffix 33 | if (tier == 0) return (!sign ? "-" : "") + num.toString(); 34 | 35 | // get suffix and determine scale 36 | const suffix = symbols[tier]; 37 | if (!suffix) throw new RangeError(); 38 | 39 | const scale = Math.pow(10, tier * 3); 40 | 41 | // scale the number 42 | const scaled = num / scale; 43 | 44 | let rounded = scaled.toFixed(digit); 45 | if (!padding) { 46 | rounded = String(Number(rounded)); 47 | } 48 | 49 | // format number and add suffix 50 | return (!sign ? "-" : "") + rounded + suffix; 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-abbreviation-number", 3 | "version": "1.4.0", 4 | "description": "Abbreviate numbers in javascript", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "test": "jest --config=jest.config.json --collectCoverage", 9 | "build": "tsc", 10 | "prepare": "tsc", 11 | "lint": "eslint *.ts", 12 | "prettier-write": "prettier --write \"*.ts\"", 13 | "prettier-check": "prettier --check \"*.ts\"", 14 | "codecov": "codecov -t 6fc5b4e4-4e80-42dd-b9ed-057a670de47a" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/moh3n9595/js-abbreviation-number.git" 19 | }, 20 | "homepage": "https://github.com/moh3n9595/js-abbreviation-number#readme", 21 | "keywords": [ 22 | "js", 23 | "number", 24 | "abbreviation", 25 | "javascript" 26 | ], 27 | "author": { 28 | "name": "Mohsen Madani", 29 | "email": "mohsenando@gmail.com" 30 | }, 31 | "files": [ 32 | "dist" 33 | ], 34 | "license": "MIT", 35 | "devDependencies": { 36 | "@types/jest": "^25.1.0", 37 | "@types/node": "^9.4.6", 38 | "@typescript-eslint/eslint-plugin": "^5.5.0", 39 | "@typescript-eslint/parser": "^2.18.0", 40 | "all-contributors-cli": "^6.26.1", 41 | "codecov": "^3.6.5", 42 | "eslint": "^8.4.0", 43 | "eslint-plugin-import": "^2.19.1", 44 | "jest": "^25.5.4", 45 | "prettier": "1.19.1", 46 | "ts-jest": "^22.4.1", 47 | "typescript": "^3.7.5" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [12.x] 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Set up Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - name: Cache dependencies 27 | uses: actions/cache@v2 28 | with: 29 | path: | 30 | **/node_modules 31 | key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} 32 | 33 | - name: Install dependencies 34 | run: npm install 35 | 36 | - name: Run the lints 37 | run: npm run lint 38 | 39 | - name: Run Prettier 40 | run: npm run prettier-check 41 | 42 | - name: Run the tests 43 | run: npm run test 44 | 45 | - name: Upload coverage to Codecov 46 | uses: codecov/codecov-action@v3 47 | 48 | - name: Build 49 | run: npm run build 50 | 51 | - name: Archive production 52 | uses: actions/upload-artifact@v2 53 | with: 54 | name: dist 55 | path: | 56 | dist 57 | !dist/**/*.md 58 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "js-abbreviation-number", 3 | "projectOwner": "moh3n9595", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": ["README.md"], 7 | "imageSize": 75, 8 | "commit": true, 9 | "commitConvention": "angular", 10 | "contributors": [ 11 | { 12 | "login": "moh3n9595", 13 | "name": "Mohsen", 14 | "avatar_url": "https://avatars.githubusercontent.com/u/20948388?v=4", 15 | "profile": "https://github.com/moh3n9595", 16 | "contributions": ["code", "maintenance", "test"] 17 | }, 18 | { 19 | "login": "M0rteza-M", 20 | "name": "M0rteza-M", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/79398146?v=4", 22 | "profile": "https://github.com/M0rteza-M", 23 | "contributions": ["code", "test"] 24 | }, 25 | { 26 | "login": "fregante", 27 | "name": "Federico Brigante", 28 | "avatar_url": "https://avatars.githubusercontent.com/u/1402241?v=4", 29 | "profile": "https://github.com/fregante", 30 | "contributions": ["code", "test"] 31 | }, 32 | { 33 | "login": "jackdomleo7", 34 | "name": "Jack Domleo", 35 | "avatar_url": "https://avatars.githubusercontent.com/u/43371432?v=4", 36 | "profile": "https://jackdomleo.dev/", 37 | "contributions": ["code", "test"] 38 | }, 39 | { 40 | "login": "gustavobonassa", 41 | "name": "Gustavo Bonassa", 42 | "avatar_url": "https://avatars.githubusercontent.com/u/26508215?v=4", 43 | "profile": "https://github.com/gustavobonassa", 44 | "contributions": ["code", "test"] 45 | } 46 | ], 47 | "contributorsPerLine": 7, 48 | "linkToUsage": false 49 | } 50 | 51 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '40 0 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: ['javascript'] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /test/abbreviateNumber.test.ts: -------------------------------------------------------------------------------- 1 | import {abbreviateNumber} from "../src/abbreviateNumber"; 2 | 3 | test("-25 should be --> -25", () => { 4 | expect(abbreviateNumber(-25, 0)).toBe("-25"); 5 | }); 6 | 7 | test("-999 should be --> -999", () => { 8 | expect(abbreviateNumber(-999, 2)).toBe("-999"); 9 | }); 10 | 11 | test("1000 should be --> 1k", () => { 12 | expect(abbreviateNumber(1000, 0)).toBe("1k"); 13 | }); 14 | 15 | test("1000 should be --> 1.0k", () => { 16 | expect(abbreviateNumber(1000, 1)).toBe("1.0k"); 17 | }); 18 | 19 | test("1000 should be --> 1.00k", () => { 20 | expect(abbreviateNumber(1000, 2)).toBe("1.00k"); 21 | }); 22 | 23 | test("1000 should be --> 1k without padding", () => { 24 | expect(abbreviateNumber(1000, 2, { padding: false })).toBe("1k"); 25 | }); 26 | 27 | test("12345 should be --> 12k", () => { 28 | expect(abbreviateNumber(12345, 0)).toBe("12k"); 29 | }); 30 | 31 | test("12345 should be --> 12.3k", () => { 32 | expect(abbreviateNumber(12345, 1)).toBe("12.3k"); 33 | }); 34 | 35 | test("12344 should be --> 12.34k", () => { 36 | expect(abbreviateNumber(12344, 2)).toBe("12.34k"); 37 | }); 38 | 39 | test("123456789112345678923456789 should be --> RangeError!", () => { 40 | // eslint-disable-next-line @typescript-eslint/no-loss-of-precision 41 | expect(() => abbreviateNumber(123456789112345678923456789, 0)).toThrow( 42 | RangeError, 43 | ); 44 | }); 45 | 46 | test("0 should be --> 0", () => { 47 | expect(abbreviateNumber(0, 2)).toBe("0"); 48 | }); 49 | 50 | test("12 should be --> 12", () => { 51 | expect(abbreviateNumber(12, 2)).toBe("12"); 52 | }); 53 | 54 | test("-1234 should be --> -1.2k", () => { 55 | expect(abbreviateNumber(-1234, 1)).toBe("-1.2k"); 56 | }); 57 | 58 | test("47475782130 should be --> 47.48G", () => { 59 | expect(abbreviateNumber(47475782130, 2)).toBe("47.48G"); 60 | }); 61 | 62 | test("47475782130 should be --> 47.48T", () => { 63 | expect(abbreviateNumber(47475782130000, 2)).toBe("47.48T"); 64 | }); 65 | 66 | test("-1234 should be --> -1.2Kilo", () => { 67 | expect(abbreviateNumber(-1234, 1, ["", "Kilo"])).toBe("-1.2Kilo"); 68 | }); 69 | 70 | test("-1234 should be --> -1.2Kilo with object", () => { 71 | expect(abbreviateNumber(-1234, 1, { symbols: ["", "Kilo"] })).toBe( 72 | "-1.2Kilo", 73 | ); 74 | }); 75 | 76 | test("-1200 should be --> -1.2Kilo without padding", () => { 77 | expect( 78 | abbreviateNumber(-1200, 4, { symbols: ["", "Kilo"], padding: false }), 79 | ).toBe("-1.2Kilo"); 80 | }); 81 | 82 | test("-1234567 should be --> -1234.5Kilo", () => { 83 | expect(abbreviateNumber(-1234, 1, { symbols: ["", "Kilo"] })).toBe( 84 | "-1.2Kilo", 85 | ); 86 | }); 87 | 88 | test("12.34 should be --> 12.34", () => { 89 | expect(abbreviateNumber(12.34, 2)).toBe("12.34"); 90 | }); 91 | 92 | test("1234.56 should be --> 1.2k", () => { 93 | expect(abbreviateNumber(1234.56, 1)).toBe("1.2k"); 94 | }); 95 | 96 | test("12 should be --> 12", () => { 97 | expect(abbreviateNumber(12)).toBe("12"); 98 | }); 99 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": ["es2017", "es7", "es6", "dom"], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | //"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "exclude": [ 67 | "node_modules", 68 | "dist", 69 | "test" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |

5 |

js-abbreviation-number

6 |

Abbreviate numbers in javascript

7 | 8 | ![CI/CD](https://github.com/moh3n9595/js-abbreviation-number/workflows/Continuous%20Integration/badge.svg) 9 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/moh3n9595/js-abbreviation-number/blob/master/LICENSE) 10 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg)](https://github.com/moh3n9595/js-abbreviation-number/compare) 11 | [![codecov](https://codecov.io/gh/moh3n9595/js-abbreviation-number/branch/master/graph/badge.svg)](https://codecov.io/gh/moh3n9595/js-abbreviation-number) [![CodeFactor](https://www.codefactor.io/repository/github/moh3n9595/js-abbreviation-number/badge/master)](https://www.codefactor.io/repository/github/moh3n9595/js-abbreviation-number/overview/master) 12 | [![NPM](https://img.shields.io/npm/v/js-abbreviation-number.svg)](https://www.npmjs.com/package/js-abbreviation-number) 13 | [![GitHub contributors](https://img.shields.io/github/contributors/moh3n9595/js-abbreviation-number.svg)](https://GitHub.com/moh3n9595/js-abbreviation-number/contributors/) 14 | [![Npm package yearly downloads](https://badgen.net/npm/dy/js-abbreviation-number)](https://npmjs.com/package/js-abbreviation-number) 15 | 16 |
17 |
18 | 19 | ## Installation 20 | 21 | ``` 22 | yarn add js-abbreviation-number 23 | ``` 24 | or 25 | ``` 26 | npm i js-abbreviation-number 27 | ``` 28 | 29 | ## Usage 30 | 31 | #### abbreviateNumber 32 | 33 | ```js 34 | import { abbreviateNumber } from "js-abbreviation-number"; 35 | 36 | const num = abbreviateNumber(1000, 1); // expected 1.0k 37 | 38 | const num = abbreviateNumber(12, 0); // expected 12 39 | 40 | const num = abbreviateNumber(1100, 2); // expected 1.10k 41 | 42 | const num = abbreviateNumber(1100, 2, {padding: false}); // expected 1.1k 43 | 44 | const num = abbreviateNumber(1234.56, 2); // expected 1.23k 45 | 46 | const num = abbreviateNumber(1234, 1, {symbols: ['', 'kg']}); // expected 1.2kg 47 | 48 | const num = abbreviateNumber(1234567, 1, {symbols: ['', 'kg']}); // expected 1234.5kg 49 | ``` 50 | 51 | `abbreviateNumber(num: number, digit?: number, options?: {symbols?: string[]; padding: boolean}): string` 52 | 53 | `digit` is optional (unless you need to specify the `options` object). It defaults to 1. 54 | 55 | The `options` object is optional too: 56 | 57 | - `symbols` can be an array of units, defaulting to `["", "k", "M", "G", "T", "P", "E"]`. 58 | - `padding` determines whether to keep the exact number of digits or to drop trailing zeroes. 59 | 60 | #### unabbreviateNumber 61 | 62 | ```js 63 | import { unabbreviateNumber } from "js-abbreviation-number"; 64 | 65 | const num = unabbreviateNumber("-25"); // expected -25 66 | 67 | const num = unabbreviateNumber("-1.3k"); // expected -1300 68 | 69 | const num = unabbreviateNumber("1.123456k"); // expected 1123.456 70 | 71 | const num = unabbreviateNumber("666kilo", ["", "kilo", "Mega"]); // expected 666000 72 | ``` 73 | 74 | `unabbreviateNumber(num: string, symbols?: string[]): number` 75 | 76 | `symbols` is optional that can be an array of units, defaulting to `["", "k", "M", "G", "T", "P", "E"]`. 77 | 78 | ## Contributing 79 | 80 | Thank you for your interest in contributing! Please feel free to put up a PR for any issue or feature request. 81 | 82 | ## License 83 | 84 | This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/moh3n9595/js-abbreviation-number/blob/master/LICENSE) file for details 85 | 86 | 87 | ## Contributors ✨ 88 | 89 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
Mohsen
Mohsen

💻 🚧 ⚠️
M0rteza-M
M0rteza-M

💻 ⚠️
Federico Brigante
Federico Brigante

💻 ⚠️
Jack Domleo
Jack Domleo

💻 ⚠️
Gustavo Bonassa
Gustavo Bonassa

💻 ⚠️
105 | 106 | 107 | 108 | 109 | 110 | 111 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! -------------------------------------------------------------------------------- /test/unabbreviateNumber.test.ts: -------------------------------------------------------------------------------- 1 | import { unabbreviateNumber } from "../src/unabbreviateNumber"; 2 | 3 | test("0 should be 0", () => { 4 | expect(unabbreviateNumber("0")).toBe(0); 5 | }); 6 | 7 | test("0000 should be 0", () => { 8 | expect(unabbreviateNumber("0000")).toBe(0); 9 | }); 10 | 11 | test("25 should be 25", () => { 12 | expect(unabbreviateNumber("25")).toBe(25); 13 | }); 14 | 15 | test("666 should be 666", () => { 16 | expect(unabbreviateNumber("666")).toBe(666); 17 | }); 18 | 19 | test("-25 should be -25", () => { 20 | expect(unabbreviateNumber("-25")).toBe(-25); 21 | }); 22 | 23 | test("-4800 should be -4800", () => { 24 | expect(unabbreviateNumber("-4800")).toBe(-4800); 25 | }); 26 | 27 | test("-0820 should be -820", () => { 28 | expect(unabbreviateNumber("-820")).toBe(-820); 29 | }); 30 | 31 | test("09950 should be 9950", () => { 32 | expect(unabbreviateNumber("9950")).toBe(9950); 33 | }); 34 | 35 | test("25k should be 25000", () => { 36 | expect(unabbreviateNumber("25k")).toBe(25000); 37 | }); 38 | 39 | test("-25k should be -25000", () => { 40 | expect(unabbreviateNumber("-25k")).toBe(-25000); 41 | }); 42 | 43 | test("1.3k should be 1300", () => { 44 | expect(unabbreviateNumber("1.3k")).toBe(1300); 45 | }); 46 | 47 | test("-1.3k should be -1300", () => { 48 | expect(unabbreviateNumber("-1.3k")).toBe(-1300); 49 | }); 50 | 51 | test("1.123456k should be 1123.456", () => { 52 | expect(unabbreviateNumber("1.123456k")).toBe(1123.456); 53 | }); 54 | 55 | test("-1.123456k should be -1123.456", () => { 56 | expect(unabbreviateNumber("-1.123456k")).toBe(-1123.456); 57 | }); 58 | 59 | test("zZz should be Error", () => { 60 | expect(() => unabbreviateNumber("zZz")).toThrow( 61 | new Error("This is not a valid input"), 62 | ); 63 | }); 64 | 65 | test("abc should be Error", () => { 66 | expect(() => unabbreviateNumber("abc")).toThrow( 67 | new Error("This is not a valid input"), 68 | ); 69 | }); 70 | 71 | test("abck should be Error", () => { 72 | expect(() => unabbreviateNumber("abck")).toThrow( 73 | new Error("This is not a valid input"), 74 | ); 75 | }); 76 | 77 | test("zZzT should be Error", () => { 78 | expect(() => unabbreviateNumber("abcT")).toThrow( 79 | new Error("This is not a valid input"), 80 | ); 81 | }); 82 | 83 | test("kzZz should be Error", () => { 84 | expect(() => unabbreviateNumber("kzZz")).toThrow( 85 | new Error("This is not a valid input"), 86 | ); 87 | }); 88 | 89 | test("abkc should be Error", () => { 90 | expect(() => unabbreviateNumber("abkc")).toThrow( 91 | new Error("This is not a valid input"), 92 | ); 93 | }); 94 | 95 | test("237M should be 237000000", () => { 96 | expect(unabbreviateNumber("237M")).toBe(237000000); 97 | }); 98 | 99 | test("00237k should be 237000", () => { 100 | expect(unabbreviateNumber("00237k")).toBe(237000); 101 | }); 102 | 103 | test("-00237k should be -237000", () => { 104 | expect(unabbreviateNumber("-237k")).toBe(-237000); 105 | }); 106 | 107 | test("237zZz should be Error", () => { 108 | expect(() => unabbreviateNumber("237zZz")).toThrow( 109 | new Error("This is not a valid input"), 110 | ); 111 | }); 112 | 113 | test("zZz237 should be Error", () => { 114 | expect(() => unabbreviateNumber("zZz237")).toThrow( 115 | new Error("This is not a valid input"), 116 | ); 117 | }); 118 | 119 | test("237zk should be Error", () => { 120 | expect(() => unabbreviateNumber("237zk")).toThrow( 121 | new Error("This is not a valid input"), 122 | ); 123 | }); 124 | 125 | test("2z37k should be Error", () => { 126 | expect(() => unabbreviateNumber("2z37k")).toThrow( 127 | new Error("This is not a valid input"), 128 | ); 129 | }); 130 | 131 | test("z237k should be Error", () => { 132 | expect(() => unabbreviateNumber("z237k")).toThrow( 133 | new Error("This is not a valid input"), 134 | ); 135 | }); 136 | 137 | test("237kz should be Error", () => { 138 | expect(() => unabbreviateNumber("237kz")).toThrow( 139 | new Error("This is not a valid input"), 140 | ); 141 | }); 142 | 143 | test("237k666 should be Error", () => { 144 | expect(() => unabbreviateNumber("237k666")).toThrow( 145 | new Error("This is not a valid input"), 146 | ); 147 | }); 148 | 149 | test("237kkk should be Error", () => { 150 | expect(() => unabbreviateNumber("237kkk")).toThrow( 151 | new Error("This is not a valid input"), 152 | ); 153 | }); 154 | 155 | test("237k k should be Error", () => { 156 | expect(() => unabbreviateNumber("237k k")).toThrow( 157 | new Error("This is not a valid input"), 158 | ); 159 | }); 160 | 161 | test("666 with [ , kilo, Mega, Giga, Tera, Peta, Exa] should be 666", () => { 162 | expect( 163 | unabbreviateNumber("666", [ 164 | "", 165 | "kilo", 166 | "Mega", 167 | "Giga", 168 | "Tera", 169 | "peta", 170 | "Exa", 171 | ]), 172 | ).toBe(666); 173 | }); 174 | 175 | test("666k with [ , kilo, Mega, Giga, Tera, Peta, Exa] should be Error", () => { 176 | expect(() => 177 | unabbreviateNumber("666k", [ 178 | "", 179 | "kilo", 180 | "Mega", 181 | "Giga", 182 | "Tera", 183 | "peta", 184 | "Exa", 185 | ]), 186 | ).toThrow(new Error("This is not a valid input")); 187 | }); 188 | 189 | test("666kilo with [ , kilo, Mega, Giga, Tera, Peta, Exa] should be 666000", () => { 190 | expect( 191 | unabbreviateNumber("666kilo", [ 192 | "", 193 | "kilo", 194 | "Mega", 195 | "Giga", 196 | "Tera", 197 | "peta", 198 | "Exa", 199 | ]), 200 | ).toBe(666000); 201 | }); 202 | 203 | test("666 kilo with [ , kilo, Mega, Giga, Tera, Peta, Exa] should be Error", () => { 204 | expect(() => 205 | unabbreviateNumber("666 kilo", [ 206 | "", 207 | "kilo", 208 | "Mega", 209 | "Giga", 210 | "Tera", 211 | "peta", 212 | "Exa", 213 | ]), 214 | ).toThrow(new Error("This is not a valid input")); 215 | }); 216 | 217 | test("666Exa with [ , kilo, Mega, Giga, Tera, Peta, Exa] should be 666000000000000000000", () => { 218 | expect( 219 | unabbreviateNumber("666Exa", [ 220 | "", 221 | "kilo", 222 | "Mega", 223 | "Giga", 224 | "Tera", 225 | "peta", 226 | "Exa", 227 | ]), 228 | ).toBe(666000000000000000000); 229 | }); 230 | 231 | test("666kilo with [ , kilo, Mega] should be 666000", () => { 232 | expect(unabbreviateNumber("666kilo", ["", "kilo", "Mega"])).toBe(666000); 233 | }); 234 | 235 | test("666Giga with [ , kilo, Mega] should be Error", () => { 236 | expect(() => unabbreviateNumber("666Giga", ["", "kilo", "Mega"])).toThrow( 237 | new Error("This is not a valid input"), 238 | ); 239 | }); 240 | 241 | test("666T with [ , kilo, Mega] should be Error", () => { 242 | expect(() => unabbreviateNumber("666T", ["", "kilo", "Mega"])).toThrow( 243 | new Error("This is not a valid input"), 244 | ); 245 | }); 246 | 247 | test("666Zetta with [ , kilo, Mega, Giga, Tera, peta, Exa, Zetta] should be 666000000000000000000000", () => { 248 | expect( 249 | unabbreviateNumber("666Zetta", [ 250 | "", 251 | "kilo", 252 | "Mega", 253 | "Giga", 254 | "Tera", 255 | "peta", 256 | "Exa", 257 | "Zetta", 258 | ]), 259 | ).toBe(666000000000000000000000); 260 | }); 261 | --------------------------------------------------------------------------------