Skip to content

Commit

Permalink
fix: should format max/min with precision (#385)
Browse files Browse the repository at this point in the history
Co-authored-by: wanghao <[email protected]>
  • Loading branch information
boomler and wanghao authored Dec 6, 2021
1 parent d65b0dc commit d3de147
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 7 deletions.
41 changes: 35 additions & 6 deletions src/InputNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@ import * as React from 'react';
import classNames from 'classnames';
import KeyCode from 'rc-util/lib/KeyCode';
import { composeRef } from 'rc-util/lib/ref';
import getMiniDecimal, { DecimalClass, toFixed, ValueType } from './utils/MiniDecimal';
import getMiniDecimal, {
DecimalClass,
roundDownUnsignedDecimal,
roundUpUnsignedDecimal,
toFixed,
ValueType
} from './utils/MiniDecimal';
import StepHandler from './StepHandler';
import { getNumberPrecision, num2str, validateNumber } from './utils/numberUtil';
import { getNumberPrecision, num2str, trimNumber, validateNumber } from './utils/numberUtil';
import useCursor from './hooks/useCursor';
import useUpdateEffect from './hooks/useUpdateEffect';
import useFrame from './hooks/useFrame';

/**
* We support `stringMode` which need handle correct type when user call in onChange
* format max or min value
* 1. if isInvalid return null
* 2. if precision is undefined, return decimal
* 3. format with precision
* I. if max > 0, round down with precision. Example: max= 3.5, precision=0 afterFormat: 3
* II. if max < 0, round up with precision. Example: max= -3.5, precision=0 afterFormat: -4
* III. if min > 0, round up with precision. Example: min= 3.5, precision=0 afterFormat: 4
* IV. if min < 0, round down with precision. Example: max= -3.5, precision=0 afterFormat: -3
*/
const getDecimalValue = (stringMode: boolean, decimalValue: DecimalClass) => {
if (stringMode || decimalValue.isEmpty()) {
Expand All @@ -20,9 +34,24 @@ const getDecimalValue = (stringMode: boolean, decimalValue: DecimalClass) => {
return decimalValue.toNumber();
};

const getDecimalIfValidate = (value: ValueType) => {
const getDecimalIfValidate = (value: ValueType, precision: number | undefined, isMax?: boolean) => {
const decimal = getMiniDecimal(value);
return decimal.isInvalidate() ? null : decimal;
if (decimal.isInvalidate()) {
return null;
}

if (precision === undefined) {
return decimal;
}

const {negative, integerStr, decimalStr, negativeStr} = trimNumber(decimal.toString());
const unSignedNumberStr = integerStr +'.' + decimalStr;

if ((isMax && !negative) || (!isMax && negative)) {
return getMiniDecimal(negativeStr + roundDownUnsignedDecimal(unSignedNumberStr, precision));
} else {
return getMiniDecimal(negativeStr + roundUpUnsignedDecimal(unSignedNumberStr, precision));
}
};

export interface InputNumberProps<T extends ValueType = ValueType>
Expand Down Expand Up @@ -232,8 +261,8 @@ const InputNumber = React.forwardRef(
}

// >>> Max & Min limit
const maxDecimal = React.useMemo(() => getDecimalIfValidate(max), [max]);
const minDecimal = React.useMemo(() => getDecimalIfValidate(min), [min]);
const maxDecimal = React.useMemo(() => getDecimalIfValidate(max, precision, true), [max, precision]);
const minDecimal = React.useMemo(() => getDecimalIfValidate(min, precision, false), [min, precision]);

const upDisabled = React.useMemo(() => {
if (!maxDecimal || !decimalValue || decimalValue.isInvalidate()) {
Expand Down
25 changes: 25 additions & 0 deletions src/utils/MiniDecimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,31 @@ export default function getMiniDecimal(value: ValueType): DecimalClass {
return new NumberDecimal(value);
}

/**
* round up an unsigned number str, like: 1.4 -> 2, 1.5 -> 2
*/
export function roundUpUnsignedDecimal(numStr: string, precision: number) {
const {integerStr, decimalStr} = trimNumber(numStr);
const advancedDecimal = getMiniDecimal(integerStr + '.' + decimalStr).add(
`0.${'0'.repeat(precision)}${5}`,
);
return toFixed(advancedDecimal.toString(), '.', precision);
}

/**
* round up an unsigned number str, like: 1.4 -> 1, 1.5 -> 1
*/
export function roundDownUnsignedDecimal(numStr: string, precision: number) {
const {negativeStr, integerStr, decimalStr} = trimNumber(numStr);
const numberWithoutDecimal = `${negativeStr}${integerStr}`;
if (precision === 0) {
return integerStr;
}
return `${numberWithoutDecimal}.${decimalStr
.padEnd(precision, '0')
.slice(0, precision)}`;
}

/**
* Align the logic of toFixed to around like 1.5 => 2
*/
Expand Down
21 changes: 21 additions & 0 deletions tests/input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ describe('InputNumber.Input', () => {
expect(wrapper.getInputValue()).toEqual('-98');
});

it('negative min with higher precision', () => {
const wrapper = prepareWrapper('-4', {min: -3.5, precision: 0});
expect(wrapper.getInputValue()).toEqual('-3');
});

it('positive min with higher precision', () => {
const wrapper = prepareWrapper('4', {min: 3.5, precision: 0});
expect(wrapper.getInputValue()).toEqual('4');
});

it('negative max with higher precision', () => {
const wrapper = prepareWrapper('-4', {max: -3.5, precision: 0});
expect(wrapper.getInputValue()).toEqual('-4');
});

it('positive max with higher precision', () => {
const wrapper = prepareWrapper('4', {max: 3.5, precision: 0});
expect(wrapper.getInputValue()).toEqual('3');
});


// https://github.com/ant-design/ant-design/issues/9439
it('input negative zero', () => {
const wrapper = prepareWrapper('-0', {}, true);
Expand Down
20 changes: 19 additions & 1 deletion tests/util.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import getMiniDecimal, {
BigIntDecimal,
DecimalClass,
NumberDecimal,
ValueType,
roundDownUnsignedDecimal,
roundUpUnsignedDecimal,
toFixed,
ValueType,
} from '../src/utils/MiniDecimal';

jest.mock('../src/utils/supportUtil');
Expand Down Expand Up @@ -150,5 +152,21 @@ describe('InputNumber.Util', () => {
// expect(toFixed('77.88', '.', 1)).toEqual('77.9');
expect(toFixed('-77.88', '.', 1)).toEqual('-77.9');
});

it('round down', () => {
expect(roundDownUnsignedDecimal('77.89', 1)).toEqual('77.8');
expect(roundDownUnsignedDecimal('77.1', 2)).toEqual('77.10');
expect(roundDownUnsignedDecimal('77.81', 1)).toEqual('77.8');
expect(roundDownUnsignedDecimal('77.50', 1)).toEqual('77.5');
expect(roundDownUnsignedDecimal('77.5999', 0)).toEqual('77');
expect(roundDownUnsignedDecimal('77.0001', 0)).toEqual('77');
})
it('round up', () => {
expect(roundUpUnsignedDecimal('77.89', 1)).toEqual('77.9');
expect(roundUpUnsignedDecimal('77.81', 1)).toEqual('77.9');
expect(roundUpUnsignedDecimal('77.89', 0)).toEqual('78');
expect(roundUpUnsignedDecimal('77.599', 0)).toEqual('78');
expect(roundUpUnsignedDecimal('77.01', 0)).toEqual('78');
})
});
});

1 comment on commit d3de147

@vercel
Copy link

@vercel vercel bot commented on d3de147 Dec 6, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.