weather: Fix incorrect rounding for negative temperatures - #2194
weather: Fix incorrect rounding for negative temperatures#2194FintasticMan wants to merge 2 commits into
Conversation
|
Build size and comparison to main:
|
|
Would it be worth extracting out rounding integer division to some shared code? It's somewhat annoying that this isn't part of the C++ stdlib really :( Maybe an implementation similar to this? https://github.com/lucianpls/Rounding-Integer-Division |
|
What do you think of this? I've made a RoundedDiv function in the Utility namespace, that uses C++20 concepts so that we can have keep the code type-agnostic, while making sure that it only works for expected values. Didn't you also use a rounded division somewhere in the AoD code? Would this solution also work there? |
|
Wow that looks great. I didn't know that concepts allow this, I should read up on it. Yes I think this would work for AOD, I guess that would go in a separate PR though? I want to double check correctness properly before approving, but otherwise LGTM |
mark9064
left a comment
There was a problem hiding this comment.
I think this doesn't work as C++ demotes the dividend from signed to unsigned when the divisor is unsigned:
Suppose the following
int a;
// -1 / 5, rounded
a = (-1 + (-1 >= 0 ? 5 : -5) / 2) / 5;
// a = 0 as expected
a = (-1 + (-1 >= 0 ? 5U : -5U) / 2) / 5U;
// uh oh, a = 429496728
// more simply
a = -1 / 2u;
// a = 2147483647I didn't even know this was a thing??
|
Oh yeah of course. Integer promotion rules are really stupid. This conversion to unsigned also only happens when the values are |
of course... 😅 I love the C++ spec too |
|
I was curious about this piece of c++ code so I tried to check how it works, but It turned out it doesn't worked as I expected: It looks like, you cannot use unsigned (i.e. 100u) as divisor, because My knowledge of modern c++ is limited. It started to work when I have replaced and used 100 instead of 100u in those two lines |
Make weather service use it.
4a13235 to
635aa0c
Compare
|
@vkareh thanks for taking a look at this! The whole expression gets "promoted" to an unsigned integer if any of the terms are unsigned (and an |
| // returns the arcsin of `arg`. asin(-32767) = -90, asin(32767) = 90 | ||
| int16_t Asin(int16_t arg); | ||
|
|
||
| static constexpr auto RoundedDiv(std::integral auto dividend, std::integral auto divisor) -> decltype(dividend / divisor) { |
There was a problem hiding this comment.
I think we should note the overflow properties in a docstring. abs(dividend) + abs(divisor) / 2 must be less than the type maximum I think? Also I think it would be useful to note whether both the dividend and divisor can be negative
| // returns the arcsin of `arg`. asin(-32767) = -90, asin(32767) = 90 | ||
| int16_t Asin(int16_t arg); | ||
|
|
||
| static constexpr auto RoundedDiv(std::integral auto dividend, std::integral auto divisor) -> decltype(dividend / divisor) { |
There was a problem hiding this comment.
I think adding a docstring/comment what we expect from this function would be helpful. And as @mark9064 said what are the constraints on the inputs (if any)
There was a problem hiding this comment.
I'm all for ? operators but where things are strictly math based...might as well write using strictly math.
int16_t fixed_rounding(int16_t value, int16_t divisor) {
int16_t signed_value = ((uint16_t)value >> 14u) & 0x2; //if signed 2 otherwise 0
int16_t half_divisor = (divisor / 2);
//add a half, subtract divisor conditionally on sign
return (value + half_divisor - half_divisor*signed_value) / divisor;
}
//same as above except we don't calculate divisor*
int16_t fixed_rounding_b(int16_t value, int16_t divisor) {
int16_t signed_value = (uint16_t)value >> 15u; //if signed 1 otherwise 0
int16_t half_divisor = (divisor / 2);
return (value + half_divisor - divisor * signed_value) / divisor;
}There was a problem hiding this comment.
Or...if you trust bithacky madness, no * needed.
int16_t fixed_rounding(int16_t value, int16_t divisor) {
int16_t signed_value = value >> 15u; // true evil: we use >>'s signed behavior to propagate the leading 1 across all bits, eg: we have 0xffff or 0x0000
int16_t half_divisor = (divisor / 2);
return (value + half_divisor - (divisor & signed_value)) / divisor; //we replace the * by "1" with an &
}|
How about these? https://github.com/lucianpls/rounding_integer_division (LOL, just realised I proposed this right at the start and then forgot about it, sorry for the double post) // Round half toward zero integer division
// If T signed, divisor cannot be std::numeric_limits<T>::min()
// Adapted from https://github.com/lucianpls/rounding_integer_division
// Under the MIT license
template<std::integral T>
T RoundedDiv(T dividend, T divisor) {
bool neg = divisor < 0;
if (neg) {
// overflows if divisor is minimum value for T
divisor = -divisor;
}
T m = dividend % divisor;
T h = divisor / 2;
T res = dividend / divisor + (!(dividend < 0) & (m > h)) - ((dividend < 0) & ((m + h) < 0));
if (neg) {
res = -res;
}
return res;
}Should work for everything other than dividing with the negative type minimum, which would be a totally fine edge case IMO |
|
Closing since #2390 merged :) |
Thanks @tokiwee for reporting this issue!
Closes #2185 (if GitHub un-deletes it).