According to the ECMAScript specification, Javascript number values correspond a to double-precision 64-bit binary format IEEE 754 value.
For a WebIDL validator I'm currently working on, I need to be able to figure out if a given number value can be converted to a WebIDL float type, i.e. if it can be represented as a finite single precision 32-bit IEEE 754 value.
I've currently settled on the following approach:
validate: function(value) {
if (typeof value !== 'number' || !Number.isFinite(value)) {
return false;
}
if (value === 0) {
return true;
}
var view = new DataView(new ArrayBuffer(4));
view.setFloat32(0, value);
var converted = view.getFloat32(0);
var relativeError = Math.abs(value - converted) / value;
return relativeError < Number.EPSILON;
}
In essence, what I'm doing is:
- Wrap a
DataView
around a 4 byteArrayBuffer
. - Store the
Number
value as a 32 bit float in the buffer. - Get the converted number back out of the buffer.
- Calculate the relative error between the original value and the converted value.
- Check whether the relative error is smaller than the machine epsilon for floating point numbers, using
Number.EPSILON
.
A couple of comments:
- I'm implementing this as part of a Chrome extension, browser compatibility is not really an issue.
- Yes, I have read What Every Computer Scientist Should Know About Floating-Point Arithmetic. My eyes have yet to stop bleeding.
Is the above logic sound for what I'm trying to achieve? Is it overkill? Is there a more idiomatic, elegant or performant way to do this?
Update
I have in the meantime figured out that the above approach is incorrect. It will fail for a wide range of values such as 1.001
, 3.14159
, and so on. Changing the epsilon value to the machine epsilon for 32-bit floats (2−23) is on the other hand too permissive and allows values like 16777217
.
Still looking for a good solution, but currently going with the function below that just checks the integer upper and lower bounds (224 and -(224)).
validate: function(value) {
return typeof value === 'number' && Number.isFinite(value)
&& value >= -16777216 && value <= 16777216;
}
As you're using ECMAScript 6, you can use
Math.fround
to check if it's a single-precision number:UPDATE: I missed the part about a WebIDL float being finite (i.e. not an Inf or NaN). In that case,