Skip to content

Commit eba9b1a

Browse files
committed
NaN's most significant bit signifies the sign of NaN. This bit's value was inconsistent with other engines' as well as inconsistent in Chakra when using the -NoNative flag. This fix makes NaN respect the sign bit. This is viewable through typed array buffers.
1 parent f9c1970 commit eba9b1a

13 files changed

+90
-14
lines changed

lib/Common/Common/NumberUtilities.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ using namespace Js;
1717
// Redeclare static constants
1818
const UINT64 NumberConstantsBase::k_Nan;
1919
const UINT32 NumberConstantsBase::k_Nan32;
20+
const UINT64 NumberConstantsBase::k_NegativeNan;
2021
const INT64 NumberUtilitiesBase::Pos_InvalidInt64;
2122
const INT64 NumberUtilitiesBase::Neg_InvalidInt64;
2223
const uint64 NumberConstants::k_PosInf;
@@ -59,6 +60,7 @@ using namespace Js;
5960
const double NumberConstants::MAX_VALUE = *(double*)(&NumberConstants::k_PosMax);
6061
const double NumberConstants::MIN_VALUE = *(double*)(&NumberConstants::k_PosMin);
6162
const double NumberConstants::NaN = *(double*)(&NumberConstants::k_Nan);
63+
const double NumberConstants::NegativeNaN = *(double*)(&NumberConstants::k_NegativeNan);
6264
const double NumberConstants::NEGATIVE_INFINITY= *(double*)(&NumberConstants::k_NegInf);
6365
const double NumberConstants::POSITIVE_INFINITY= *(double*)(&NumberConstants::k_PosInf );
6466
const double NumberConstants::NEG_ZERO= *(double*)(&NumberConstants::k_NegZero );

lib/Common/Common/NumberUtilities.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ namespace Js
6464
static const double MAX_VALUE;
6565
static const double MIN_VALUE;
6666
static const double NaN;
67+
static const double NegativeNaN;
6768
static const double NEGATIVE_INFINITY;
6869
static const double POSITIVE_INFINITY;
6970
static const double NEG_ZERO;
@@ -99,6 +100,7 @@ namespace Js
99100

100101
static bool IsFinite(double value);
101102
static bool IsNan(double value);
103+
static bool IsNegative(double value);
102104
static bool IsFloat32NegZero(float value);
103105
static bool IsSpecial(double value, uint64 nSpecial);
104106
static uint64 ToSpecial(double value);

lib/Common/Common/NumberUtilities.inl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ namespace Js
137137
NUMBER_UTIL_INLINE bool NumberUtilities::IsNan(double value)
138138
{
139139
#if defined(TARGET_64)
140-
// NaN is a range of values; all bits on the exponent are 1's and some nonzero significant.
141-
// no distinction on signed NaN's
140+
// NaN is a range of values; all bits on the exponent are 1's
141+
// and some nonzero significant. No distinction on signed NaN's.
142142
uint64 nCompare = ToSpecial(value);
143143
bool isNan = (0 == (~nCompare & 0x7FF0000000000000ull) &&
144144
0 != (nCompare & 0x000FFFFFFFFFFFFFull));
@@ -149,6 +149,12 @@ namespace Js
149149
#endif
150150
}
151151

152+
NUMBER_UTIL_INLINE bool NumberUtilities::IsNegative(double value)
153+
{
154+
uint64 nCompare = ToSpecial(value);
155+
return nCompare & 0x8000000000000000ull;
156+
}
157+
152158
NUMBER_UTIL_INLINE bool NumberUtilities::IsSpecial(double value, uint64 nSpecial)
153159
{
154160
// Perform a bitwise comparison using uint64 instead of a double comparison, since that

lib/Common/Common/NumberUtilitiesBase.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ namespace Js
1212
class NumberConstantsBase
1313
{
1414
public:
15-
static const UINT64 k_Nan = 0xFFF8000000000000ull;
15+
static const UINT64 k_Nan = 0x7FF8000000000000ull;
1616
static const UINT32 k_Nan32 = 0x7FC00000ul;
17+
static const UINT64 k_NegativeNan = 0xFFF8000000000000ull;
1718
};
1819

1920
class NumberUtilitiesBase

lib/Runtime/Language/JavascriptConversion.inl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,11 @@ namespace Js {
297297
#if FLOATVAR
298298
if (typeId == TypeIds_Number)
299299
{
300-
// NaN could have sign bit set, but that isn't observable so canonicalize to positive NaN
301300
double numberValue = JavascriptNumber::GetValue(value);
302301
return JavascriptNumber::IsNan(numberValue)
303-
? JavascriptNumber::ToVar(JavascriptNumber::NaN)
302+
? JavascriptNumber::IsNegative(numberValue)
303+
? JavascriptNumber::ToVar(JavascriptNumber::NegativeNaN)
304+
: JavascriptNumber::ToVar(JavascriptNumber::NaN)
304305
: value;
305306
}
306307
#else

lib/Runtime/Library/JavascriptNumber.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace Js
2323
#if FLOATVAR
2424
if (IsNan(value))
2525
{
26-
value = JavascriptNumber::NaN;
26+
value = IsNegative(value) ? JavascriptNumber::NegativeNaN : JavascriptNumber::NaN;
2727
}
2828
#endif
2929
return JavascriptNumber::NewInlined(value, scriptContext);

lib/Runtime/Library/JavascriptNumber.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ namespace Js
5555
static bool TryToVarFastWithCheck(double value, Var* result);
5656

5757
inline static BOOL IsNan(double value) { return NumberUtilities::IsNan(value); }
58+
inline static BOOL IsNegative(double value) { return NumberUtilities::IsNegative(value); }
5859
static bool IsZero(double value);
5960
static BOOL IsNegZero(double value);
6061
static bool IsPosInf(double value);

lib/Runtime/Library/JavascriptNumber.inl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Js
1414
#endif
1515
)
1616
{
17-
AssertMsg(!IsNan(value) || ToSpecial(value) == k_Nan || ToSpecial(value) == 0x7FF8000000000000ull, "We should only produce a NaN with this value");
17+
AssertMsg(!IsNan(value) || ToSpecial(value) == k_NegativeNan || ToSpecial(value) == 0x7FF8000000000000ull, "We should only produce a NaN with this value");
1818
SetSpecial(ToSpecial(value) ^ FloatTag_Value);
1919
}
2020
#else
@@ -96,7 +96,7 @@ namespace Js
9696
#if FLOATVAR
9797
if (IsNan(value))
9898
{
99-
value = JavascriptNumber::NaN;
99+
value = IsNegative(value) ? JavascriptNumber::NegativeNaN : JavascriptNumber::NaN;
100100
}
101101

102102
*result = JavascriptNumber::ToVar(value);
@@ -128,7 +128,7 @@ namespace Js
128128
{
129129
if (IsNan(value))
130130
{
131-
value = JavascriptNumber::NaN;
131+
value = IsNegative(value) ? JavascriptNumber::NegativeNaN : JavascriptNumber::NaN;
132132
}
133133
return ToVar(value);
134134
}
@@ -148,7 +148,7 @@ namespace Js
148148
inline Var JavascriptNumber::ToVar(double value)
149149
{
150150
uint64 val = *(uint64*)&value;
151-
AssertMsg(!IsNan(value) || ToSpecial(value) == k_Nan || ToSpecial(value) == 0x7FF8000000000000ull, "We should only produce a NaN with this value");
151+
AssertMsg(!IsNan(value) || ToSpecial(value) == k_NegativeNan || ToSpecial(value) == 0x7FF8000000000000ull, "We should only produce a NaN with this value");
152152
return reinterpret_cast<Var>(val ^ FloatTag_Value);
153153
}
154154

test/Number/NegativeNaN.baseline

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
0,0,0,0,0,0,248,127
2+
0,0,0,0,0,0,248,255
3+
0,0,192,127
4+
0,0,192,255
5+
7ff8000000000000
6+
fff8000000000000
7+
0,0,0,0,0,0,248,127
8+
0,0,0,0,0,0,248,255
9+
0,0,192,127
10+
0,0,192,255
11+
7ff8000000000000
12+
fff8000000000000

test/Number/NegativeNaN.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
6+
function f(){
7+
f64 = new Float64Array([NaN]);
8+
u8 = new Uint8Array(f64.buffer);
9+
print(u8)
10+
11+
f64 = new Float64Array([-NaN]);
12+
u8 = new Uint8Array(f64.buffer);
13+
print(u8)
14+
15+
f64 = new Float32Array([NaN]);
16+
u8 = new Uint8Array(f64.buffer);
17+
print(u8)
18+
19+
f64 = new Float32Array([-NaN]);
20+
u8 = new Uint8Array(f64.buffer);
21+
print(u8)
22+
23+
// GitHub bug #398
24+
function numberToRawBits(v) {
25+
var isLittleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
26+
var reduce = Array.prototype[isLittleEndian ? 'reduceRight' : 'reduce'];
27+
var uint8 = new Uint8Array(new Float64Array([v]).buffer);
28+
return reduce.call(uint8, (a, v) => a + (v < 16 ? "0" : "") + v.toString(16), "");
29+
}
30+
31+
console.log(numberToRawBits(NaN));
32+
console.log(numberToRawBits(-NaN));
33+
34+
}
35+
f()
36+
f()
37+

0 commit comments

Comments
 (0)