question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Add a --strictNaNChecks option, and a NaN / integer / float type to avoid runtime NaN errors

See original GitHub issue

I have read the FAQ and looked for duplicate issues.

Search Terms

  • NaN
  • NaN type
  • Integer type

Related Issues

Suggestion

NaN has been a big source of errors in my code. I was under the impression that TypeScript (and Flow) could help to prevent these errors, but this is not really true.

TypeScript can prevent some NaN errors, because you cannot add a number to an object, for example. But there are many math operations that can return NaN. These NaN values often propagate through the code silently and crash in some random place that was expecting an integer or a float. It can be extremely difficult to backtrack through the code and try to figure out where the NaN came from.

I would like TypeScript to provide a better way of preventing runtime NaN errors, by ensuring that an unhandled NaN value cannot propagate throughout the code. This would be a compile-time check in TypeScript. Other solutions might be a run-time check added with a Babel plugin, or a way for JS engines to throw an error instead of returning NaN (but these are outside the scope of this issue.)

Use Cases / Examples

const testFunction = (a: number, b: number) => {
  if (a > b) {
    return;
  } else if (a < b) {
    return;
  } else if (a === b) {
    return;
  } else {
    throw new Error("Unreachable code");
  }
}

testFunction(1, 2);

testFunction(1, 0 / 0);

testFunction(1, Math.log(-1));

testFunction(1, Math.sqrt(-2));

testFunction(1, Math.pow(99999999, 99999999));

testFunction(1, parseFloat('string'));

A programmer might assume that the Unreachable code error could never be thrown, because the conditions appear to be exhaustive, and the types of a and b are number. It is very easy to forget that NaN breaks all the rules of comparison and equality checks.

It would be really helpful if TypeScript could warn about the possibility of NaN with a more fine-grained type system, so that the programmer was forced to handle these cases.

Possible Solutions

TypeScript could add a --strictNaNChecks option. To implement this, I think TS might need to add some more fine-grained number types that can be used to exclude NaN. The return types of built-in JavaScript functions and operations would be updated to show which functions can return NaN, and which ones can never return NaN. A call to !isNaN(a) would narrow down the type and remove the possibility of NaN.

Here are some possible types that would make this possible:

type integer
type float
type NaN
type Infinity

type number = integer | float | NaN | Infinity   // Backwards compatible
type realNumber = integer | float   // NaN and Infinity are not valid values

(I don’t know if realNumber is a good name, but hopefully it gets the point across.)

Here are some examples of what this new type system might look like:

const testFunction = (a: integer, b: integer) => {
  if (a > b || a < b || a === b) {
    return;
  } else {
    throw new Error("Unreachable code");
  }
}

// Ok
testFunction(1, 2);

// Type error. TypeScript knows that a division might produce a NaN or a float
testFunction(1, 0 / 0);

const a: integer = 1;
const b: integer = 0;

const c = a + b;  // inferred type is `integer`. Adding two integers cannot produce NaN or Infinity.
testFunction(1, c); // Ok

const d = a / b;   // inferred type is `number`, which includes NaN and Infinity.
testFunction(1, d); // Type error (number is not integer)

const e = -2;   // integer
const f = Math.sqrt(e);    // inferred type is: integer | float | NaN    (sqrt of an integer cannot return Infinity)

const g: number = 2; 
const h = Math.sqrt(g);    // inferred type is number (sqrt of Infinity is Infinity)

testFunction(1, h);  // Type error. `number` is not compatible with `integer`.

if (!isNaN(h)) {
  // The type of h has been narrowed down to integer | float | Infinity
  testFunction(1, h);  // Still a type error. integer | float | Infinity is not compatible with integer.
}

if (Number.isInteger(h)) {
  // The type of h has been narrowed down to integer
  testFunction(1, h);  // Ok
}

When the --strictNaNChecks option is disabled (default), then the integer and float types would also include NaN and Infinity:

type integer      // Integers plus NaN and Infinity
type float        // Floats plus NaN and Infinity

type number = integer | float    // Backwards compatible
type realNumber = number         // Just an alias, for forwards-compatibility.

I would personally be in favor of making this the default behavior, because NaN errors have caused me a lot of pain in the past. They even made me lose trust in the type system, because I didn’t realize that it was still possible to run into them. I would really love to prevent errors like this at compile-time:

screen shot 2018-11-27 at 4 35 34 pm

This error is from a fully-typed Flow app, although I’m switching to TypeScript for any future projects. It’s one of the very few crashes that I’ve seen in my app, but I just gave up because I have no idea where it was coming from. I actually thought it was a bug in Flow, but now I understand that type checking didn’t protect me against NaN errors. It would be really awesome if it did!

(Sorry for the Flow example, but this is a real-world example where a NaN type check would have saved me a huge amount of time.)

Number Literal Types

It would be annoying if you had to call isNaN() after every division. When the programmer calls a / 2, there is no need to warn about NaN (unless a is a number type that could potentially be NaN.) NaN is only possible for 0 / 0. So if either the dividend or the divisor are non-zero numbers, then the NaN type can be excluded in the return type. And actually zero can be excluded as well, if both dividend and divisor are non-zero.

Maybe this can be done with the Exclude conditional type? Something like:

type nonZeroNumber = Exclude<number, 0>
type nonZeroRealNumber = Exclude<realNumber, 0>
type nonZeroInteger = Exclude<integer, 0>
type nonZeroFloat = Exclude<float, 0>

If the dividend and divisor type both match nonZeroInteger, then the return type would be nonZeroFloat. So you could test any numeric literal types against these non-zero types. e.g.:

const a = 2;     // Type is numeric literal "2"

// "a" matches the "nonZeroInteger" type, so the return type is "nonZeroFloat" 
// (this excludes Infinity as well.)
// (Technically it could be "nonZeroInteger", or even "2" if TypeScript did 
// constant propagation. But that's way outside the scope of this issue.)
const b = 4 / a;

Checklist

My suggestion meets these guidelines:

  • This wouldn’t be a breaking change in existing TypeScript/JavaScript code
  • This wouldn’t change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:287
  • Comments:32 (6 by maintainers)

github_iconTop GitHub Comments

25reactions
devlatocommented, Oct 21, 2021

It would actually be great to have integer type. As discussed in #195 and #4639, it might help to force developers to do strict conversions and checks when you expect to get an integer value, eg. with math operations (as far as under the hood of JS engines, an internal conversion to integers and back takes place):

let x: integer = 1; // you expect its value to always be an integer
x = 5 / 2; // compile error
x = Math.floor(5 / 2); // ok
18reactions
MaxGraeycommented, Nov 11, 2019

It will be great also suggest replace x === NaN to Number.isNaN(x) or isNaN(x) during diagnostics because x === NaN always false and 100% mistake.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to Fix: ValueError: cannot convert float NaN to integer
This error occurs when you attempt to convert a column in a pandas DataFrame from a float to an integer, yet the column...
Read more >
Pandas: ValueError: cannot convert float NaN to integer
The "x" is a column in the csv file, I cannot spot any float NaN in the file, and I don't understand the...
Read more >
Thoughts on preventing divide-by-zero errors with the Crystal ...
This might be similar to an issue that I opened for TypeScript: Add a --strictNaNChecks option, and a NaN / integer / float...
Read more >
How to Fix: ValueError: cannot convert float NaN to integer
In Python, NaN stands for Not a Number. This error will occur when we are converting the dataframe column of the float type...
Read more >
Erp plot_topomap NaN to integer error - MNE Forum - Discourse
I thought it was an issue of the projections as 0 projections items are activated. I'm not sure. Not setting metadata Not setting...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found