5

I'm building an application with a react.js front-end (although I'm fairly sure that's not relevant to the issue) and an asp.net back-end (targeting net5.0, not sure if that's relevant). When I call to the back-end API, I get back an object that includes, in part, an ID it generated based on the data passed in, that is of type long in C# (a 64 bit int). The behavior that I'm seeing is that the variable in C# and the variable read from the response on the front end are different. They appear to drift after about 16-17 digits.

Is this expected? Is there any way to get around it?

Code to reproduce / Pictures of what I see:

C#

[HttpPost]
[Route("test")]
public object TestPassingLongInObject()
{
    /* actual logic omitted for brevity */

    var rv = new
    {
        DataReadIn = new { /* omitted */ },
        ValidationResult = new ValidationResult(),
        GeneratedID =  long.Parse($"922337203{new Random().Next(int.MaxValue):0000000000}") // close to long.MaxValue
    };

    Console.WriteLine($"ID in C# controller method: {rv.GeneratedID}");

    return rv;
}

Console output: ID in C# controller method: 9223372030653055062

Chrome Dev Tools:

Response seen by Chrome dev tools

When I try to access the ID on the front-end I get the incorrect ID ending in 000 instead of 062.

Edit 1: It's been suggested that this is because JavaScript's Number.MAX_SAFE_INTEGER is less than the value I'm passing. I don't believe that is the reason, but perhaps I'm wrong and someone can enlighten me. On the JS side, I'm using BigInt, precisely because the number I'm passing is too large for Number. The issue is before I'm parsing the result to a JS object though (unless Chrome is doing that automatically, leading to the picture referenced in the issue).

Edit 2: Based on the answers below, it looks like perhaps JS is parsing the value to a number before I'm parsing it to a BigInt, so I'm still losing precision. Can someone more familiar with the web confirm this?

Brian
  • 137
  • 1
  • 6
  • I think it is a limitation of Javascript or of the Json format, can you use a string instead of long? – Alessandro Albi Feb 24 '21 at 17:05
  • `long.Parse()` is about the slowest possible way to do this. Use `9223372030000000000 + rand.Next(int.MaxValue)` Also, re-use the Random instance (make it a static member). It's not just a weak performance optimization, but also ensures you don't accidentally use the same seed. – Joel Coehoorn Feb 24 '21 at 17:10
  • @JoelCoehoorn The actual logic was omitted. That's not actually how the ID is generated, simply a way to illustrate the issue. There is no Random() use in the real formulas. – Brian Feb 24 '21 at 18:27
  • @phuzi I edited my question to say that I'm using BigInt on the front-end, but maybe JS is still using Number? I'm not very familiar with web development yet. – Brian Feb 24 '21 at 18:27
  • If the response is being parsed to a JS object before you get chance to use BigInt then the number will already have been converted to a reglar JS number the precision will be reduced. You could debug the AJAX request/response to confirm this. – phuzi Feb 24 '21 at 19:38
  • The preview image you show above shows a preview of the response that has already been through the JSON.parse. This is not what gets received in the response to the AJAX request before being parsed. It's a red herring. – phuzi Feb 24 '21 at 19:40

4 Answers4

4

JavaScript is interpreting that value as a number which is a 64-bit floating-point number. If you want to keep that value, you're better off passing it back as a string.

Example JS to demonstrate the problem:

var value = 9223372030653055062;
console.log(value);
console.log(typeof(value));
DavidG
  • 113,891
  • 12
  • 217
  • 223
3

JavaScript engines will (apparently) fail to create numbers this big. Try to open your console and enter the following:

var myLong = 9223372030653055062;

console.log(myLong);

You can check 9223372030653055 > Number.MAX_SAFE_INTEGER as well, as a pointer that you're going out of bounds.

AndreasHassing
  • 687
  • 4
  • 19
1

The maximum size of a Number in Javascript is 2^53 - 1 which is 9007199254740991. In C# a long is 2^64 - 1, which is bigger. So "9223372030653055062" works as a C# long but not as a JavaScript Number because it is too big. If this ID is being used in a database using a long, I'd suggest you just pass it as a string to JavaScript.

GlennSills
  • 3,977
  • 26
  • 28
  • OP's number is 9223372030653055000 is considerably bigger than your stated maximum for `Number` at 9007199254740991 making the maximum size of `Number` much bigger that 2^53-1 – phuzi Feb 24 '21 at 19:53
0

Although the reason is that the number is bigger than JS can accurately represent, you seem to have been distracted be Chrome's Dev Tools Preview. This is only a "helpful" preview and not necessarily the truth.

The dev tools preview shows a helpful view of the response. I presume the AJAX response is transferred with content type of application/json, so Chrome helpfully parses the JSON to a JS object and lets you preview the response. Check the Response tab, this is what will actually be received by your AJAX code.

The chances are that the JSON is being parsed before you have chance to use BigInt on that number, capping the precision. You haven't shown us your AJAX code so this is my best guess.

The only solution would be to serialize it as a string then add a manual step to convert the string to BigInt.

jsObject.generatedId = BigInt(jsObject.generatedId);

If you're using it as an ID then you might as well keep it as a string on the client-side as you're not likely to be using it to do any calculations.

phuzi
  • 12,078
  • 3
  • 26
  • 50