New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add JsNumberToInt64
function
#4827
base: master
Are you sure you want to change the base?
Conversation
I had written this up when trying to fix a bug in node-chakracore, but ended up just using a static cast from double to int64_t instead. Looking to see if other people think this would be useful. |
lib/Jsrt/Jsrt.cpp
Outdated
CHAKRA_API JsNumberToInt64(_In_ JsValueRef value, _Out_ int64_t *result) | ||
{ | ||
return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode { | ||
PERFORM_JSRT_TTD_RECORD_ACTION_NOT_IMPLEMENTED(scriptContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should fix this before landing this PR
Is this only guaranteed to be precise for 52 bit numbers? |
It's still subject to the significant bit limits of the IEEE754 double-precision numbers. In this case it's 53-bits of integer precision (technically 54 if you count the sign bit). I can add that remark to the documentation if there's agreement that the function is useful. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems alright to me
@MSLaguana I updated the comment to call out the precision caveat. |
@boingoing Can you take another look? |
} | ||
else if (Js::JavascriptNumber::Is_NoTaggedIntCheck(value)) | ||
{ | ||
*result = Js::JavascriptConversion::ToInt64(Js::JavascriptNumber::GetValue(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this case apply to Number
objects, e.g. the result of new Number(1)
? If so, this conversion can be re-entrant into script via a custom valueOf
I think? If so, then you can't guarantee that there won't be an exception here, since the script code can do anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this is explicitly checking for the tagged number case/JavascriptNumber case. Number objects are JavascriptNumberObjects. That being said, should we be checking for JavascriptNumberObject here? It looks like the existing implementations of JsNumberTo* don't handle Number objects, they likely require the caller to call JsConvertValueToNumber first.
@@ -1256,6 +1256,11 @@ namespace Js | |||
return JavascriptMath::ToInt32Core(T1); | |||
} | |||
|
|||
__int64 JavascriptConversion::ToInt64(double T1) | |||
{ | |||
return JavascriptMath::TryToInt64(T1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should we do if NumberUtilities::IsValidTryToInt64
is false for the result of this call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like TryToInt64 would handle this case? In the case of overflows/Nans, it would just return canonical values? We should however document it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TryToInt64
doesn't do anything special for x64 builds, it's simply a cast from double
to int64_t
. There are special paths for x86 (which can use SSE3) and ARM (which has different behavior), but in the base case it's just a cast.
For ToInt32
it ends up normalizing all of these cases down to zero without returning an error, but I'm not sure if that's spec'd anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally LGTM
} | ||
else if (Js::JavascriptNumber::Is_NoTaggedIntCheck(value)) | ||
{ | ||
*result = Js::JavascriptConversion::ToInt64(Js::JavascriptNumber::GetValue(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this is explicitly checking for the tagged number case/JavascriptNumber case. Number objects are JavascriptNumberObjects. That being said, should we be checking for JavascriptNumberObject here? It looks like the existing implementations of JsNumberTo* don't handle Number objects, they likely require the caller to call JsConvertValueToNumber first.
@@ -1256,6 +1256,11 @@ namespace Js | |||
return JavascriptMath::ToInt32Core(T1); | |||
} | |||
|
|||
__int64 JavascriptConversion::ToInt64(double T1) | |||
{ | |||
return JavascriptMath::TryToInt64(T1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like TryToInt64 would handle this case? In the case of overflows/Nans, it would just return canonical values? We should however document it
// Number.MIN_SAFE_INTEGER | ||
REQUIRE(JsDoubleToNumber(-9007199254740991, &value) == JsNoError); | ||
REQUIRE(JsNumberToInt(value, &intValue) == JsNoError); | ||
CHECK(1 == intValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mild preference for defining Pos_InvalidInt64/Neg_InvalidInt64 as JSRT constants, and compare against that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case it's not actually invalid, it's the result of overflowing the int32, I'm still trying to figure out what to do for truly invalid numbers that exceed the bounds of int64, those end up returning 0x8000000000000000
on x86/x64, but that doesn't seem to be 100% consistent across architectures.
REQUIRE(JsNumberToInt(value, &intValue) == JsNoError); | ||
CHECK(1 == intValue); | ||
REQUIRE(JsNumberToInt64(value, &int64Value) == JsNoError); | ||
CHECK(-9007199254740991ll == int64Value); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some test cases for Nan and overflow too?
lib/Jsrt/ChakraCore.h
Outdated
@@ -1063,5 +1063,22 @@ CHAKRA_API | |||
_Out_opt_ JsValueRef* target, | |||
_Out_opt_ JsValueRef* handler); | |||
|
|||
/// Retrieves the <c>int64_t</c> value of a number value. | |||
/// </summary> | |||
/// <remarks> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: missing opening
tag
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I must have dropped it when I was rebasing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just saw this PR. Btw there's a new primitive BigInt proposal in TC39 (stage 3 already, pretty close to standardized), which allows representing int64 in JS. It would complicate things a bit if this API also takes BigInt. |
There are some open questions about how this API should handle numbers too large to represent even without considering BigInt. For Int32 we seem to just wrap back around, but for Int64 the default behavior is to return a number indicating the invalid state (some CPUs do this by returning 0x8000000000000000 and we appear to use that ourselves). I suspect the same sort of issues will occur with BigInt, except that 0x8000000000000000 will be possible to represent exactly with a BitInt when it's not possible to do so using a double. Any thoughts about what the API should do in cases where the number can't be represented in an Int64 value? |
It would be good to study some prior art. Can you point me to what language/platform use this as a default behavior? I was thinking more along the lines of returning a overflow error code, since you'd be getting that from .NET if you try to convert a bigint too large to int64. I looked a bit more into BigInt and it looks like the proposal champion wants to set a clear boundary between BigInt and Number by having them as separate primitives and disallowing implicit conversions and ops mix-up, which is an approach I can agree with. This also makes me feel that if we were to expose BigInt natively, we want a different API, and b/c there is no builtin in C corresponding directly to bigint, we'll have to think about that later. |
Allow the caller to get the integer value for a given number back as a 64-bit integer value.
hey, what is the final decision on this PR? I need to convert a Int64 in .net environment. |
@kfarnung ?? |
@alexgwolff technically you can just convert the number to a double and then cast to a 64-bit integer. Since JavaScript numbers are all IEEE754, that's essentially what this code does internally. The problem was that we couldn't find agreement on how to represent invalid conversions, that behavior isn't defined by the standard and V8 seemed to use the value that the base conversion returns ( |
Allow the caller to get the integer value for a given number back as a
64-bit integer value.