-
Notifications
You must be signed in to change notification settings - Fork 106
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
[Compiler] Limitations regarding JavaScript's number type #934
Comments
Thank you for the write-up! Initial thoughts, might totally change my mind later 😁 I'm afraid of the float conversion change. It would be alright behind a feature flag but I would say it should be off by default..? I don't know what's more likely, floating point errors or accidental casts. But I ca see people wanting both and being stuck :/ The bytecode change suggestion is very interesting as well. The bytecode generation mode is a little dangerous for the ink ecosystem I think, but probably acceptable. I would lean towards this solution. I do wonder however — is this maybe a change that could be integrated upstream @joethephish ? Hypothetically we could prototype the solution in inkjs before porting it upstream. |
I’d definitely be up for changing the “byte code” JSON format so that we can keep both perfectly aligned. It would be a breaking change of course, but it’s probably worth it in the long term. I don’t really mind if we have a different format for integers or floats. I guess the options are |
What I suggest is to only transform misleading (f%1==0) float in "12.0f" notation and make a small PR on the C# codebase to read and write them accordingly. This would only break new generated code running on old engine if round float occur in the source code. |
Oh I see, that’s pretty elegant, I like that. |
Wouldn't using using two different encodings at the same time, for the same type ( |
Confusing for whom ? People reading the raw bytecode ? That'd be just me 🤣 The argument in favour of switching to string notation for all floats is that it would break consistantly the old engine instead of possibly breaking it seemingly randomly after weeks when the author starts tweaking values and suddenly use a rounded float. |
Haha, yeah, that wouldn't change anything for the end-user for sure! I'm only bringing this up because those sort of decisions aren't small and they can have unforeseen consequences in the future.
That's a good point. I can't remember, but wouldn't old engines complain anyway if we were to bump up the format version? I'm a bit out of my depth when it comes to the JSON bytecode, to be honest. I'm glad @joethephish is up for changing the format in upstream, though, because that means we can figure out a solution that's suitable for inkle as well. |
Yeah we’d definitely have to bump the version anyway. So it probably makes sense to make the format the best version of itself, compromise-free as much as possible. Though it’s hardly a format that’s ever really meant to be read, except when debugging TBH, so it’s not a huge deal whichever one we choose. It’s always been ugly, always will be 😁 |
I'm looking at trying out Ink for a web-based project and noticed it's been some time since any work or discussion has occurred on this issue. Is there a current "best practice" for avoiding this problem, other than the string replacement trick noted above? And is there any potential timeline on fixes? I would be willing to contribute, but I've never worked with C# so I'd only easily be able to make contributions to inkjs specifically. |
@lambdadog Unless you have very specific casting needs and rely on very strict typing of numbers, you should be safe to use the current version. In case anything should happen to you in that regard, come here and we'll help for sure ! |
In #931 @smwhr introduced a new mechanism to fix a long standing issue that inkjs has with number divisions.
I think it's a great idea that can be further refined and I want to discuss the options we have.
Background
Unlike C#, Javascript only have one floating-point
number
type that is used to represent both floats and integers. This means that1.0
and1
are the same thing and JavaScript cannot perform integer divisions.The same rules apply to JSON,
1.0
can be serialised as1
in order to optimise the file size, but the format makes no distinction between integers and floating-point numbers. The reference implementation circumvents this limitation by implementing its own JSON (de)serialiser. Integers are serialised without a fractional part (1
) while floats use at least one significant digit (1.0
).Since inkjs uses the built-in JSON parser, it cannot import
1.0
as a floating-point value (becauseNumber.isInteger(1.0) == true
) and that causes some discrepancies in the runtime. For instance both{ 7 / 3 }
and{ 7 / 3.0 }
evaluate to2
, because all numbers are interpreted as integers by the runtimes. In the original implementation{ 7 / 3 }
would evaluate to2
while{ 7 / 3.0 }
would evaluate to2.33333333
.Since version 1.11.0, inkjs uses a custom JSON writer, which is a thin wrapper over the built-in serialiser (see #363 and #493 for more information) but we've been historically reluctant to implement our own JSON (de)serialisers. After all, JSON stands for JavaScript Object Notation. 🤷🏻♂️
Possible Solutions
Custom (de)serialiser
Implement a custom (de)serialiser that reads / writes integers and floats as different types.
This would require creating temporary structures to hold the values and retain the type information before they can be finally converted to
IntValue
orFloatValue
.This solution isn't satisfactory for the reasons stated above.
Float conversion
Wrap floating-point numbers in a structure that retains the type information before parsing the JSON content.
This is the approach used by @smwhr; floating-point numbers are detected before parsing the container hierarchy and converted to a string value of the form:
"x.xf"
, with x being either a number or a digit. During the container creation, the value is converted to aFloatValue
.For more information, see here and there.
This solution has two minor pitfalls:
As result, a feature toggle is required. Users need the ability to disable the float recognition routine if it ends up conflicting with the content of their story.
Bytecode changes
Tweak the bytecode format to encode floats and integers in a different, unambiguous format.
The compiler has access to the full context and could ensure that only valid numbers are encoded. Like the above solution, number types would be converted during the creation of the story.
Since the native JavaScript numbers are floating-point numbers, I would be inclined to encode integers instead of floats. The lines of text are already prefixed by a caret (to differentiate them from control commands) and integers could be prefixed with another symbol.
This approach has a major downside. It requires changing the bytecode and the save format in a way that makes them incompatible with upstream. To mitigate potential issues, we could introduce two compilation modes:
On the upside, however, it doesn't require setting a flag on the runtime. The bytecode itself defines the runtime mode.
The changes to the compiler and the runtime engine would be minimal and I if well documented, I don't believe they would introduce a huge maintenance burden.
Let me know what y'all think!
The text was updated successfully, but these errors were encountered: