Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kfarnung
Copy link
Contributor

Allow the caller to get the integer value for a given number back as a
64-bit integer value.

@kfarnung
Copy link
Contributor Author

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.

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);
Copy link
Contributor Author

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

@MSLaguana
Copy link
Contributor

Is this only guaranteed to be precise for 52 bit numbers?

@kfarnung
Copy link
Contributor Author

kfarnung commented Mar 14, 2018

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.

Copy link
Contributor

@boingoing boingoing left a 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

@kfarnung
Copy link
Contributor Author

@MSLaguana I updated the comment to call out the precision caveat.

@kfarnung kfarnung self-assigned this Mar 15, 2018
@kfarnung
Copy link
Contributor Author

@boingoing Can you take another look?

}
else if (Js::JavascriptNumber::Is_NoTaggedIntCheck(value))
{
*result = Js::JavascriptConversion::ToInt64(Js::JavascriptNumber::GetValue(value));
Copy link
Contributor

@MSLaguana MSLaguana Mar 15, 2018

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.

Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

@digitalinfinity digitalinfinity left a 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));
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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);

Copy link
Contributor

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?

@@ -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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: missing opening

tag

Copy link
Contributor Author

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.

Copy link
Contributor

@akroshg akroshg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@liminzhu
Copy link
Collaborator

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.

@kfarnung
Copy link
Contributor Author

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?

@liminzhu
Copy link
Collaborator

for Int64 the default behavior is to return a number indicating the invalid state

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.

@kfarnung kfarnung changed the base branch from release/1.9 to release/1.10 June 27, 2018 22:24
@kfarnung kfarnung changed the base branch from release/1.10 to master July 27, 2018 21:15
Allow the caller to get the integer value for a given number back as a
64-bit integer value.
@alexgwolff
Copy link

alexgwolff commented Apr 14, 2019

hey, what is the final decision on this PR? I need to convert a Int64 in .net environment.
thanks

@alexgwolff
Copy link

@kfarnung ??

@kfarnung
Copy link
Contributor Author

@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 (0x8000000000000000), but that's basically an architecture-specific magic number.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants