{"id":18867521,"url":"https://github.com/frappe/pesa","last_synced_at":"2025-04-14T14:31:29.345Z","repository":{"id":45544876,"uuid":"427653427","full_name":"frappe/pesa","owner":"frappe","description":"A JS money lib whose precision goes up to 11 (and beyond). ","archived":false,"fork":false,"pushed_at":"2022-12-13T08:57:26.000Z","size":181,"stargazers_count":46,"open_issues_count":0,"forks_count":16,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-03-28T03:32:28.023Z","etag":null,"topics":["conversion","currency","javascript","money","nodejs"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/frappe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-11-13T12:02:52.000Z","updated_at":"2025-01-30T20:25:16.000Z","dependencies_parsed_at":"2023-01-28T10:01:07.965Z","dependency_job_id":null,"html_url":"https://github.com/frappe/pesa","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frappe%2Fpesa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frappe%2Fpesa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frappe%2Fpesa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frappe%2Fpesa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/frappe","download_url":"https://codeload.github.com/frappe/pesa/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248897154,"owners_count":21179547,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["conversion","currency","javascript","money","nodejs"],"created_at":"2024-11-08T05:09:40.626Z","updated_at":"2025-04-14T14:31:29.052Z","avatar_url":"https://github.com/frappe.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\" markdown=\"1\"\u003e\n\n\u003cimg src=\"https://user-images.githubusercontent.com/29507195/207266449-e035883d-fab5-49ef-a60e-d2b085f21835.png\" alt=\"pesa logo\" width=\"256\"/\u003e\n\n# पेसा\n\n\u003c/div\u003e\n\nA money handling library for JavaScript that can handle USD to VEF conversions for Jeff without breaking a sweat!\n\n---\n\n```javascript\nconst money = pesa(135, 'USD').add(25).to('INR', 75);\n\nmoney.round(2);\n// '12000.00'\n```\n\n\u003e Why should I use this, when I can just do all of this with plain old JavaScript numbers?!\n\nBecause JavaScript numbers full of fun foibles such as these:\n\n```javascript\n0.1 + 0.2;\n// 0.30000000000000004\n\n9007199254740992 + 1;\n// 9007199254740992\n```\n\nUsing them for financial transactions will most likely lead to technical bankruptcy [[1](#credits)].\n\n_(check this talk by [Bartek Szopka](https://www.youtube.com/watch?v=MqHDDtVYJRI) to understand why JS numbers are like this)_\n\n**`pesa`** circumvents this by conducting scaled integer operations using JS [`BigInt`](https://github.com/tc39/proposal-bigint) instead of `Number`. This allows for arithmetic involving arbitrarily large numbers with unnecessarily high precision.\n\n## Index\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eshow/hide\u003c/code\u003e\u003c/summary\u003e\n\n1. [Installation](#installation)\n2. [Usage](#usage)\n   1. [Initialization](#initialization)\n   2. [Currency and Conversions](#currency-and-conversions)\n   3. [Immutability](#immutability)\n   4. [Chaining](#chaining)\n3. [Documentation](#documentation)\n   1. [Arithmetic Functions](#arithmetic-functions)\n   2. [Comparison Functions](#comparison-functions)\n   3. [Check Functions](#check-functions)\n   4. [Other Calculation Functions](#other-calculations-functions)\n   5. [Display](#display)\n   6. [Chainable Methods](#chainable-methods)\n   7. [Non Chainable Methods](#non-chainable-methods)\n   8. [Internals](#internals)\n4. [Additional Notes](#additional-notes)\n   1. [Storing The Value](#storing-the-value)\n   2. [Non-Money Stuff](#non-money-stuff)\n   3. [Support](#Support)\n   4. [Precision vs Display](#precision-vs-display)\n   5. [Why High Precision](#why-high-precision)\n   6. [Implicit Conversions](#implicit-conversions)\n   7. [Rounding](#rounding)\n   8. [Use Cases](#use-cases)\n5. [Alternatives](#alternatives)\n\n\u003c/details\u003e\n\n## Documentation Index\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eshow/hide\u003c/code\u003e\u003c/summary\u003e\n\n1. [Arithmetic Functions](#arithmetic-functions)\n   - [Arguments](#arguments-arithmetic)\n   - [Return](#return-arithmetic)\n   - [Operations](#operations-arithmetic)\n2. [Comparison Functions](#comparison-functions)\n   - [Arguments](#arguments-comparison)\n   - [Return](#return-comparison)\n   - [Operations](#operations-comparison)\n3. [Check Functions](#check-functions)\n   - [Operations](#operations-check)\n4. [Other Calculation Functions](#other-calculations-functions)\n   - [`percent`](#percent)\n   - [`split`](#split)\n   - [`abs`](#abs)\n5. [Display](#display)\n   - [`float`](#float)\n   - [`round`](#round)\n   - [`store`](#store)\n6. [Chainable Methods](#chainable-methods)\n   - [`currency`](#currency)\n   - [`rate`](#rate)\n   - [`clip`](#clip)\n   - [`copy`](#copy)\n7. [Non Chainable Methods](#non-chainable-methods)\n   - [`getCurrency`](#getcurrency)\n   - [`getConversionRate`](#getconversionrate)\n   - [`hasConversionRate`](#hasconversionrate)\n8. [Internals](#internals) - [Numeric Representation](#numeric-representation) - [Conversion Rates](#conversion-rates)\n\u003c/details\u003e\n\n## Installation\n\nFor `npm` users:\n\n```bash\nnpm install pesa\n```\n\nFor `yarn` users:\n\n```bash\nyarn add pesa\n```\n\n[Index](#index)\n\n## Usage\n\n```javascript\npesa(200, 'USD').add(250, 'INR', 75).percent(50).round(3);\n// '9475.000'\n```\n\nThis section describes the usage in brief. For more details, check the [Documentation](#documentation) section. For even more details, check the source code or raise an issue.\n\n[Index](#index)\n\n### Initialization\n\nTo create an initialize a money object you can either use the constructor function `pesa`:\n\n```javascript\nimport { pesa } from 'pesa';\n\nconst money = pesa(200, 'USD');\n// OR\nconst money = pesa(200, options);\n```\n\nor the constructor function maker `getMoneyMaker`, this can be used if you don't want to set the options everytime you call `pesa`:\n\n```javascript\nimport { getMoneyMaker } from 'pesa';\n\nconst pesa = getMoneyMaker('USD');\n// OR\nconst pesa = getMoneyMaker(options);\n\nconst money = pesa(200);\n```\n\n#### Options and Value\n\n**Options** are optional, but currency has to be set before any conversions can take place.\n\n```typescript\ninterface Options {\n  bankersRounding?: boolean; // Default: true, use bankers rounding instead of traditional rounding.\n  currency?: string; // Default: '', Three letter alphabetical code in uppercase ('INR').\n  precision?: number; // Default: 6, Integer between 0 and 20.\n  display?: number; // Default: 3, Number of digits .round defaults to.\n  wrapper?: Wrapper; // Default: (m) =\u003e m, Used to augment all returned money objects.\n  rate?: RateSetting | RateSetting[]; // Default: [], Conversion rates\n}\n\ninterface RateSetting {\n  from: string;\n  to: string;\n  rate: string | number;\n}\n\ntype Wrapper = (m: Money) =\u003e Money;\n```\n\n**Value** can be a `string`, `number` or a `bigint`. If value is not passed the value is set as 0.\n\n```typescript\ntype Value = string | number | bigint;\n```\n\nIf `bigint` is passed then it doesn't undergo any conversion or scaling and is used to set the internal `bigint`.\n\n```javascript\npesa(235).internal;\n// { bigint: 235000000n, precision: 6 }\n\npesa('235').internal;\n// { bigint: 235000000n, precision: 6 }\n\npesa(235n).internal;\n// { bigint: 235n, precision: 6 }\n```\n\n**Wrapper** is a function that can add additional properties to the returned object.\nOne use case is Vue3 where everything is deeply converted into a `Proxy`, this is incompatible with `pesa` because of it's private variables and immutability.\n\nSo to remedy this you can pass [`markRaw`](https://vuejs.org/api/reactivity-advanced.html#markraw) as the wrapper function.\n\nThis will prevent the _proxification_ of `pesa` objects. Which in the case of `pesa` shouldn't be required anyway because the underlying value is never changed.\n\n### Currency and Conversions\n\nA numeric value isn't money unless a currency is assigned to it.\n\n#### Setting Currency\n\nCurrency can be assigned any time before a conversion is applied.\n\n```javascript\n// During initialization\nconst money = pesa(200, 'USD');\n\n// After initialization\nconst money = pesa(200).currency('USD');\n```\n\n#### Setting Rates\n\nTo allow for conversion between two currencies, a conversion rate has to be set. This can be set before the operation or during the operation.\n\n```javascript\n// Rate set before the operation\npesa(200).currency('USD').rate('INR', 75).add(2000, 'INR');\n\n// Rate set during the operation\npesa(200).currency('USD').add(2000, 'INR', 0.013);\n```\n\n#### Conversion\n\nThe result of an operation will always have the currency on the left (USD in the above example). To convert to a currency:\n\n```javascript\n// Rate set during the operation\nmoney.to('INR', 75);\n\n// Rate set before the operation\nmoney.to('INR');\n```\n\nThis returns a new `Money` object.\n\n\u003e Does it provide conversion rates?\n\n**`pesa`** doesn't provide or fetch conversion rates. This would cause dependencies on exchange rate APIs and async behaviour. There are a lot of exchange rate apis such as [Coinbase](https://api.coinbase.com/v2/exchange-rates), [VAT Comply](https://vatcomply.com/), [European Central Bank](https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml?b743fd808a34daf65f9ac9d63a9538f9), and [others](https://github.com/public-apis/public-apis#currency-exchange).\n\n### Immutability\n\nThe underlying value or currency of a `Money` object doesn't change after an operation.\n\n```javascript\nconst a = pesa(200, 'USD');\nconst b = pesa(125, 'INR').rate('USD', 0.013);\nconst c = a.add(b);\n\n// Statements below will evaluate to true\na.float === 200;\nb.float === 125;\nc.float === 201.625;\n\na.getCurrency() === 'USD';\nb.getCurrency() === 'INR';\nc.getCurrency() === 'USD';\n```\n\n### Chaining\n\nDue to the following two points:\n\n1. All arithmetic operation (`add`, `sub`, `mul` and `div`), create a new `Money` object having the values that is the result of that operation.\n\n2. All setter methods (`currency`, `rate`), set the value of an internal parameter and return the calling `Money` object.\n\nMethods can be chained and executed like so:\n\n```javascript\npesa(200)\n  .add(22)\n  .currency('USD')\n  .sub(33)\n  .rate('INR', 75)\n  .mul(2, 'INR')\n  .to('INR')\n  .round(2);\n// '377.99'\n```\n\n[Index](#index)\n\n## Documentation\n\nCalling the main function `pesa` returns an object of the `Money` class.\n\n```typescript\nconst money: Money = pesa(200, 'USD');\n```\n\nThe rest of the documentation pertains to the methods and parameters of this class.\n\n### Arithmetic Functions\n\nOperations that involve the value of two `Money` objects and return a new `Money` object as the result.\n\nFunction signature\n\n```typescript\n[operationName](value: Input, currency?: string, rate?: number) : Money;\n\ntype Input = Money | number | string;\n```\n\nExample:\n\n```javascript\nmoney = pesa(200, 'USD');\n\nmoney.add(150).round();\n// '350.000'\n\nmoney.sub(150, 'INR', 0.013);\n// '198.050'\n```\n\n_**Note**: The `rate` argument here is from the `currency` given in the function to the calling objects `currency`. So in the above example `rate` of 0.013 is for converting `'INR'` to `'USD'`. The reason for this is to prevent precision loss due to reciprocal._\n\n#### Arguments (arithmetic)\n\n| name       | description                                                                                                                                                                   | example        |\n| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |\n| `value`    | This is compulsory. If input is a string then `'_'` or `','` can be used as digit separators, but decimal points should always be `'.'`. Scientific notation isn't supported. | `'200_000.00'` |\n| `currency` | If value is a different currency from the calling object's currency then currency should be passed. Any arbitrary combination of 3 letters in uppercase can be a `currency`.  | `'VEF'`        |\n| `rate`     | Required only if the calling object or `value` doesn't have the rate set. `rate` is the conversion rate between the calling object's `currency` and `value`'s `currency`      | `450_000`      |\n\n#### Return (arithmetic)\n\nAll arightmetic operations return a new `Money` object that inherits the calling object's options, i.e. `currency`, `precision` and `display`.\n\n#### Operations (arithmetic)\n\n| name  | description                 | example            |\n| ----- | --------------------------- | ------------------ |\n| `add` | addition, i.e. a + b        | `pesa(33).add(36)` |\n| `sub` | subtraction, i.e. a - b     | `pesa(90).sub(21)` |\n| `mul` | multiplication, i.e. a \\* b | `pesa(23).mul(3)`  |\n| `div` | division, i.e. a / b        | `pesa(138).div(2)` |\n\n### Comparison Functions\n\nOperations that compare two values and return a boolean.\n\nFunction Signature:\n\n```typescript\n[operationName](value: Input, currency?: string, rate?: number) : boolean;\n\ntype Input = Money | number | string;\n```\n\nExample:\n\n```javascript\nmoney = pesa(150, 'INR');\n\nmoney.eq(2, 'USD', 75);\n// true\n\nmoney.lte(200);\n// true\n```\n\n_**Note**: The `rate` argument here is from the `currency` given in the function to the calling objects `currency`. So in the above example `rate` of 75 is for converting `'USD'` to `'INR'`. The reason for this is to prevent precision loss due to reciprocal._\n\n#### Arguments (comparison)\n\nSee the **Arguments** table under the **Arithmetic** section.\n\n#### Return\n\n`Boolean` indicating the result of the comparison.\n\n#### Operations (comparison)\n\n| name  | description                                                                              | example            |\n| ----- | ---------------------------------------------------------------------------------------- | ------------------ |\n| `eq`  | checks if the two amounts are the same, i.e. `===`                                       | `pesa(20).eq(20)`  |\n| `neq` | checks if the two amounts are not the same, i.e. `!==`                                   | `pesa(20).neq(19)` |\n| `gt`  | checks if calling object amount is greater than passed amount, i.e. `\u003e`                  | `pesa(20).gt(19)`  |\n| `lt`  | checks if calling object amount is less than passed amount, i.e. `\u003c`                     | `pesa(20).lt(21)`  |\n| `gte` | checks if calling object amount is greater than or equal to the passed amount, i.e. `\u003e=` | `pesa(20).gte(19)` |\n| `lte` | checks if calling object amount is less than or equal to passed amount, i.e. `\u003c`         | `pesa(20).lte(21)` |\n\n### Check Functions\n\nFunctions that return a `boolean` after evaluating the internal state.\n\nFunction signature:\n\n```typescript\n[checkName](): boolean;\n```\n\nExample:\n\n```typescript\npesa(200, 'USD').isPositive();\n// true\n\npesa(0, 'USD').isZero();\n// true\n\npesa(-200, 'USD').isNegative();\n// true\n```\n\n#### Operations (check)\n\n| name         | description                                            |\n| ------------ | ------------------------------------------------------ |\n| `isPositive` | Returns true if underlying value is greater than zero. |\n| `isZero`     | Returns true if underlying value is zero.              |\n| `isNegative` | Returns true if underlying value is less than zero.    |\n\n### Other Calculation Functions\n\n#### `percent`\n\nFunction that returns another `Money` object having a percent of the calling objects value.\n\nFunction signature\n\n```typescript\npercent(value: number): Money;\n```\n\nExample\n\n```javascript\npesa(200, 'USD').percent(50).round(2);\n// '100.00'\n```\n\n#### `split`\n\nFunction that splits the underlying value into given list of percentages or `n` equal parts and returns an array of `Money` objects.\n\nThe sum of `values` can exceed `100`. Argument `round` is used to decide at what precision the sum of all returned will equate to the calling objects value. If `round` is not passed then the calling object's `display` value is used.\n\nFunction signature:\n\n```typescript\nsplit(values: number | number[], round?: number): Money[];\n```\n\nExample:\n\n```javascript\npesa(200.99)\n  .split([33, 66, 1])\n  .map((m) =\u003e m.float);\n// [66.327, 132.653, 2.01]\n\npesa(200.99)\n  .split([33, 66, 1], 2)\n  .map((m) =\u003e m.float);\n// [66.33, 132.65, 2.01]\n\npesa(100)\n  .split(3)\n  .map((m) =\u003e m.float);\n// [33.333, 33.333, 33.334]\n```\n\n#### `abs`\n\nReturns a `Money` object having the the absolute value of the calling money object.\n\nFunction signature:\n\n```typescript\nabs(): Money;\n```\n\nExample:\n\n```javascript\npesa(-2).abs().eq(2);\n// true\n\npesa(2).abs().eq(2);\n// true\n```\n\n#### `neg`\n\nReturns a `Money` object having the the negated value of the calling money object.\n\nFunction signature:\n\n```typescript\nneg(): Money;\n```\n\nExample:\n\n```javascript\npesa(-2).neg().round();\n// '2.000'\n\npesa(2).neg().round();\n// '-2.000'\n```\n\n### Display\n\nFunctions and parameters used to display the `Money` object's value.\n\n\u003e Does this support formatting?\n\nNope, but you can use the [ECMAScript Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) to handle formatting for you. The `NumberFormat` constructor can be configured to your needs then you can pass `Money#float` to it's format method.\n\n```javascript\nconst numberFormat = new Intl.NumberFormat('en-US', {\n  style: 'currency',\n  currency: 'USD',\n});\nconst money = pesa(2000, 'USD');\n\nnumberFormat.format(money.float);\n// '$2,000.00'\n```\n\n#### `float`\n\nReturns the JS Number form of the underlying value.\n\nFunction signature:\n\n```typescript\nfloat: number;\n```\n\nExample:\n\n```javascript\npesa(200).float;\n// 200\n```\n\n#### `round`\n\nRounds the underlying value and returns it, this function is not susceptible to JS number foibles. If `to` is not passed it uses the `display` amount.\n\nFunction Signature:\n\n```typescript\nround(to?: number): string\n```\n\nExample:\n\n```javascript\npesa(200).round(2);\n// '200.00'\n```\n\n#### `store`\n\nProperty that displays a precision intact string representation of the value.\n\n```javascript\npesa(200, { precision: 7 }).store;\n// '200.0000000'\n```\n\n### Chainable Methods\n\nThese methods are used to set some value and return the `Money` object itself so that other functions can be called on it.\n\n#### `currency`\n\nThis is used to set the currency after initialization. Currency can be set only once so if currency has been set, this function will not change it.\n\nFunction signature:\n\n```typescript\ncurrency(value: string): Money;\n```\n\nExample:\n\n```javascript\npesa(200).currency('INR');\n```\n\n#### `rate`\n\nThis is used to set a single or multiple conversion rates. If input is a string then it is assumed that the conversion rate is from the calling objects `currency` to the string `input` passed as the first parameter with a value of `rate`. You can be more explicit by passing an object.\n\nFunction signature:\n\n```typescript\nrate(input: string | RateSetting | RateSetting[], rate?: Rate):\n\ninterface RateSetting {\n  from: string;\n  to: string;\n  rate: string | number;\n}\n```\n\nExample:\n\n```javascript\n// string input\npesa(200, 'INR').rate('USD', 75);\n\n// RateSetting input\npesa(200, 'INR').rate({ from: 'INR', to: 'USD', rate: 75 });\n\n// RateSetting[] input\npesa(200, 'INR').rate([\n  { from: 'INR', to: 'USD', rate: 75 },\n  { from: 'USD', to: 'INR', rate: 0.013 },\n]);\n```\n\n#### `clip`\n\nUsed to receive a copy of the calling object with the internal representation rounded off to the given place.\n\nFunction signature:\n\n```typescript\nclip(to: string): Money;\n```\n\nExample:\n\n```javascript\nconst money = pesa(7500, 'INR').to('USD', 1 / 75);\n\nmoney.round();\n// '99.998'\n\nconst clipped = money.clip(2);\nclipped.round();\n// '100.000'\n\nclipped.internal;\n// { bigint: 100000000n, precision: 6 }\n```\n\n#### `copy`\n\nReturns a copy of it self.\n\nFunction signature:\n\n```typescript\ncopy(): Money;\n```\n\nExample;\n\n```javascript\npesa(200).copy().round();\n// '200.000'\n```\n\n### Non Chainable Methods\n\nThese methods are used to retrieve some value from the `Money` object.\n\n#### `getCurrency`\n\nThis will return the set currency of the money object. If nothing is set then `''` is returned.\n\nFunction signature;\n\n```typescript\ngetCurrency(): string\n```\n\nExample:\n\n```javascript\npesa(200, 'INR').getCurrency();\n// 'INR'\n```\n\n#### `getConversionRate`\n\nThis will return the stored conversion rate for the given arguments. If no conversion rate is found, it will throw an error. If conversion rate is stored for _from A to B_, fetching the conversion rate for _from B to A_ will return the reciprocal unless it is explicitly set.\n\nFunction signature:\n\n```typescript\ngetConversionRate(from: string, to:string): string | number\n```\n\nExample\n\n```javascript\nconst money = pesa(200, 'INR').rate('USD', 75);\n\nmoney.getConversionRate('INR', 'USD');\n// 75\n\nmoney.getConversionRate('USD', 'INR');\n// 0.013333333333333334\n```\n\n#### `hasConversionRate`\n\nWill return true if either conversion rates _from A to B_ or _from B to A_ is found.\n\nFunction signature:\n\n```typescript\nhasConversionRate(to: string): boolean;\n```\n\nExample:\n\n```javascript\npesa(200, 'USD').rate('INR', 75).hasConversionRate('INR');\n// true\n```\n\n### Internals\n\nThese values can be used for debugging.\n\n_Note: altering the returned values won't change the values stored in the `Money` object, these are copies._\n\n#### Numeric Representation\n\nTo view the internal numeric representation, the **`.internal`** attribute can be used.\n\n```javascript\npesa(201).internal;\n// { bigint: 201000000n, precision: 6 }\n```\n\n#### Conversion Rates\n\nTo view the stored conversion rates, the **`.conversionRate`** attribute can be used.\n\n```javascript\npesa(200, 'USD').rate('INR', 75).conversionRate;\n// Map(1) { 'USD-INR' =\u003e 75 }\n```\n\nThe returned object is that of the javascript `Map` class, which has the following key format `${fromCurrency}-${toCurrency}`. The value may be a `string` or `number`.\n\n[Index](#index)\n\n## Additional Notes\n\nAdditional notes pertaining to this lib.\n\n### Storing The Value\n\nSince a `Money` constitutes of 2 values for the number: 1. the `precision`, and 2. the `value`. Storing this would require two cells, but this would be incredible stupid cause fractional numbers have already solved this with the decimal point.\n\nWe can use the `store` property to get a string representation where the mantissa length gives the precision irrespective of the significant digits.\n\n```javascript\npesa(0, { precision: 4 }).store;\n// '0.0000'\n```\n\nYou still have to deal with the `currency` though. Also if your db doesn't have a decimal type (I'm looking at you SQLite), then you'll have to store this as a string and cast it before operations.\n\n### Non-Money Stuff\n\nBecause of these two points:\n\n1. A number is considered as money only when a currency is attached to it.\n2. **`pesa`** allows deferring currency until conversion is required.\n\n**`pesa`** can be used for non monetary operations for when high precision is required or you want to circumvent the foibles of JS `number`.\n\n```javascript\npesa(0.1).add(0.2).eq(0.3);\n// true\n\npesa(9007199254740992).add(1).round(0);\n// '9007199254740993'\n```\n\n### Support\n\nSince **`pesa`** is built for high precision and large numbers that can exceed `Number.MAX_SAFE_INTEGER` it is dependent on `BigInt` so if your environment doesn't support `BigInt` you will have to rely on some other library.\n\nCheck this chart for more info: [`BigInt` Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility)\n\n### Precision vs Display\n\nA short note on the difference between the two in the context of **`pesa`**.\n\n\u003cimg src=\"https://user-images.githubusercontent.com/29507195/207266783-acb0c9ae-fd66-4307-9b58-9c2ec18e87e3.png\" alt=\"precision\" width=\"512\"/\u003e\n\nEssentially:\n\n- **Precision** refers to how many digits are stored after the decimal point.\n- **Display** refers to how many digits are shown after the decimal point when `.round` is called.\n\nIdeally this number would be the same as the minor unit value of a currency. For example if currency is USD the minor is 2, the same for INR. The problem arises when the amount is to be multiplied with another amount having a fractional component such as in the case of conversion.\n\n```javascript\npesa(333, 'INR').to('USD', 0.013).float;\n// 4.329\n```\n\nIf in the above example `precision` were set to 2 it would mess up the calculations because 0.013 gets rounded to 0.01 since input `precision` is matched to internal precision.\n\n```javascript\npesa(333, { currency: 'INR', precision: 2 }).to('USD', 0.013).float;\n// 3.33\n```\n\nSo the number you want to adjust is `display` which doesn't mess up the internal representation.\n\n```javascript\nconst money = pesa(333, { currency: 'INR', display: 2 }).to('USD', 0.013);\n\nmoney.round();\n// '4.33'\n\nmoney.float;\n// 4.329\n```\n\n### Why High Precision\n\nThe reason why you would want to conduct your operations in high precision is because: say you make 10,000 INR to USD conversions at 0.013 then that's a difference 10 USD that is added in due to rounding:\n\n```javascript\nconst oneConversion = pesa(333).mul(0.013).round(2);\n// '4.33'\n\npesa(oneConversion).mul(10000).round(2);\n// '43300.00'\n```\n\nas opposed to:\n\n```javascript\npesa(333).mul(0.013).mul(10000).round(2);\n// '43290.00'\n```\n\ni.e. someone is losing money that they shouldn't be.\n\nNow this can't be solved entirely for the same reason that 1/3 in base 10 is 0.3 with the 3 recurring ad infinitum, but in base 3 it would be 0.1. But this can be mitigated by using high precision. Which means that someone will always loose money they shouldn't be but we can control the extent to which they do.\n\n### Implicit Conversions\n\nIn **`pesa`** if you provide the conversion _from A to B_, the conversion rate _from B to A_ is implied, i.e. the reciprocal of the former.\n\nThis means that chained conversions will cause value loss:\n\n```javascript\npesa(100, 'USD').round();\n// '100.000'\n\npesa(100, 'USD').to('INR', 75).round();\n// '7500.000'\n\npesa(100, 'USD').to('INR', 75).to('USD').round();\n// '99.998'\n```\n\nThere are two ways to alleviate this until there's a proper solution:\n\n#### 1. Increase `precision` or decrease `display`:\n\n```javascript\npesa(100, { currency: 'USD', precision: 7 }).to('INR', 75).to('USD').round();\n// '100.000'\n\npesa(100, { currency: 'USD', display: 2 }).to('INR', 75).to('USD').round();\n// '100.00'\n```\n\nin both the above cases the internal representation would represent a fractional value.\n\n#### 2. Use `clip` to maintain internally rounded values:\n\n```javascript\nconst clipped = pesa(100, 'USD').to('INR', 75).to('USD').clip(2);\n\nclipped.internal;\n// { bigint: 100000000n, precision: 6 }\n\nclipped.round();\n// '100.000'\n```\n\n### Rounding\n\nRounding at mid-points is confusing af.\n\n```javascript\n2.5; // Mid-point\n\n2.49999; // Not mid-point\n2.50001; // Not mid-point\n```\n\nThe **traditional** rounding method always _rounds up_ from the mid point.\n\n```javascript\npesa(2.5, { bankersRounding: false }).round(0);\n// '3'\n```\n\nThis is uneven because of right side bias on the number line, so to mitigate this bias we have **bankers** rounding which _rounds to the closest even number_. `pesa` uses bankers rounding by default:\n\n```javascript\npesa(0.5).round(0);\n// '0'\n\npesa(1.5).round(0);\n// '2'\n\npesa(2.5).round(0);\n// '2'\n\npesa(3.5).round(0);\n// '4'\n```\n\nThings get even more confusing when considering negative numbers. But if you remember that **traditional** rounding rounds _up_, i.e. towards positive infinity or to the right of the number line, not away from zero:\n\n```javascript\npesa(-2.5, { bankersRounding: false }).round(0);\n// '-2'\n```\n\nand **bankers** rounding rounds to the _closest even number_ which is always at a distance of 0.5 (if rounding to 0):\n\n```javascript\npesa(-2.5).round(0);\n// '-2'\n\npesa(-3.5).round(0);\n// '-4'\n```\n\nyou will be less confused.\n\nFinally, remember that bankers rounding kicks in _only at the mid point_, this depends on the precision:\n\n```javascript\npesa(2.51, { precision: 2 }).round(0);\n// '3'\n\npesa(2.51, { precision: 1 }).round(0);\n// '2'\n```\n\ndue to precision loss, a non mid-point can become a mid-point and bankers algo will be used for rounding.\n\n### Use Cases\n\nHyper-realistic use cases designed to showcase the capabilities of **`pesa`**.\n\n_Disclaimer: all characters, places and events in this `README.md` are entirely fictional. Any resemblances to real characters, places, and events are purely coincidental._\n\n#### Mega Jeff in Venezuela\n\nImagine Jeff was a million times as powerful, we can call him `megaJeff`, his bank being a whopping 200 quadrillion USD:\n\n```javascript\nconst megaJeff = pesa('200_000_000_000_000_000.18', 'USD');\n```\n\n(_`megaJeff` is too powerful for JS numbers to handle accurately and hence we must rely on string for input._)\n\nand decided to emigrate to Venezuela at the height of it's hyper-inflation, requiring him to convert his USD to VEF:\n\n```javascript\nconst megaJeffInVenezuela = megaJeff.to('VEF', 451_853.23);\n```\n\nthis is a conversion that JS numbers can't handle without resorting to dirty tricks such as _E notation_.\n\n```javascript\n200_000_000_000_000_000.18 * 451_853.23;\n// 9.037064599999999e+22\n```\n\nWhich is why you need **`pesa`**.\n\n```javascript\nmegaJeffInVenezuela.round(4);\n// '90370646000000000081333.5814'\n```\n\n#### Alan the Seed Seller\n\nAlan sells seeds. He lives in Venezuela. Alan doesn't trust his dirty government, they messed up his currency. So Alan conducts his dealings only in BTC. Compared to VEF, BTC is less volatile. Seeds are precious, Alan is parsimonious. Alan likes to record the flow of each seed:\n\n```javascript\nconst numberOfSeeds = 101_234_318;\n\nconst options = { currency: 'BTC', precision: 30 };\nconst costPerSeed = pesa('0.000000031032882086386885', options); // ~3 satoshis\n```\n\nSay he wants to calculate the total value of his seeds:\n\n```javascript\nconst totalvalue = costPerSeed.mul(numberOfSeeds);\n\ntotalValue.round(24);\n// '3.141592653589793387119430'\n```\n\nif he were to rely on clumsy old JS number:\n\n```javascript\n0.000000031032882086386885 * 101_234_318;\n// 3.1415926535897936\n```\n\nhe would end up loosing almost 30% of his very significant digits. Any one who deals in BTC knows that it is very bad to loose one's digits.\n\nSay he wanted to find out the value of his seeds in USD, which at the time of writing has the conversion rate of 60951.60 from BTC.\n\n```javascript\ntotalValue.to('USD', 59379.3).round(30);\n// '186545.572655304418471780769799000000'\n```\n\nwere he to rely on JS number, he would end up with a number that—having lost more than 40% of it extremely significant digits—is completely detached from reality:\n\n```javascript\n0.000000031032882086386885 * 101_234_318 * 59379.3;\n// 186545.57265530445\n```\n\nKeep your significant digits, use **`pesa`**.\n\n_Note: hyperinflation is not a joke, if you or your country is experiencing hyperinflation please seek help._\n\n[Index](#index)\n\n## Alternatives\n\nA few good alternatives to **`pesa`** that solve a similar problem.\n\n- [currency.js](https://github.com/scurker/currency.js/)\n- [dinero.js](https://github.com/dinerojs/dinero.js/)\n\n\u003e Why a create another Money lib if these already exists?!\n\nThey either didn't use `bigint` (_[megaJeff](#mega-jeff-in-venezuela) sad_) or had too verbose an API.\n\n## Credits\n\n1. These are [Ankush's](http://github.com/ankush) wise words of wisdom. These words instilled fear of the JS `Number` in me. Thank you Ankush.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrappe%2Fpesa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrappe%2Fpesa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrappe%2Fpesa/lists"}