Skip to content

Commit f5b09de

Browse files
Emiyaaaaasindresorhus
andauthoredApr 22, 2024··
Add IsInteger and IsFloat, fix Integer and Float handing with edge case (#857)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
1 parent c1d2e0a commit f5b09de

8 files changed

+178
-9
lines changed
 

‎index.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export type {HasReadonlyKeys} from './source/has-readonly-keys';
9898
export type {WritableKeysOf} from './source/writable-keys-of';
9999
export type {HasWritableKeys} from './source/has-writable-keys';
100100
export type {Spread} from './source/spread';
101+
export type {IsInteger} from './source/is-integer';
102+
export type {IsFloat} from './source/is-float';
101103
export type {TupleToUnion} from './source/tuple-to-union';
102104
export type {IntRange} from './source/int-range';
103105
export type {IsEqual} from './source/is-equal';

‎readme.md

+2
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
285285
- [`NegativeInteger`](source/numeric.d.ts) - A negative (`-∞ < x < 0`) `number` that is an integer.
286286
- [`NonNegativeInteger`](source/numeric.d.ts) - A non-negative (`0 <= x < ∞`) `number` that is an integer.
287287
- [`IsNegative`](source/numeric.d.ts) - Returns a boolean for whether the given number is a negative number.
288+
- [`IsFloat`](source/is-float.d.ts) - Returns a boolean for whether the given number is a float, like `1.5` or `-1.5`.
289+
- [`IsInteger`](source/is-integer.d.ts) - Returns a boolean for whether the given number is a integer, like `-5`, `1.0` or `100`.
288290
- [`GreaterThan`](source/greater-than.d.ts) - Returns a boolean for whether a given number is greater than another number.
289291
- [`GreaterThanOrEqual`](source/greater-than-or-equal.d.ts) - Returns a boolean for whether a given number is greater than or equal to another number.
290292
- [`LessThan`](source/less-than.d.ts) - Returns a boolean for whether a given number is less than another number.

‎source/is-float.d.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type {Zero} from './numeric';
2+
3+
/**
4+
Returns a boolean for whether the given number is a float, like `1.5` or `-1.5`.
5+
6+
It returns `false` for `Infinity`.
7+
8+
Use-case:
9+
- If you want to make a conditional branch based on the result of whether a number is a float or not.
10+
11+
@example
12+
```
13+
type Float = IsFloat<1.5>;
14+
//=> true
15+
16+
type IntegerWithDecimal = IsInteger<1.0>;
17+
//=> false
18+
19+
type NegativeFloat = IsInteger<-1.5>;
20+
//=> true
21+
22+
type Infinity_ = IsInteger<Infinity>;
23+
//=> false
24+
```
25+
*/
26+
export type IsFloat<T> =
27+
T extends number
28+
? `${T}` extends `${infer _Sign extends '' | '-'}${number}.${infer Decimal extends number}`
29+
? Decimal extends Zero
30+
? false
31+
: true
32+
: false
33+
: false;

‎source/is-integer.d.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type {Not} from './internal';
2+
import type {IsFloat} from './is-float';
3+
import type {PositiveInfinity, NegativeInfinity} from './numeric';
4+
5+
/**
6+
Returns a boolean for whether the given number is a integer, like `-5`, `1.0` or `100`.
7+
8+
Like [`Number#IsInteger()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/IsInteger) but for types.
9+
10+
Use-case:
11+
- If you want to make a conditional branch based on the result of whether a number is a intrger or not.
12+
13+
@example
14+
```
15+
type Integer = IsInteger<1>;
16+
//=> true
17+
18+
type IntegerWithDecimal = IsInteger<1.0>;
19+
//=> true
20+
21+
type NegativeInteger = IsInteger<-1>;
22+
//=> true
23+
24+
type Float = IsInteger<1.5>;
25+
//=> false
26+
27+
// Supports non-decimal numbers
28+
29+
type OctalInteger: IsInteger<0o10>;
30+
//=> true
31+
32+
type BinaryInteger: IsInteger<0b10>;
33+
//=> true
34+
35+
type HexadecimalInteger: IsInteger<0x10>;
36+
//=> true
37+
```
38+
*/
39+
export type IsInteger<T> =
40+
T extends bigint
41+
? true
42+
: T extends number
43+
? number extends T
44+
? false
45+
: T extends PositiveInfinity | NegativeInfinity
46+
? false
47+
: Not<IsFloat<T>>
48+
: false;

‎source/numeric.d.ts

+39-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type {IsFloat} from './is-float';
2+
import type {IsInteger} from './is-integer';
3+
14
export type Numeric = number | bigint;
25

36
type Zero = 0 | 0n;
@@ -49,10 +52,35 @@ export type Finite<T extends number> = T extends PositiveInfinity | NegativeInfi
4952

5053
/**
5154
A `number` that is an integer.
52-
You can't pass a `bigint` as they are already guaranteed to be integers.
5355
5456
Use-case: Validating and documenting parameters.
5557
58+
@example
59+
```
60+
type Integer = Integer<1>;
61+
//=> 1
62+
63+
type IntegerWithDecimal = Integer<1.0>;
64+
//=> 1
65+
66+
type NegativeInteger = Integer<-1>;
67+
//=> -1
68+
69+
type Float = Integer<1.5>;
70+
//=> never
71+
72+
// Supports non-decimal numbers
73+
74+
type OctalInteger: Integer<0o10>;
75+
//=> 0o10
76+
77+
type BinaryInteger: Integer<0b10>;
78+
//=> 0b10
79+
80+
type HexadecimalInteger: Integer<0x10>;
81+
//=> 0x10
82+
```
83+
5684
@example
5785
```
5886
import type {Integer} from 'type-fest';
@@ -67,14 +95,18 @@ declare function setYear<T extends number>(length: Integer<T>): void;
6795
*/
6896
// `${bigint}` is a type that matches a valid bigint literal without the `n` (ex. 1, 0b1, 0o1, 0x1)
6997
// Because T is a number and not a string we can effectively use this to filter out any numbers containing decimal points
70-
export type Integer<T extends number> = `${T}` extends `${bigint}` ? T : never;
98+
export type Integer<T> =
99+
T extends unknown // To distributive type
100+
? IsInteger<T> extends true ? T : never
101+
: never; // Never happens
71102

72103
/**
73104
A `number` that is not an integer.
74-
You can't pass a `bigint` as they are already guaranteed to be integers.
75105
76106
Use-case: Validating and documenting parameters.
77107
108+
It does not accept `Infinity`.
109+
78110
@example
79111
```
80112
import type {Float} from 'type-fest';
@@ -86,7 +118,10 @@ declare function setPercentage<T extends number>(length: Float<T>): void;
86118
87119
@category Numeric
88120
*/
89-
export type Float<T extends number> = T extends Integer<T> ? never : T;
121+
export type Float<T> =
122+
T extends unknown // To distributive type
123+
? IsFloat<T> extends true ? T : never
124+
: never; // Never happens
90125

91126
/**
92127
A negative (`-∞ < x < 0`) `number` that is not an integer.

‎test-d/is-float.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {expectType} from 'tsd';
2+
import type {IsFloat, PositiveInfinity} from '../index';
3+
4+
expectType<false>({} as IsFloat<0>);
5+
expectType<false>({} as IsFloat<1>);
6+
expectType<false>({} as IsFloat<1.0>); // eslint-disable-line unicorn/no-zero-fractions
7+
expectType<true>({} as IsFloat<1.5>);
8+
expectType<false>({} as IsFloat<-1>);
9+
expectType<false>({} as IsFloat<number>);
10+
expectType<false>({} as IsFloat<0o10>);
11+
expectType<false>({} as IsFloat<1n>);
12+
expectType<false>({} as IsFloat<0n>);
13+
expectType<false>({} as IsFloat<0b10>);
14+
expectType<false>({} as IsFloat<0x10>);
15+
expectType<false>({} as IsFloat<1e+100>);
16+
expectType<false>({} as IsFloat<PositiveInfinity>);
17+
expectType<false>({} as IsFloat<typeof Number.POSITIVE_INFINITY>);

‎test-d/is-integer.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {expectType} from 'tsd';
2+
import type {IsInteger, PositiveInfinity} from '../index';
3+
4+
expectType<true>({} as IsInteger<0>);
5+
expectType<true>({} as IsInteger<1>);
6+
expectType<true>({} as IsInteger<1.0>); // eslint-disable-line unicorn/no-zero-fractions
7+
expectType<false>({} as IsInteger<1.5>);
8+
expectType<true>({} as IsInteger<-1>);
9+
expectType<false>({} as IsInteger<number>);
10+
expectType<true>({} as IsInteger<0o10>);
11+
expectType<true>({} as IsInteger<1n>);
12+
expectType<true>({} as IsInteger<0n>);
13+
expectType<true>({} as IsInteger<0b10>);
14+
expectType<true>({} as IsInteger<0x10>);
15+
expectType<true>({} as IsInteger<1e+100>);
16+
expectType<false>({} as IsInteger<PositiveInfinity>);
17+
expectType<false>({} as IsInteger<typeof Number.POSITIVE_INFINITY>);

‎test-d/numeric.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,40 @@ expectType<1>(infinityMixed);
2121

2222
// Integer
2323
declare const integer: Integer<1>;
24-
declare const integerMixed: Integer<1 | 1.5>;
24+
declare const integerWithDecimal: Integer<1.0>; // eslint-disable-line unicorn/no-zero-fractions
25+
declare const numberType: Integer<number>;
26+
declare const integerMixed: Integer<1 | 1.5 | -1>;
27+
declare const bigInteger: Integer<1e+100>;
28+
declare const octalInteger: Integer<0o10>;
29+
declare const binaryInteger: Integer<0b10>;
30+
declare const hexadecimalInteger: Integer<0x10>;
2531
declare const nonInteger: Integer<1.5>;
2632
declare const infinityInteger: Integer<PositiveInfinity | NegativeInfinity>;
33+
const infinityValue = Number.POSITIVE_INFINITY;
34+
declare const infinityInteger2: Integer<typeof infinityValue>;
2735

2836
expectType<1>(integer);
29-
expectType<never>(integerMixed); // This may be undesired behavior
37+
expectType<1>(integerWithDecimal);
38+
expectType<never>(numberType);
39+
expectType<1 | -1>(integerMixed);
40+
expectType<1e+100>(bigInteger);
41+
expectType<0o10>(octalInteger);
42+
expectType<0b10>(binaryInteger);
43+
expectType<0x10>(hexadecimalInteger);
3044
expectType<never>(nonInteger);
3145
expectType<never>(infinityInteger);
46+
expectType<never>(infinityInteger2);
3247

3348
// Float
3449
declare const float: Float<1.5>;
35-
declare const floatMixed: Float<1 | 1.5>;
50+
declare const floatMixed: Float<1 | 1.5 | -1.5>;
3651
declare const nonFloat: Float<1>;
3752
declare const infinityFloat: Float<PositiveInfinity | NegativeInfinity>;
3853

3954
expectType<1.5>(float);
40-
expectType<1.5>(floatMixed);
55+
expectType<1.5 | -1.5>(floatMixed);
4156
expectType<never>(nonFloat);
42-
expectType<PositiveInfinity | NegativeInfinity>(infinityFloat); // According to Number.isInteger
57+
expectType<never>(infinityFloat);
4358

4459
// Negative
4560
declare const negative: Negative<-1 | -1n | 0 | 0n | 1 | 1n>;

0 commit comments

Comments
 (0)
Please sign in to comment.