Repository: streamlit/streamlit
File: frontend/lib/src/components/widgets/NumberInput/utils.ts (TypeScript)

Code before:
 *
 * This function handles both standard decimal steps (0.01) and very small steps
 * that JavaScript represents in scientific notation (1e-7).
 *
 * @note Precision limitation: For steps requiring more than 15 decimal places,
 * the scale factor (10^decimalPlaces) may exceed JavaScript's MAX_SAFE_INTEGER
 * (2^53 - 1 ≈ 9e15), causing precision loss. This is a fundamental limitation
 * of IEEE 754 double-precision floating-point numbers. For most practical
 * use cases (steps >= 1e-14), this function provides exact results.
 */
export const preciseStepArithmetic = (
  currentValue: number,
  step: number,
  operation: "add" | "subtract"
): number => {
  const decimalPlaces = getDecimalPlaces(step)

  // Fast path for integer steps (decimalPlaces === 0)
  // Standard arithmetic is exact for integers, no scaling needed
  if (decimalPlaces === 0) {
    return operation === "add" ? currentValue + step : currentValue - step
  }

  const scale = Math.pow(10, decimalPlaces)

  return operation === "add"
    ? (Math.round(currentValue * scale) + Math.round(step * scale)) / scale
    : (Math.round(currentValue * scale) - Math.round(step * scale)) / scale
}

export const getStep = ({
  step,
  dataType,
}: Pick<NumberInputProto, "step" | "dataType">): number => {
  if (step) {
    return step
  }
  if (dataType === NumberInputProto.DataType.INT) {
    return 1
  }
  return 0.01
}

// State management callbacks for useBasicWidgetState
export function getStateFromWidgetMgr(
  widgetMgr: WidgetStateManager,
  element: NumberInputProto
): number | null | undefined {
  const isIntData = element.dataType === NumberInputProto.DataType.INT
  return isIntData
    ? widgetMgr.getIntValue(element)

Review comment (suggestion):
**super nit:** I think I'd slightly prefer a switch statement here with `switch(operation)` just for readability and defensiveness reasons

Code after:
 *
 * This function handles both standard decimal steps (0.01) and very small steps
 * that JavaScript represents in scientific notation (1e-7).
 *
 * @note Precision limitation: For steps requiring more than 15 decimal places,
 * the scale factor (10^decimalPlaces) may exceed JavaScript's MAX_SAFE_INTEGER
 * (2^53 - 1 ≈ 9e15), causing precision loss. This is a fundamental limitation
 * of IEEE 754 double-precision floating-point numbers. For most practical
 * use cases (steps >= 1e-14), this function provides exact results.
 */
export const preciseStepArithmetic = (
  currentValue: number,
  step: number,
  operation: "add" | "subtract"
): number => {
  const decimalPlaces = getDecimalPlaces(step)

  // Fast path for integer steps (decimalPlaces === 0)
  // Standard arithmetic is exact for integers, no scaling needed
  if (decimalPlaces === 0) {
    switch (operation) {
      case "add":
        return currentValue + step
      case "subtract":
        return currentValue - step
    }
  }

  const scale = Math.pow(10, decimalPlaces)
  const scaledCurrent = Math.round(currentValue * scale)
  const scaledStep = Math.round(step * scale)

  switch (operation) {
    case "add":
      return (scaledCurrent + scaledStep) / scale
    case "subtract":
      return (scaledCurrent - scaledStep) / scale
  }
}

export const getStep = ({
  step,
  dataType,
}: Pick<NumberInputProto, "step" | "dataType">): number => {
  if (step) {
    return step
  }
  if (dataType === NumberInputProto.DataType.INT) {
    return 1
  }
  return 0.01