From b2d765844953f7a50b5b8e4c36d947d33660ff8b Mon Sep 17 00:00:00 2001 From: Lucas Weng Date: Thu, 22 May 2025 23:59:23 +0800 Subject: [PATCH 1/3] fix: round to correct precision when step uses exponential notation --- packages/@react-stately/utils/src/number.ts | 11 +++++++- .../@react-stately/utils/test/number.test.ts | 26 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/@react-stately/utils/src/number.ts b/packages/@react-stately/utils/src/number.ts index b6184ecd7ff..42cd38eadee 100644 --- a/packages/@react-stately/utils/src/number.ts +++ b/packages/@react-stately/utils/src/number.ts @@ -20,9 +20,18 @@ export function clamp(value: number, min: number = -Infinity, max: number = Infi export function roundToStepPrecision(value: number, step: number): number { let roundedValue = value; + let precision = 0; let stepString = step.toString(); let pointIndex = stepString.indexOf('.'); - let precision = pointIndex >= 0 ? stepString.length - pointIndex : 0; + if (pointIndex >= 0) { + precision = stepString.length - pointIndex; + } else { + // Handle negative exponents in exponential notation (e.g., "1e-7" → precision 8) + let eIndex = stepString.toLowerCase().indexOf('e-'); + if (eIndex > 0) { + precision = Math.abs(Number(stepString.slice(eIndex + 1))) + 1; + } + } if (precision > 0) { let pow = Math.pow(10, precision); roundedValue = Math.round(roundedValue * pow) / pow; diff --git a/packages/@react-stately/utils/test/number.test.ts b/packages/@react-stately/utils/test/number.test.ts index 0cfdef0b6e5..6e8e0a4b684 100644 --- a/packages/@react-stately/utils/test/number.test.ts +++ b/packages/@react-stately/utils/test/number.test.ts @@ -1,4 +1,4 @@ -import {clamp, snapValueToStep} from '../src/number'; +import {clamp, roundToStepPrecision, snapValueToStep} from '../src/number'; describe('number utils', () => { describe('clamp', () => { @@ -8,6 +8,30 @@ describe('number utils', () => { }); }); + describe('roundToStepPrecision', () => { + it('should return the input unchanged for integer steps', () => { + expect(roundToStepPrecision(7.123, 1)).toBe(7.123); + expect(roundToStepPrecision(5, 10)).toBe(5); + }); + it('should round to the correct decimal places for steps with decimals', () => { + expect(roundToStepPrecision(1.24, 0.1)).toBe(1.24); + expect(roundToStepPrecision(1.456, 0.01)).toBe(1.456); + // Should not overcount precision + expect(roundToStepPrecision(2.349, 0.100)).toBe(2.35); + expect(roundToStepPrecision(2.35, 0.100)).toBe(2.35); + // Should handle negative values + expect(roundToStepPrecision(-1.456, 0.01)).toBe(-1.456); + // Should handle zero value + expect(roundToStepPrecision(0, 0.01)).toBe(0); + }); + it('should handle rounding for exponential step values', () => { + expect(roundToStepPrecision(0.123456789, 1e-3)).toBe(0.1235); + expect(roundToStepPrecision(0.123456789, 1e-7)).toBe(0.12345679); + // Should handle exponential notation steps regardless of e/E case + expect(roundToStepPrecision(0.123456789, 1E-8)).toBe(0.123456789); + }); + }); + describe('snapValueToStep', () => { it('should snap value to nearest step based on min and max', () => { expect(snapValueToStep(2, -0.5, 100, 3)).toBe(2.5); From 872c52702bae55fa43008b23976ae0d0503e6f41 Mon Sep 17 00:00:00 2001 From: Lucas Weng Date: Mon, 2 Jun 2025 23:17:41 +0800 Subject: [PATCH 2/3] fix: correct significand precision calculation for exponential steps --- packages/@react-stately/utils/src/number.ts | 18 +++++++++++------- .../@react-stately/utils/test/number.test.ts | 4 ++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/@react-stately/utils/src/number.ts b/packages/@react-stately/utils/src/number.ts index 42cd38eadee..0c2df8fb62e 100644 --- a/packages/@react-stately/utils/src/number.ts +++ b/packages/@react-stately/utils/src/number.ts @@ -22,14 +22,18 @@ export function roundToStepPrecision(value: number, step: number): number { let roundedValue = value; let precision = 0; let stepString = step.toString(); - let pointIndex = stepString.indexOf('.'); - if (pointIndex >= 0) { - precision = stepString.length - pointIndex; + // Handle negative exponents in exponential notation (e.g., "1e-7" → precision 8) + let eIndex = stepString.toLowerCase().indexOf('e-'); + if (eIndex > 0) { + let significand = stepString.slice(0, eIndex); + let exponent = stepString.slice(eIndex + 1); + let pointIndex = significand.indexOf('.'); + let decimals = pointIndex >= 0 ? significand.length - pointIndex : 0; + precision = Math.abs(Number(exponent)) + decimals + 1; } else { - // Handle negative exponents in exponential notation (e.g., "1e-7" → precision 8) - let eIndex = stepString.toLowerCase().indexOf('e-'); - if (eIndex > 0) { - precision = Math.abs(Number(stepString.slice(eIndex + 1))) + 1; + let pointIndex = stepString.indexOf('.'); + if (pointIndex >= 0) { + precision = stepString.length - pointIndex; } } if (precision > 0) { diff --git a/packages/@react-stately/utils/test/number.test.ts b/packages/@react-stately/utils/test/number.test.ts index 6e8e0a4b684..ed845f69e27 100644 --- a/packages/@react-stately/utils/test/number.test.ts +++ b/packages/@react-stately/utils/test/number.test.ts @@ -16,6 +16,8 @@ describe('number utils', () => { it('should round to the correct decimal places for steps with decimals', () => { expect(roundToStepPrecision(1.24, 0.1)).toBe(1.24); expect(roundToStepPrecision(1.456, 0.01)).toBe(1.456); + expect(roundToStepPrecision(1.2345, 0.015)).toBe(1.2345); + expect(roundToStepPrecision(1.2345, 0.25)).toBe(1.235); // Should not overcount precision expect(roundToStepPrecision(2.349, 0.100)).toBe(2.35); expect(roundToStepPrecision(2.35, 0.100)).toBe(2.35); @@ -27,6 +29,8 @@ describe('number utils', () => { it('should handle rounding for exponential step values', () => { expect(roundToStepPrecision(0.123456789, 1e-3)).toBe(0.1235); expect(roundToStepPrecision(0.123456789, 1e-7)).toBe(0.12345679); + expect(roundToStepPrecision(0.123456789, 1.5e-7)).toBe(0.123456789); + expect(roundToStepPrecision(0.123456789, 2.5e-6)).toBe(0.12345679); // Should handle exponential notation steps regardless of e/E case expect(roundToStepPrecision(0.123456789, 1E-8)).toBe(0.123456789); }); From c447134dbbb3cd7830af01559b6fa95e1f1f7359 Mon Sep 17 00:00:00 2001 From: Lucas Weng Date: Thu, 5 Jun 2025 20:50:32 +0800 Subject: [PATCH 3/3] refactor: simplify precision calculation for exponents --- packages/@react-stately/utils/src/number.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/@react-stately/utils/src/number.ts b/packages/@react-stately/utils/src/number.ts index 0c2df8fb62e..13a1c3fd871 100644 --- a/packages/@react-stately/utils/src/number.ts +++ b/packages/@react-stately/utils/src/number.ts @@ -25,11 +25,7 @@ export function roundToStepPrecision(value: number, step: number): number { // Handle negative exponents in exponential notation (e.g., "1e-7" → precision 8) let eIndex = stepString.toLowerCase().indexOf('e-'); if (eIndex > 0) { - let significand = stepString.slice(0, eIndex); - let exponent = stepString.slice(eIndex + 1); - let pointIndex = significand.indexOf('.'); - let decimals = pointIndex >= 0 ? significand.length - pointIndex : 0; - precision = Math.abs(Number(exponent)) + decimals + 1; + precision = Math.abs(Math.floor(Math.log10(Math.abs(step)))) + eIndex; } else { let pointIndex = stepString.indexOf('.'); if (pointIndex >= 0) {