-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Expand upon and improve reading of integers from JSON #35999
Expand upon and improve reading of integers from JSON #35999
Conversation
Primarily to expand support for very large integers. Before, precision was being lost due to conversion to float, and then back to integer. I also made integer loading more strict. An error will now be thrown if: 1. An unsigned integer is provided with a negative value. 2. The supplied integer is too big (we now have int64 support, but even that has its limits) 3. An integer is supplied with a decimal (previously it was truncated) 4. The above rules hold even when scientific notation is used (yes, we have support for using scientific notation in JSON fields) Number 3 immediately caused a lot of errors in both the base game and in mods. I went through and fixed these.
src/json.cpp
Outdated
error( "Found a number greater than " + std::to_string( INT_MAX ) + | ||
" which is unsupported in this context." ); | ||
} else if( n.negative && n.number > ( UINT_MAX - INT_MAX ) ) { | ||
error( "Found a number less than " + std::to_string( INT_MIN ) + |
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.
This reports INT_MIN
in the error message, but the test is against a different term. If it's guaranteed that both evaluate to the same number, why use different terms in the code? If the numbers might be different (UINT_MAX - INT_MAX
is not guaranteed to be the same as INT_MIN
), the error will report a different error condition than what is checked.
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.
UINT_MAX - INT_MAX
is guaranteed to evaluate to the absolute value of INT_MIN
. I can't do -INT_MIN
because of overflow, and I can't do -( UINT_MAX - INT_MAX )
because an unsigned int
can't be negative.
The only option I see to reconcile your issue with the type limitations, is to reframe the message like this:
error( "Found a number whose absolute value is more than " +
std::to_string( UINT_MAX - INT_MAX ) +
" which is unsupported in this context." )
Which I think is too convoluted to be reasonable, primarily due to the fact that it's not actually accurate -- if the number is positive, the absolute value limit is not UINT_MAX - INT_MAX
, it's just INT_MAX
.
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.
Never mind, Kevin enlightened me to numeric_limits<int>::min()
. Will fix this shortly.
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.
Err, nevermind again, I don't know the solution to this, and now I'm not even sure that this:
UINT_MAX - INT_MAX
is guaranteed to evaluate to the absolute value ofINT_MIN
.
Is accurate. Still looking for a solution.
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.
Okay, I think I've solved this, would appreciate a second look if you have time.
The way the absolute value of INT_MIN was being determined before may have given inconsistent results on different platforms. Also, switched to using std::numeric_limits rather than the corresponding macro constants.
I wanted to be absolutely sure that these functions never risked overflow at any point on any compiler, because I know that if at any point they return an incorrect value, saved games will fail to load due to incorrectly thinking it's unsafe to read INT_MIN from JSON. Also, Qrox pointed out that they should be constexpr.
Summary
SUMMARY: Infrastructure "Expand upon and improve reading of integers from JSON"
Purpose of change
This is primarily to expand support for very large numbers (because I need them to work properly and consistently for #35910). Before, we read numbers the following way:
neg
to true.int
, we'll call itnum
exp
by 1E
/e
to the counterexp
num * std::pow( 10.0, exp ) * ( neg ? -1.0 : 1.0 )
float
,int
,unsigned int
, orint64_t
.There were a lot of problems with this:
INT_MAX
would result in undefined behavior due to overflow, so requesting anint64_t
was functionally identical to requesting anint
, which similar pointlessness forunsigned int
.double
bystd::pow
, then back to their respective integer data type.uint64_t
hash.Describe the solution
Changed the above 6 steps as follows:
num
is now auint64_t
so it is big enough to handle every other data type.float
ordouble
was requested. Otherwise, the resultantnum
,neg
, andexp
variables are passed to dedicated functions that calculate the resultant integer in such a way that it is never cast to adouble
orfloat
in the process.uint64_t
.I also made number loading more strict. An error will now be thrown if:
Implementing number 3 immediately caused a lot of JSON load errors in both the base game and in mods. I went through and fixed these.
Testing
I've loaded into the game and checked all kinds of items to see if the numerical properties were properly read from their JSON definitions. I also tested #35910 to ensure that monotony penalties were carrying over after save/load, and they were, which implies that the new
uint64_t
hashes are being correctly and precisely (de)serialized.Additional context
Possibly, when this is merged, there might be some JSON load errors. It mostly will depend on whether anyone adds numbers with decimals to JSON properties that are integer-only. The most likely offender would be weight and/or volume entries.