@peter-present/format-number 0.0.1 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,115 +1,105 @@
1
1
  # format-number
2
2
 
3
- A lightweight, high-precision JavaScript/TypeScript library for rounding and formatting numbers. It handles everything from large financial figures and crypto balances to extremely small fractions with scientific subscripts.
3
+ A lightweight, zero-dependency JavaScript/TypeScript library for precise number formatting. Designed for financial applications, crypto dashboards, and scientific data where precision is paramount.
4
4
 
5
- Designed for precision-critical applications where numbers often exceed safe integer limits.
5
+ Unlike standard libraries that rely on floating-point math, **format-number** uses internal string-based arithmetic to handle numbers of any size (including `string`, `number`, and `bigint`) without losing a single decimal point.
6
6
 
7
- For detailed documentation and advanced usage, visit: [here](https://blog.peter-present.xyz/format-number)
7
+ For detailed documentation, visit: [here](https://blog.peter-present.xyz/format-number)
8
8
 
9
9
  ## Key Features
10
10
 
11
- - **High Precision**: Native support for `string`, `number`, and `bigint` without loss of precision.
12
- - **Multiple Rounding Modes**: 5 different strategies to handle rounding exactly how you need.
13
- - **Compact Formatting**: Automatically shorten large numbers with suffixes (K, M, B, T, Q).
14
- - **Small Number Notation**: Smart formatting for tiny numbers using subscript zeros (e.g., `0.0₃5`).
15
- - **Flexible Metadata**: Easily add prefixes and suffixes (e.g., Currency symbols, Units).
16
- - **Fluent API**: Chained operations for cleaner, more readable code.
17
- - **Zero Dependencies**: Keeps your bundle size minimal.
11
+ - **Zero Precision Loss**: Uses string manipulation for rounding and formatting to avoid binary floating-point errors.
12
+ - **Robust Input Parsing**: Handles messy strings with currency symbols ($€£¥), commas, underscores, and scientific notation automatically.
13
+ - **Advanced Rounding**: 5 strategies (`half`, `up`, `down`, `banker`, `truncate`) with customizable decimal precision.
14
+ - **Intelligent Notation**: Format small numbers with subscript zeros (e.g., `0.0₃5`) or standard scientific notation.
15
+ - **Flexible Compacting**: Shorten massive numbers into readable strings like `1.5T` or `20.4B`.
16
+ - **Fluent Chainable API**: Build complex formatting logic with a clean, readable syntax.
18
17
 
19
18
  ## Installation
20
19
 
21
20
  ```shell
22
21
  # npm
23
- npm install format-number
22
+ npm install @peter-present/format-number
24
23
 
25
24
  # yarn
26
- yarn add format-number
25
+ yarn add @peter-present/format-number
27
26
 
28
27
  # bun
29
- bun install format-number
28
+ bun install @peter-present/format-number
30
29
  ```
31
30
 
32
- ## Core Functions
31
+ ## Core API
33
32
 
34
- ### Rounding: `round()`
33
+ ### `formatNumber(value, options)`
35
34
 
36
- Round numbers using specific strategies and precision.
35
+ The primary entry point for one-off formatting. It combines parsing, rounding, and visualization.
37
36
 
38
37
  ```typescript
39
- import { round } from 'format-number';
38
+ import { formatNumber } from '@peter-present/format-number';
40
39
 
41
- round(123.456, { precision: 2 }); // '123.46'
42
- round(-123.45, { mode: 'up', precision: 1 }); // '-123.4' (Towards +Infinity)
43
- ```
44
-
45
- ### Compact Formatting: `compact()`
46
-
47
- Format large numbers with readable suffixes.
40
+ // Currency & Rounding
41
+ formatNumber('$1,234.567', { prefix: '', precision: 2 }); // '€1234.57'
48
42
 
49
- ```typescript
50
- import { compact } from 'format-number';
43
+ // Compact Notation
44
+ formatNumber(1500000000, { isCompact: true }); // '1.5B'
51
45
 
52
- compact(1500); // '1.5K'
53
- compact(1200000, { precision: 1 }); // '1.2M'
54
- compact('1000000000000'); // '1T'
46
+ // Special Notations
47
+ formatNumber(0.00005, { notation: 'subscript' }); // '0.0₄5'
48
+ formatNumber(12345, { notation: 'scientific' }); // '1.2345e+4'
55
49
  ```
56
50
 
57
- ### Advanced Formatting: `formatNumber()`
51
+ ### `FN(value)` - Fluent API
58
52
 
59
- A comprehensive function combining rounding, compacting, and metadata.
53
+ Best for complex, multi-step formatting requirements where readability is key.
60
54
 
61
55
  ```typescript
62
- import { formatNumber } from 'format-number';
56
+ import { FN } from '@peter-present/format-number';
63
57
 
64
- // Currency formatting
65
- formatNumber(1234.56, { prefix: '$', precision: 1 }); // '$1234.6'
58
+ const formatted = FN('1234567.89')
59
+ .round({ precision: 0, rounding: 'banker' })
60
+ .prefix('Balance: ')
61
+ .suffix(' USD')
62
+ .toNumber();
66
63
 
67
- // Compact with metadata
68
- formatNumber(1500000, { isCompact: true, suffix: ' units' }); // '1.5M units'
64
+ console.log(formatted); // 'Balance: 1234568 USD'
69
65
 
70
- // Small number subscript notation
71
- formatNumber(0.00005, { isSmall: true }); // '0.0₄5'
66
+ // Seamlessly combine with compact and notation
67
+ FN(1500).compact({ precision: 1 }).prefix('$').toNumber(); // '$1.5K'
72
68
  ```
73
69
 
74
- ### Fluent / Chained API: `FN()`
70
+ ### `round(value, options)`
75
71
 
76
- Perform multiple operations in a readable chain.
72
+ Independent rounding utility with high-precision string logic.
77
73
 
78
74
  ```typescript
79
- import { FN } from 'format-number';
80
-
81
- const result = FN('1234567.89')
82
- .round({ precision: 0 })
83
- .format({ prefix: 'Total: ', suffix: ' tokens' });
84
-
85
- console.log(result); // 'Total: 1234568 tokens'
75
+ import { round } from '@peter-present/format-number';
86
76
 
87
- // Compact chaining
88
- FN(1000000).compact({ precision: 0 }); // '1M'
77
+ round(1.235, { precision: 2, rounding: 'half' }); // '1.24'
78
+ round(1.235, { precision: 2, rounding: 'down' }); // '1.23'
89
79
  ```
90
80
 
91
- ## API Reference
81
+ ## Configuration Reference
92
82
 
93
- ### Rounding Modes (`RoundingMode`)
83
+ ### `RoundingMode`
94
84
 
95
- | Mode | Description |
96
- | :--------- | :------------------------------------------------------------------- |
97
- | `half` | Round to the nearest neighbor. If equidistant, round away from zero. |
98
- | `up` | Round towards Positive Infinity. |
99
- | `down` | Round towards Negative Infinity. |
100
- | `truncate` | Round towards Zero. |
101
- | `banker` | Round to the nearest even neighbor (Statistical rounding). |
85
+ | Mode | Description |
86
+ | :--------- | :--------------------------------------------------------------------- |
87
+ | `half` | Rounds to the nearest neighbor (rounds away from zero if equidistant). |
88
+ | `up` | Rounds towards Positive Infinity. |
89
+ | `down` | Rounds towards Negative Infinity. |
90
+ | `truncate` | Rounds towards Zero (trims decimals). |
91
+ | `banker` | Rounds to the nearest even neighbor (minimizes statistical bias). |
102
92
 
103
- ### Configuration Options
93
+ ### `FormattingConfigType`
104
94
 
105
- | Option | Type | Description |
106
- | :---------- | :------------- | :----------------------------------------------- |
107
- | `precision` | `number` | Number of decimal places (default: `0`). |
108
- | `mode` | `RoundingMode` | Rounding strategy (default: `'half'`). |
109
- | `prefix` | `string` | Text to prepend to the result. |
110
- | `suffix` | `string` | Text to append to the result. |
111
- | `isCompact` | `boolean` | If `true`, uses K/M/B/T suffixes. |
112
- | `isSmall` | `boolean` | If `true`, formats tiny numbers with subscripts. |
95
+ | Property | Type | Default | Description |
96
+ | :---------- | :---------------------------- | :---------- | :------------------------------------------------- |
97
+ | `precision` | `number` | `0` | Number of decimal places to maintain. |
98
+ | `rounding` | `RoundingMode` | `'half'` | Strategy used for rounding. |
99
+ | `prefix` | `string` | `""` | Text prepended to the result. |
100
+ | `suffix` | `string` | `""` | Text appended to the result. |
101
+ | `isCompact` | `boolean` | `false` | Whether to use K/M/B/T suffixes for large numbers. |
102
+ | `notation` | `'subscript' \| 'scientific'` | `undefined` | Special formatting for extreme values. |
113
103
 
114
104
  ## License
115
105
 
package/dist/compact.d.ts CHANGED
@@ -1,2 +1,20 @@
1
- import { RoundingConfigType, NumberType } from './types';
1
+ import { NumberType, RoundingConfigType } from './types';
2
+ export declare function _compact(value: NumberType, options?: RoundingConfigType): {
3
+ value: string;
4
+ symbol: string;
5
+ };
6
+ /**
7
+ * Formats a number into a short, human-readable string with a suffix (e.g., K, M, B).
8
+ *
9
+ * @param value - The value to be compacted. Can be a number, string, or bigint.
10
+ * @param options - Configuration for rounding the compacted value.
11
+ * @param options.rounding - The rounding strategy to use. Default is 'half'.
12
+ * @param options.precision - The number of decimal places to include after compacting. Default is 0.
13
+ * @returns The compacted number with a suffix as a string.
14
+ *
15
+ * @example
16
+ * compact(1500); // '1.5K'
17
+ * compact(1200000, { precision: 1 }); // '1.2M'
18
+ * compact('1000000000000'); // '1T'
19
+ */
2
20
  export declare function compact(value: NumberType, options?: RoundingConfigType): string;
package/dist/format.d.ts CHANGED
@@ -1,8 +1,37 @@
1
- import { MetadataType, NumberType, OtherMetadataType, RoundingConfigType } from './types';
2
- export declare function formatNumber(value: NumberType, options?: MetadataType): string;
3
- export declare function FN(value: NumberType): {
4
- round(options?: RoundingConfigType): any;
5
- compact(options?: RoundingConfigType): string;
6
- format(options?: OtherMetadataType): string;
7
- toString(): string;
1
+ import { FormattingConfigType, NotationMode, NumberType, ObjectNumberType, RoundingConfigType } from './types';
2
+ /**
3
+ * Fluent API for performing multiple formatting operations in a readable chain.
4
+ * Wraps the input value into a formatter object that supports rounding,
5
+ * compacting, and metadata addition.
6
+ *
7
+ * @param value - The initial value to format.
8
+ * @returns A chainable formatter object.
9
+ *
10
+ * @example
11
+ * FN('1234567.89')
12
+ * .round({ precision: 0 })
13
+ * .prefix('$')
14
+ * .toNumber(); // '$1234568'
15
+ */
16
+ export declare function FN(value: NumberType | ObjectNumberType): {
17
+ round: (options?: RoundingConfigType) => any;
18
+ compact: (options?: RoundingConfigType) => any;
19
+ notation(mode?: NotationMode): any;
20
+ prefix(symbol: string): any;
21
+ suffix(symbol: string): any;
22
+ toNumber(): string;
23
+ toObject(): ObjectNumberType;
8
24
  };
25
+ /**
26
+ * Comprehensive formatting function that combines rounding, compacting, and metadata.
27
+ * This is the quickest way to format a number with multiple options.
28
+ *
29
+ * @param value - The value to format.
30
+ * @param options - Formatting configuration (precision, rounding, isCompact, prefix, suffix, notation).
31
+ * @returns The formatted number string.
32
+ *
33
+ * @example
34
+ * formatNumber(1234.56, { prefix: '$', precision: 1 }); // '$1234.6'
35
+ * formatNumber(1500000, { isCompact: true }); // '1.5M'
36
+ */
37
+ export declare function formatNumber(value: NumberType, options?: FormattingConfigType): string;
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function f(s){const r=s.startsWith("-")?"-":"",t=r?s.slice(1):s;let e=0,n=t.length-1;for(;t[e]=="0"&&e<t.length;)e++;if(s.indexOf(".")>=0)for(;t[n]=="0"&&n>=0;)n--;const u=t.slice(e,n+1);return u==""||u=="."?"0":r+u}function g(s){let r=f(s);r=(r.startsWith("-")?"-":"")?r.slice(1):r;const[e,n=""]=r.split("."),i=n.match(/^0+/)?.[0]?.length||0,u=i.toString().split("").map(o=>String.fromCharCode(8320+parseInt(o))).join(""),c=n.slice(i);return`${e}.0${u}${c}`}function l(s){let r="",t=s.length-1,e="1";for(;t>=0;){const n=s[t];let i=Number(n)+(e?1:0);i>=10?(e="1",i-=10):e="",r=i.toString()+r,t--}return{result:e+r,remaining:e}}function S(s,r=0){const[t,e=""]=s.split(".");if(r===0)return e&&Number(e[0])>=5?l(t).result:t;if(r>=e.length)return t+(e?`.${e}`:"");const n=e.slice(0,r),i=e.slice(r);if(Number(i[0])>=5){const u=l(n);return u.remaining?l(t).result:t+"."+u.result}else return t+"."+n}function d(s,r=0){const[t,e=""]=s.split(".");if(r===0)return e?l(t).result:t;if(r>=e.length)return t+"."+e;const n=l(e.slice(0,r));return n.remaining?l(t).result:t+"."+n.result}function m(s,r=0){const[t,e=""]=s.split(".");if(r===0)return t;let n=e.slice(0,r),i=n.length-1;for(;i>=0&&n[i]=="0";)i--;return n=n.slice(0,i+1),t+(n?`.${n}`:"")}function $(s,r=0){const[t,e=""]=s.split(".");if(r===0)return e&&Number(e[0])>=5?Number(t[0])/2==0?t:l(t).result:t;if(r>=e.length)return t+(e?`.${e}`:"");const n=e.slice(0,r),i=e.slice(r);if(i[0]=="5"){if(Number(n[n.length-1])%2==0)return t+"."+n;{const u=l(n);return u.remaining?l(t).result:t+"."+u.result}}else if(Number(i[0])>5){const u=l(n);return u.remaining?l(t).result:t+"."+u.result}else return t+"."+n}function a(s,r={}){const t=r.mode||"half",e=r.precision||0;let n=f(String(s)),i="";switch(n.startsWith("-")&&(n=n.slice(1),i="-"),t){case"half":return i+S(n,e);case"up":return i==""?d(n,e):"-"+m(n,e);case"down":return i==""?m(n,e):"-"+d(n,e);case"truncate":return i+m(n,e);case"banker":return i+$(n,e)}}const b=["K","M","B","T","Q"];function P(s,r={}){let t=f(String(s)),e="";t.startsWith("-")&&(t=t.slice(1),e="-");let[n,i=""]=t.split("."),u=n.length,c=-1;for(;u>3&&c<4;){const o=n.slice(0,u-3);i=n.slice(u-3)+i,n=o,u-=3,c++}return a(`${e}${n}.${i}`,r)+(c>=0?b[c]:"")}function N(s,r){let t=String(s);r.isCompact==!0?t=P(t,r):r.isSmall||(t=a(t,r));const e=r.prefix||"",n=r.suffix||"",i=r.isSmall||!1;return`${e}${i?g(t):t}${n}`}function h(s){return{round(r={}){return h(a(s,r))},compact(r={}){return P(s,r)},format(r={}){const t=r.prefix||"",e=r.suffix||"",n=r.isSmall||!1;return`${t}${n?g(String(s)):s}${e}`},toString(){return String(s)}}}exports.FN=h;exports.compact=P;exports.formatNumber=N;exports.formatSmallNum=g;exports.removeZero=f;exports.round=a;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function $(n){return n.replace(/^(-?)0+(?=\d)/,"$1").replace(/(\.\d*?)0+$/,"$1").replace(/\.$/,"")}function d(n){let t=String(n);t=t.trim();let e="";return t.startsWith("-")&&(t=t.slice(1),e="-"),{sign:e,value:$(t)}}const Z={K:3,M:6,B:9,T:12,Q:15};function E(n){if(typeof n=="number"||typeof n=="bigint")return n.toString();const t=String(n??"").trim();if(t==="")return"0";const e=t.match(/^[\s+-]*/),i=((e?e[0]:"").match(/-/g)||[]).length%2===1,s=t.replace(/[$€£¥,_\s]/g,""),c=/^([+-]?\d+)\.0([\u2080-\u2089]+)([0-9]*)$/i,u=s.match(c);if(u){const m=u[1].replace(/^[+-]/,""),j=u[2],D=u[3],R=j.split("").reduce((K,O)=>{const Q=O.charCodeAt(0)-8320;return K+Q},0),w="0".repeat(R)+D;let h=m;return(w!==""||R>0)&&(h+="."+w),h=$(h),(i?"-":"")+(h||"0")}const l=s.match(/[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?[KMBTQ]*/i);if(!l)return"0";let o=l[0].toUpperCase(),P=0;const S=o.match(/[KMBTQ]$/);S&&(P+=Z[S[0]]??0,o=o.slice(0,-1));const y=o.match(/[eE][+-]?\d+$/);if(y&&(P+=parseInt(y[0].slice(1),10),o=o.replace(/[eE][+-]?\d+$/i,"")),o=o.replace(/^[+-]/,""),!/^(?:\d+\.?\d*|\.\d+)$/.test(o))return"0";const[C="0",M=""]=o.split(".");let g=(C.replace(/^0+/,"")||"0")+M;const b=M.length-P;let p;if(b<=0)g+="0".repeat(-b),p=g.replace(/^0+(?=\d)/,"")||"0";else{const m=g.length-b;m<=0?p="0."+"0".repeat(-m)+g:p=g.slice(0,m)+"."+g.slice(m),p=$(p)}return(i?"-":"")+(p||"0")}function F(n){const t=typeof n;if(t=="string"||t=="bigint"||t=="number"){const{sign:e,value:r}=d(n);return{sign:e,value:r}}return n}function a(n){let t="",e=n.length-1,r="1";for(;e>=0;){const i=n[e];let s=Number(i)+(r?1:0);s>=10?(r="1",s-=10):r="",t=s.toString()+t,e--}return{result:r+t,remaining:r}}function I(n,t=0){const[e,r=""]=n.split(".");if(t===0)return r&&Number(r[0])>=5?a(e).result:e;if(t>=r.length)return e+(r?`.${r}`:"");const i=r.slice(0,t),s=r.slice(t);if(Number(s[0])>=5){const c=a(i);return c.remaining?a(e).result:e+"."+c.result}else return e+"."+i}function v(n,t=0){const[e,r=""]=n.split(".");if(t===0)return r?a(e).result:e;if(t>=r.length)return e+"."+r;const i=a(r.slice(0,t));return i.remaining?a(e).result:e+"."+i.result}function x(n,t=0){const[e,r=""]=n.split(".");if(t===0)return e;let i=r.slice(0,t),s=i.length-1;for(;s>=0&&i[s]=="0";)s--;return i=i.slice(0,s+1),e+(i?`.${i}`:"")}function k(n,t=0){const[e,r=""]=n.split(".");if(t===0)return r&&Number(r[0])>=5?Number(e[0])/2==0?e:a(e).result:e;if(t>=r.length)return e+(r?`.${r}`:"");const i=r.slice(0,t),s=r.slice(t);if(s[0]=="5"){if(Number(i[i.length-1])%2==0)return e+"."+i;{const c=a(i);return c.remaining?a(e).result:e+"."+c.result}}else if(Number(s[0])>5){const c=a(i);return c.remaining?a(e).result:e+"."+c.result}else return e+"."+i}function N(n,t={}){const e=t.rounding||"half",r=t.precision||0,{sign:i,value:s}=d(n);switch(e){case"half":return i+I(s,r);case"up":return i==""?v(s,r):"-"+x(s,r);case"down":return i==""?x(s,r):"-"+v(s,r);case"truncate":return i+x(s,r);case"banker":return i+k(s,r)}}const U=["K","M","B","T","Q"];function _(n,t={}){const{sign:e,value:r}=d(n);let[i,s=""]=r.split("."),c=i.length,u=-1;for(;c>3&&u<4;){const o=i.slice(0,c-3);s=i.slice(c-3)+s,i=o,c-=3,u++}const l=`${e}${i}${s.length>0?"."+s:s}`;return{value:t.precision!=null?N(l,t):l,symbol:u>=0?U[u]:""}}function V(n,t={}){const{value:e,symbol:r}=_(n,t);return e+r}function T(n){const{sign:t,value:e}=d(n),[r,i=""]=e.split("."),s=i.match(/^0+/)?.[0]?.length||0,c=s.toString().split("").map(l=>String.fromCharCode(8320+parseInt(l))).join(""),u=i.slice(s);return`${t}${r}.0${c}${u}`}function z(n){const{sign:t,value:e}=d(n);if(e=="0")return{value:"0",exponent:0};const[r,i=""]=e.split(".");if(r!=="0"){const l=r.length-1,o=r[0]+"."+(r.slice(1)+i).replace(/0+$/,"");return{value:`${t}${o}${o.length>1,""}`,exponent:l,sign:"+"}}const s=i.search(/[1-9]/);if(s===-1)return{value:"0",exponent:0};const c=-(s+1),u=i[s]+"."+i.slice(s+1).replace(/0+$/,"");return{value:`${t}${u}`,exponent:c-1,sign:""}}function B(n){const{value:t,exponent:e,sign:r}=z(n);return e!=0?`${t}e${r}${e}`:t}function f(n){return{round:t=>{const e=N(n.value,t);return f({...n,value:e})},compact:t=>{if(n.compactedSymbol)return f(n);const{value:e,symbol:r}=_(n.value,t);return f({...n,compactedSymbol:r,value:e})},notation(t="scientific"){return f({...n,notation:t})},prefix(t){return f({...n,prefix:t})},suffix(t){return f({...n,suffix:t})},toNumber(){let{value:t}=n;const{sign:e,prefix:r,suffix:i,compactedSymbol:s}=n;return n.notation=="scientific"?t=B(t):n.notation=="subscript"&&(t=T(t)),`${r||""}${e}${t}${s||""}${i||""}`},toObject(){return n}}}function A(n){return f(F(n))}exports.FN=A;exports.clearTrailingZero=$;exports.compact=V;exports.parseNum=E;exports.round=N;exports.scientific=B;exports.subscript=T;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './types';
2
- export * from './utils';
3
2
  export { compact } from './compact';
4
- export { FN, formatNumber } from './format';
3
+ export { FN } from './format';
4
+ export { clearTrailingZero, parseNum } from './io';
5
+ export { scientific, subscript } from './notation';
5
6
  export { round } from './round';
package/dist/index.js CHANGED
@@ -1,125 +1,200 @@
1
- function o(s) {
2
- const r = s.startsWith("-") ? "-" : "", t = r ? s.slice(1) : s;
3
- let e = 0, n = t.length - 1;
4
- for (; t[e] == "0" && e < t.length; ) e++;
5
- if (s.indexOf(".") >= 0) for (; t[n] == "0" && n >= 0; ) n--;
6
- const l = t.slice(e, n + 1);
7
- return l == "" || l == "." ? "0" : r + l;
8
- }
9
- function P(s) {
10
- let r = o(s);
11
- r = (r.startsWith("-") ? "-" : "") ? r.slice(1) : r;
12
- const [e, n = ""] = r.split("."), i = n.match(/^0+/)?.[0]?.length || 0, l = i.toString().split("").map((f) => String.fromCharCode(8320 + parseInt(f))).join(""), c = n.slice(i);
13
- return `${e}.0${l}${c}`;
14
- }
15
- function u(s) {
16
- let r = "", t = s.length - 1, e = "1";
17
- for (; t >= 0; ) {
18
- const n = s[t];
19
- let i = Number(n) + (e ? 1 : 0);
20
- i >= 10 ? (e = "1", i -= 10) : e = "", r = i.toString() + r, t--;
1
+ function x(r) {
2
+ return r.replace(/^(-?)0+(?=\d)/, "$1").replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
3
+ }
4
+ function d(r) {
5
+ let t = String(r);
6
+ t = t.trim();
7
+ let e = "";
8
+ return t.startsWith("-") && (t = t.slice(1), e = "-"), { sign: e, value: x(t) };
9
+ }
10
+ const j = { K: 3, M: 6, B: 9, T: 12, Q: 15 };
11
+ function z(r) {
12
+ if (typeof r == "number" || typeof r == "bigint")
13
+ return r.toString();
14
+ const t = String(r ?? "").trim();
15
+ if (t === "") return "0";
16
+ const e = t.match(/^[\s+-]*/), i = ((e ? e[0] : "").match(/-/g) || []).length % 2 === 1, s = t.replace(/[$€£¥,_\s]/g, ""), c = /^([+-]?\d+)\.0([\u2080-\u2089]+)([0-9]*)$/i, u = s.match(c);
17
+ if (u) {
18
+ const m = u[1].replace(/^[+-]/, ""), B = u[2], C = u[3], w = B.split("").reduce((D, K) => {
19
+ const Q = K.charCodeAt(0) - 8320;
20
+ return D + Q;
21
+ }, 0), y = "0".repeat(w) + C;
22
+ let h = m;
23
+ return (y !== "" || w > 0) && (h += "." + y), h = x(h), (i ? "-" : "") + (h || "0");
24
+ }
25
+ const l = s.match(/[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?[KMBTQ]*/i);
26
+ if (!l) return "0";
27
+ let o = l[0].toUpperCase(), $ = 0;
28
+ const N = o.match(/[KMBTQ]$/);
29
+ N && ($ += j[N[0]] ?? 0, o = o.slice(0, -1));
30
+ const S = o.match(/[eE][+-]?\d+$/);
31
+ if (S && ($ += parseInt(S[0].slice(1), 10), o = o.replace(/[eE][+-]?\d+$/i, "")), o = o.replace(/^[+-]/, ""), !/^(?:\d+\.?\d*|\.\d+)$/.test(o)) return "0";
32
+ const [T = "0", R = ""] = o.split(".");
33
+ let g = (T.replace(/^0+/, "") || "0") + R;
34
+ const P = R.length - $;
35
+ let p;
36
+ if (P <= 0)
37
+ g += "0".repeat(-P), p = g.replace(/^0+(?=\d)/, "") || "0";
38
+ else {
39
+ const m = g.length - P;
40
+ m <= 0 ? p = "0." + "0".repeat(-m) + g : p = g.slice(0, m) + "." + g.slice(m), p = x(p);
21
41
  }
22
- return { result: e + r, remaining: e };
23
- }
24
- function d(s, r = 0) {
25
- const [t, e = ""] = s.split(".");
26
- if (r === 0)
27
- return e && Number(e[0]) >= 5 ? u(t).result : t;
28
- if (r >= e.length) return t + (e ? `.${e}` : "");
29
- const n = e.slice(0, r), i = e.slice(r);
30
- if (Number(i[0]) >= 5) {
31
- const l = u(n);
32
- return l.remaining ? u(t).result : t + "." + l.result;
33
- } else return t + "." + n;
34
- }
35
- function g(s, r = 0) {
36
- const [t, e = ""] = s.split(".");
37
- if (r === 0)
38
- return e ? u(t).result : t;
39
- if (r >= e.length) return t + "." + e;
40
- const n = u(e.slice(0, r));
41
- return n.remaining ? u(t).result : t + "." + n.result;
42
- }
43
- function a(s, r = 0) {
44
- const [t, e = ""] = s.split(".");
45
- if (r === 0) return t;
46
- let n = e.slice(0, r), i = n.length - 1;
47
- for (; i >= 0 && n[i] == "0"; ) i--;
48
- return n = n.slice(0, i + 1), t + (n ? `.${n}` : "");
49
- }
50
- function S(s, r = 0) {
51
- const [t, e = ""] = s.split(".");
52
- if (r === 0)
53
- return e && Number(e[0]) >= 5 ? Number(t[0]) / 2 == 0 ? t : u(t).result : t;
54
- if (r >= e.length) return t + (e ? `.${e}` : "");
55
- const n = e.slice(0, r), i = e.slice(r);
56
- if (i[0] == "5") {
57
- if (Number(n[n.length - 1]) % 2 == 0)
58
- return t + "." + n;
42
+ return (i ? "-" : "") + (p || "0");
43
+ }
44
+ function E(r) {
45
+ const t = typeof r;
46
+ if (t == "string" || t == "bigint" || t == "number") {
47
+ const { sign: e, value: n } = d(r);
48
+ return { sign: e, value: n };
49
+ }
50
+ return r;
51
+ }
52
+ function a(r) {
53
+ let t = "", e = r.length - 1, n = "1";
54
+ for (; e >= 0; ) {
55
+ const i = r[e];
56
+ let s = Number(i) + (n ? 1 : 0);
57
+ s >= 10 ? (n = "1", s -= 10) : n = "", t = s.toString() + t, e--;
58
+ }
59
+ return { result: n + t, remaining: n };
60
+ }
61
+ function I(r, t = 0) {
62
+ const [e, n = ""] = r.split(".");
63
+ if (t === 0)
64
+ return n && Number(n[0]) >= 5 ? a(e).result : e;
65
+ if (t >= n.length) return e + (n ? `.${n}` : "");
66
+ const i = n.slice(0, t), s = n.slice(t);
67
+ if (Number(s[0]) >= 5) {
68
+ const c = a(i);
69
+ return c.remaining ? a(e).result : e + "." + c.result;
70
+ } else return e + "." + i;
71
+ }
72
+ function M(r, t = 0) {
73
+ const [e, n = ""] = r.split(".");
74
+ if (t === 0)
75
+ return n ? a(e).result : e;
76
+ if (t >= n.length) return e + "." + n;
77
+ const i = a(n.slice(0, t));
78
+ return i.remaining ? a(e).result : e + "." + i.result;
79
+ }
80
+ function b(r, t = 0) {
81
+ const [e, n = ""] = r.split(".");
82
+ if (t === 0) return e;
83
+ let i = n.slice(0, t), s = i.length - 1;
84
+ for (; s >= 0 && i[s] == "0"; ) s--;
85
+ return i = i.slice(0, s + 1), e + (i ? `.${i}` : "");
86
+ }
87
+ function O(r, t = 0) {
88
+ const [e, n = ""] = r.split(".");
89
+ if (t === 0)
90
+ return n && Number(n[0]) >= 5 ? Number(e[0]) / 2 == 0 ? e : a(e).result : e;
91
+ if (t >= n.length) return e + (n ? `.${n}` : "");
92
+ const i = n.slice(0, t), s = n.slice(t);
93
+ if (s[0] == "5") {
94
+ if (Number(i[i.length - 1]) % 2 == 0)
95
+ return e + "." + i;
59
96
  {
60
- const l = u(n);
61
- return l.remaining ? u(t).result : t + "." + l.result;
97
+ const c = a(i);
98
+ return c.remaining ? a(e).result : e + "." + c.result;
62
99
  }
63
- } else if (Number(i[0]) > 5) {
64
- const l = u(n);
65
- return l.remaining ? u(t).result : t + "." + l.result;
66
- } else return t + "." + n;
67
- }
68
- function m(s, r = {}) {
69
- const t = r.mode || "half", e = r.precision || 0;
70
- let n = o(String(s)), i = "";
71
- switch (n.startsWith("-") && (n = n.slice(1), i = "-"), t) {
100
+ } else if (Number(s[0]) > 5) {
101
+ const c = a(i);
102
+ return c.remaining ? a(e).result : e + "." + c.result;
103
+ } else return e + "." + i;
104
+ }
105
+ function _(r, t = {}) {
106
+ const e = t.rounding || "half", n = t.precision || 0, { sign: i, value: s } = d(r);
107
+ switch (e) {
72
108
  case "half":
73
- return i + d(n, e);
109
+ return i + I(s, n);
74
110
  case "up":
75
- return i == "" ? g(n, e) : "-" + a(n, e);
111
+ return i == "" ? M(s, n) : "-" + b(s, n);
76
112
  case "down":
77
- return i == "" ? a(n, e) : "-" + g(n, e);
113
+ return i == "" ? b(s, n) : "-" + M(s, n);
78
114
  case "truncate":
79
- return i + a(n, e);
115
+ return i + b(s, n);
80
116
  case "banker":
81
- return i + S(n, e);
117
+ return i + O(s, n);
82
118
  }
83
119
  }
84
- const $ = ["K", "M", "B", "T", "Q"];
85
- function h(s, r = {}) {
86
- let t = o(String(s)), e = "";
87
- t.startsWith("-") && (t = t.slice(1), e = "-");
88
- let [n, i = ""] = t.split("."), l = n.length, c = -1;
89
- for (; l > 3 && c < 4; ) {
90
- const f = n.slice(0, l - 3);
91
- i = n.slice(l - 3) + i, n = f, l -= 3, c++;
120
+ const Z = ["K", "M", "B", "T", "Q"];
121
+ function v(r, t = {}) {
122
+ const { sign: e, value: n } = d(r);
123
+ let [i, s = ""] = n.split("."), c = i.length, u = -1;
124
+ for (; c > 3 && u < 4; ) {
125
+ const o = i.slice(0, c - 3);
126
+ s = i.slice(c - 3) + s, i = o, c -= 3, u++;
92
127
  }
93
- return m(`${e}${n}.${i}`, r) + (c >= 0 ? $[c] : "");
128
+ const l = `${e}${i}${s.length > 0 ? "." + s : s}`;
129
+ return {
130
+ value: t.precision != null ? _(l, t) : l,
131
+ symbol: u >= 0 ? Z[u] : ""
132
+ };
94
133
  }
95
- function b(s, r) {
96
- let t = String(s);
97
- r.isCompact == !0 ? t = h(t, r) : r.isSmall || (t = m(t, r));
98
- const e = r.prefix || "", n = r.suffix || "", i = r.isSmall || !1;
99
- return `${e}${i ? P(t) : t}${n}`;
134
+ function A(r, t = {}) {
135
+ const { value: e, symbol: n } = v(r, t);
136
+ return e + n;
137
+ }
138
+ function k(r) {
139
+ const { sign: t, value: e } = d(r), [n, i = ""] = e.split("."), s = i.match(/^0+/)?.[0]?.length || 0, c = s.toString().split("").map((l) => String.fromCharCode(8320 + parseInt(l))).join(""), u = i.slice(s);
140
+ return `${t}${n}.0${c}${u}`;
141
+ }
142
+ function F(r) {
143
+ const { sign: t, value: e } = d(r);
144
+ if (e == "0") return { value: "0", exponent: 0 };
145
+ const [n, i = ""] = e.split(".");
146
+ if (n !== "0") {
147
+ const l = n.length - 1, o = n[0] + "." + (n.slice(1) + i).replace(/0+$/, "");
148
+ return { value: `${t}${o}${o.length > 1, ""}`, exponent: l, sign: "+" };
149
+ }
150
+ const s = i.search(/[1-9]/);
151
+ if (s === -1) return { value: "0", exponent: 0 };
152
+ const c = -(s + 1), u = i[s] + "." + i.slice(s + 1).replace(/0+$/, "");
153
+ return { value: `${t}${u}`, exponent: c - 1, sign: "" };
100
154
  }
101
- function x(s) {
155
+ function U(r) {
156
+ const { value: t, exponent: e, sign: n } = F(r);
157
+ return e != 0 ? `${t}e${n}${e}` : t;
158
+ }
159
+ function f(r) {
102
160
  return {
103
- round(r = {}) {
104
- return x(m(s, r));
161
+ round: (t) => {
162
+ const e = _(r.value, t);
163
+ return f({ ...r, value: e });
164
+ },
165
+ compact: (t) => {
166
+ if (r.compactedSymbol) return f(r);
167
+ const { value: e, symbol: n } = v(r.value, t);
168
+ return f({ ...r, compactedSymbol: n, value: e });
169
+ },
170
+ notation(t = "scientific") {
171
+ return f({ ...r, notation: t });
105
172
  },
106
- compact(r = {}) {
107
- return h(s, r);
173
+ prefix(t) {
174
+ return f({ ...r, prefix: t });
108
175
  },
109
- format(r = {}) {
110
- const t = r.prefix || "", e = r.suffix || "", n = r.isSmall || !1;
111
- return `${t}${n ? P(String(s)) : s}${e}`;
176
+ suffix(t) {
177
+ return f({ ...r, suffix: t });
112
178
  },
113
- toString() {
114
- return String(s);
179
+ toNumber() {
180
+ let { value: t } = r;
181
+ const { sign: e, prefix: n, suffix: i, compactedSymbol: s } = r;
182
+ return r.notation == "scientific" ? t = U(t) : r.notation == "subscript" && (t = k(t)), `${n || ""}${e}${t}${s || ""}${i || ""}`;
183
+ },
184
+ toObject() {
185
+ return r;
115
186
  }
116
187
  };
117
188
  }
189
+ function H(r) {
190
+ return f(E(r));
191
+ }
118
192
  export {
119
- x as FN,
120
- h as compact,
121
- b as formatNumber,
122
- P as formatSmallNum,
123
- o as removeZero,
124
- m as round
193
+ H as FN,
194
+ x as clearTrailingZero,
195
+ A as compact,
196
+ z as parseNum,
197
+ _ as round,
198
+ U as scientific,
199
+ k as subscript
125
200
  };
package/dist/io.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { NumberType, ObjectNumberType, SignType } from './types';
2
+ /**
3
+ * Removes redundant leading and trailing zeros from a number string.
4
+ *
5
+ * @param value - The number string to clean.
6
+ * @returns The cleaned number string.
7
+ */
8
+ export declare function clearTrailingZero(value: string): string;
9
+ export declare function getInput(value: NumberType): {
10
+ sign: SignType;
11
+ value: string;
12
+ };
13
+ /**
14
+ * Parses various input formats into a standard decimal string.
15
+ * Supports numbers, bigints, currencies ($€£¥), commas, underscores,
16
+ * scientific notation (e+), and custom subscript notation (e.g., "5.0₄6").
17
+ *
18
+ * @param value - The input value to parse.
19
+ * @returns A standardized decimal string.
20
+ *
21
+ * @example
22
+ * parseNum('$1,234.56'); // '1234.56'
23
+ * parseNum('5.0₄6'); // '5.00006'
24
+ */
25
+ export declare function parseNum(value: NumberType): string;
26
+ export declare function convertToObjectNumber(value: NumberType | ObjectNumberType): ObjectNumberType;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Formats the decimal part of small numbers using subscript characters
3
+ * to represent the number of consecutive leading zeros.
4
+ *
5
+ * @param value - The number string to format.
6
+ * @returns A string with subscript notation (e.g., '0.0₃5').
7
+ */
8
+ export declare function subscript(value: string): string;
9
+ export declare function _scientific(value: string): {
10
+ value: string;
11
+ exponent: number;
12
+ sign?: undefined;
13
+ } | {
14
+ value: string;
15
+ exponent: number;
16
+ sign: string;
17
+ };
18
+ /**
19
+ * Formats a number string using standard scientific notation.
20
+ *
21
+ * @param value - The number string to format.
22
+ * @returns A string in scientific notation (e.g., '1.23e+5').
23
+ */
24
+ export declare function scientific(value: string): string;
package/dist/round.d.ts CHANGED
@@ -1,3 +1,16 @@
1
1
  import { NumberType, RoundingConfigType } from './types';
2
2
  export declare function roundPositiveHalf(value: string, precision?: number): string;
3
+ /**
4
+ * Rounds a number based on the specified precision and rounding mode.
5
+ *
6
+ * @param value - The value to be rounded. Can be a number, string, or bigint.
7
+ * @param options - Configuration for rounding.
8
+ * @param options.rounding - The rounding strategy to use ('half', 'up', 'down', 'banker', 'truncate'). Default is 'half'.
9
+ * @param options.precision - The number of decimal places to round to. Default is 0.
10
+ * @returns The rounded number as a string.
11
+ *
12
+ * @example
13
+ * round(123.456, { precision: 2 }); // '123.46'
14
+ * round(123.45, { rounding: 'down', precision: 1 }); // '123.4'
15
+ */
3
16
  export declare function round(value: NumberType, options?: RoundingConfigType): string;
package/dist/types.d.ts CHANGED
@@ -1,14 +1,37 @@
1
+ /** Supported input types for number formatting */
1
2
  export type NumberType = number | string | bigint;
3
+ /** Strategies for rounding numbers */
2
4
  export type RoundingMode = 'half' | 'up' | 'down' | 'banker' | 'truncate';
5
+ /** Modes for special numerical notation */
6
+ export type NotationMode = 'subscript' | 'scientific';
7
+ /** Possible sign values for internal number representation */
8
+ export type SignType = '-' | '';
9
+ /** Configuration for rounding operations */
3
10
  export type RoundingConfigType = Partial<{
4
- mode: RoundingMode;
11
+ /** The rounding strategy to use */
12
+ rounding: RoundingMode;
13
+ /** Number of decimal places to keep */
5
14
  precision: number;
6
15
  }>;
7
- export type OtherMetadataType = Partial<{
16
+ /** Configuration for basic number display properties */
17
+ export type NumberConfigType = Partial<{
18
+ /** Text to prepend to the number (e.g., '$') */
8
19
  prefix: string;
20
+ /** Text to append to the number (e.g., ' units') */
9
21
  suffix: string;
10
- isSmall: boolean;
22
+ /** The notation style to apply */
23
+ notation: NotationMode;
11
24
  }>;
12
- export type MetadataType = RoundingConfigType & OtherMetadataType & Partial<{
25
+ /** Internal object structure containing full formatting state */
26
+ export type ObjectNumberType = {
27
+ sign: SignType;
28
+ value: string;
29
+ } & NumberConfigType & Partial<{
30
+ /** The symbol used for compact notation (e.g., 'K', 'M') */
31
+ compactedSymbol: string;
32
+ }>;
33
+ /** Full configuration options for the formatNumber function */
34
+ export type FormattingConfigType = RoundingConfigType & NumberConfigType & Partial<{
35
+ /** Whether to use compact notation (K, M, B, etc.) */
13
36
  isCompact: boolean;
14
37
  }>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peter-present/format-number",
3
- "version": "0.0.1",
4
- "description": "Utility library for number formatting, rounding, and compact display.",
3
+ "version": "0.0.11",
4
+ "description": "A lightweight, zero-dependency JavaScript/TypeScript library for precise number formatting. Designed for financial applications, crypto dashboards, and scientific data where precision is paramount.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
package/dist/utils.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export declare function removeZero(value: string): string;
2
- export declare function formatSmallNum(value: string): string;