-
Notifications
You must be signed in to change notification settings - Fork 80
fix(input, input-number): increment/decrement unsafe numbers without loss of precision #6580
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
Conversation
…ment-bigdecimal * origin/master: test(tabs): delay story due to potential timing chromatic diff (#6437) build(deps): Bump chromatic from 6.14.0 to 6.17.1 (#6571) build(deps): Bump postcss from 8.4.20 to 8.4.21 (#6570) build(deps): Bump eslint-config-prettier from 8.6.0 to 8.7.0 (#6572) ci(eslint): ignore private/internal code for jsdoc rules (#6416)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✨🔢🔧✨
@@ -751,7 +761,7 @@ export class InputNumber | |||
event.stopPropagation(); | |||
}; | |||
|
|||
private setChildNumberElRef = (el) => { | |||
private setChildNumberElRef = (el: HTMLInputElement) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✨🏆✨
src/utils/math.ts
Outdated
const decimalNumberRegex = new RegExp(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); | ||
|
||
export const decimalPlaces = (value: string): number => { | ||
const match = value.match(decimalNumberRegex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: would test
help simplify this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to use the values of the match a couple lines down
@@ -12,6 +12,8 @@ describe("calcite-tooltip", () => { | |||
/** | |||
* Helps assert the canceled Esc key press when closing tooltips | |||
* Must be called before the tooltip is closed via keyboard. | |||
* | |||
* @param page |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you roll this file back? I think it was auto-fixed by the linter, but the file is unrelated to this PR.
src/components/slider/slider.tsx
Outdated
@@ -1200,7 +1200,7 @@ export class Slider | |||
const percent = (x - left) / width; | |||
const mirror = this.shouldMirror(); | |||
const clampedValue = this.clamp(this.min + range * (mirror ? 1 - percent : percent)); | |||
let value = Number(clampedValue.toFixed(decimalPlaces(this.step))); | |||
let value = Number(clampedValue.toFixed(decimalPlaces(`${this.step}`))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this fixes precision on the slider, can you update the PR title to reflect this? If it's to fix types, can you submit this in a separate PR? If it's the latter, we can merge the separate PR first and we can skip rolling it back here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameter needs to be a string so the decimalPlaces function can also be used with BigDecimal. Previously step
was being converted to a string in the decimalPlaces function itself, but we would lose precision if converting BigDecimal to number
before passing it to the function. So it doesn't fix anything in slider, it's just a small, required refactor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, Input isn't using decimalPlaces anymore, removing!
src/utils/number.ts
Outdated
if (exponentialParts.length === 1) { | ||
return numberString; | ||
} | ||
if (Number.isSafeInteger(+numberString)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: for readability, can you add a newline between if statements?
src/utils/number.ts
Outdated
if (exponentialParts.length === 1) { | ||
return numberString; | ||
} | ||
if (Number.isSafeInteger(+numberString)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you store +numberString
in a local variable and reuse it?
src/components/input/input.tsx
Outdated
|
||
const inputValPlaces = decimalPlaces(inputVal); | ||
const inputStepPlaces = decimalPlaces(inputStep); | ||
const finalValue = nudgedValueBelowInputMin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: can this be refactored to compute and evaluate a nudged value check at a time? It's currently computing two values where one might possibly be thrown away.
…calcite-components into benelan/input-increment-bigdecimal * 'benelan/input-increment-bigdecimal' of github.com:esri/calcite-components: chore: cleanup fix: remove BigDecimal.toFixed() method style: format
Related Issue: #5920
Summary
Numbers that cannot be represented in JavaScript's IEEE-754 double precision were being rounded when incrementing/decrementing because they were cast as Number (we store the value as a string). We use BigDecimal class to support numbers that do not fit into JavaScript's encoding method. I had overlooked the increment/decrement functionality when implementing BigDecimal.
BigDecimal uses BigInt internally, and unfortunately BigInt doesn't support exponential notation. For example,
BigInt("1e2")
throws the error:To work around the constraint, I manually expand any exponential notation numbers into normal decimal numbers. All of this is done via string manipulation, so sorry in advance for the burned retinas you are about to receive from this PR review.
For more info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger#description