Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add full floating point support with divFloat function #19

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/lib/src/index.js",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": [
"${workspaceFolder}/lib/**/*.js"
]
},
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"-r",
"sucrase/register",
"${workspaceFolder}/test/**/*.test.ts",
],
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": [
"<node_internals>/**"
]
},
]
}
samholmes marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- added: divf() to do floating point division with no need to specify precision

## 4.2.0 (2023-12-05)

- added: New toBns function to convert JS number to big number strings
Expand Down
74 changes: 56 additions & 18 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ import BN from 'bn.js'

interface ShiftPair {
shift: number
extraShift: number
x: string
y: string
}

const SCI_NOTATION_REGEX = /^(-?\d*\.?\d*)e((?:\+|-)?\d+)$/

// This is the approximate number of decimal digits that can fit into a 256
// bit number. For math operations, we expand floating point numbers to
// turn them into integers for use with the BN library. This is the number of
// digits we expand to.
const TARGET_BIGNUM_DIGITS = 77

// -----------------------------------------------------------------------------
// Public
// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -70,25 +77,36 @@ export function sub(
return base === 10 ? out : out.replace(/^(-)?/, '$10x')
}

export function divf(x1: string, y1: string): string {
return div(x1, y1, true)
}
Comment on lines +80 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when the decimal is repetitive?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by repetitive?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if x1 = '1' and y1 = '3'


export function div(
x1: string | number,
y1: string | number,
precision: number = 0,
precision: true | number = 0,
base: number = 10
): string {
if (base !== 10 && precision > 0) {
if (base !== 10 && precision !== 0) {
throw new Error('Cannot operate on floating point hex values')
}
if (base !== 10 && base !== 16) throw new Error('Unsupported base')
let { x, y } = floatShifts(x1, y1, precision)
let { x, y, extraShift } = floatShifts(
x1,
y1,
precision === true || precision > 0
)
const xBase = isHex(x) ? 16 : 10
const yBase = isHex(y) ? 16 : 10
x = cropHex(x)
y = cropHex(y)
const xBN = new BN(x, xBase)
const yBN = new BN(y, yBase)
let out = xBN.div(yBN).toString(base)
out = addDecimal(out, precision)
out = addDecimal(out, extraShift)
if (typeof precision === 'number' && precision > 0) {
out = toFixed(out, 0, precision)
}
return base === 10 ? out : out.replace(/^(-)?/, '$10x')
}

Expand Down Expand Up @@ -318,21 +336,37 @@ function cropHex(x: string): string {
return x.replace('0x', '')
}

// Takes two floating point (base 10) numbers and finds the multiplier needed to make them both
// operable as a integer
/**
* Takes two numbers and finds the multiplier needed to make them both
* operable as a integer. If the numbers are floating point they must
* be base 10. The ShiftPair return value contains the two numbers shifted
* by a fixed number of decimal places and the number of decimal places
* they were shifted by in the `shift` param.
*
* ShiftPair.extraShift are the extra digits we add to x when doFloat is
* true which is specifically for div so that the numerator (x) can have
* enough precision to be divided by the denominator (y) since the actual
* math is done in integers.
*
* 1/3 would be 0 without extraShift. By adding more digits to '1' we
* get 1000/3 which is 333. When we convert back to float by shifting back
* extraDigits, we get 0.333
* @param xStart
* @param yStart
* @param doFloat - If true, add extra digits to x to allow for more divide precision
* @returns ShiftPair
*/
function floatShifts(
xStart: string | number,
yStart: string | number,
moreShift?: number
doFloat: boolean = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a separate commit with a description of this internal function's signature?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Check out the comment and see if that's enough clarity

): ShiftPair {
let x = toBns(xStart)
let y = toBns(yStart)

let xPos: number = x.indexOf('.')
let yPos: number = y.indexOf('.')

const xHex: boolean = isHex(x)
const yHex: boolean = isHex(y)

if (xPos !== -1) {
// Remove trailing zeros
x = trimEnd(x)
Expand All @@ -345,8 +379,8 @@ function floatShifts(
yPos = y.indexOf('.')
}

if (xPos !== -1 || yPos !== -1 || typeof moreShift === 'number') {
if (xHex || yHex) {
if (xPos !== -1 || yPos !== -1 || doFloat) {
if (isHex(x) || isHex(y)) {
throw new Error('Cannot operate on base16 float values')
}

Expand All @@ -362,15 +396,18 @@ function floatShifts(
}

const shift = xShift > yShift ? xShift : yShift
let moreS = 0
if (typeof moreShift === 'number') {
moreS = moreShift
}

x = addZeros(x.replace('.', ''), shift + moreS - xShift)
x = addZeros(x.replace('.', ''), shift - xShift)
y = addZeros(y.replace('.', ''), shift - yShift)

const out: ShiftPair = { x, y, shift }
let extraShift = 0
if (doFloat) {
samholmes marked this conversation as resolved.
Show resolved Hide resolved
const totalLength = x.length + y.length
extraShift = Math.max(totalLength, TARGET_BIGNUM_DIGITS)
x = addZeros(x, extraShift)
samholmes marked this conversation as resolved.
Show resolved Hide resolved
}

const out: ShiftPair = { x, y, shift, extraShift }

return out
} else {
Expand All @@ -379,6 +416,7 @@ function floatShifts(
x,
y,
shift: 0,
extraShift: 0,
}
return out
}
Expand Down
56 changes: 56 additions & 0 deletions test/biggystring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
add,
ceil,
div,
divf,
eq,
floor,
gt,
Expand Down Expand Up @@ -173,6 +174,61 @@ describe('div', function () {
'400000000000000000000000000'
)
})
it('divf with smaller numerator', function () {
assert.equal(
divf('258314', '44259003611849456'),
'0.00000000000583641697552453787845483515333625998899100236535579470378814823369'
)
})
it('divf with smaller denominator', function () {
assert.equal(
divf('44259003611849456', '258314'),
'171337997986.36332525530942960892557120403849578420062404670285001974341305542866433875051'
)
})
it('divf with decimal numerator', function () {
assert.equal(
divf('4425900361184.9456', '258314'),
'17133799.79863633252553094296089255712040384957842006240467028500197434130554286643387'
)
})
it('divf with decimal denominator', function () {
assert.equal(
divf('258314', '4425900361184.9456'),
'0.00000005836416975524537878454835153336259988991002365355794703788148233694743'
)
})
it('divf with <1 decimal numerator', function () {
assert.equal(
divf('0.00001234', '258314'),
'0.00000000004777131707921367018434928033323784231594106397640081451256997297862'
)
})
it('divf with <1 decimal denominator', function () {
assert.equal(
divf('258314', '0.00001234'),
'20933063209.07617504051863857374392220421393841166936790923824959481361426256077795786061'
)
})
it('divf with very big divided by very small', function () {
assert.equal(
divf(
'258314184762834762387462843985739845739845734.123876328756',
'0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234'
),
'209330781817532222356128722840956114862111615983692324761750405186385737439222042139384116693679092382495948136142625607779578606158833063209.0761750405186385737439222042139384116693679092382495948136142625607779578606158833063209076175040518638573743922204213938411669367909238249594813614262560777957860615883306320907617504051863857374392220421393841166936790923824959481361426256077'
)
})
it('divf with very small divided by very big', function () {
assert.equal(
divf(
'0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234',
'258314184762834762387462843985739845739845734.123876328756'
),
'0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047771282910110753934911288073993261089606462593095155090550880308370686890364229227812023898933169363535'
)
})

it('very big float (precision 9, base 10)', function () {
assert.equal(
div('800000000000000000000000000.000000008', '2', 9, 10),
Expand Down
Loading