Conversation
|
BTW: With |
|
OK, I was wrong that there is no obvious way of how to round negative numbers. This PR implements rounding away from zero. E.g. |
|
I just updated the documentation to explicitly state the rounding strategy, so that people expecting other rounding strategies are at least warned. Update: I fixed the whitespace error I just introduced and squashed |
|
Just for reference the most popular rounding strategies with some examples:
So the strategies only differ when the result is exactly between two integer values. |
33217d0 to
c3a8ed2
Compare
|
Sorry, one more change: I change the input type from |
Btw: Maybe assertions could be turned on by default in the unit tests? |
96205db to
7bd6278
Compare
|
@gebart: ping :-) |
|
Ping? |
|
@miri64: @gebart seems to be to busy to review this PR. Do you have someone else in mind who could review this? I want to use this API in another PR. Therefore that PR is put on hold until this PR is decided on. |
|
In general the change looks sensible to me. Maybe @haukepetersen can have a look. However, @gebart originally provided this function, so if possible I'd like to have his opinion on this change as well (that's why I originally asked for his review). |
jnohlgard
left a comment
There was a problem hiding this comment.
There is an awful lot of division going on here, but I guess the original implementation wasn't very optimized either.
See my comments inline. The behaviour seems broken for -32768 and +32767, as they are supposed to fit inside an int16_t.
sys/include/phydat.h
Outdated
| * [@ref PHYDAT_MIN, @ref PHYDAT_MAX], and updates the stored scale factor. | ||
| * The value is rounded towards infinity, e.g. `0.5` and `0.6` are rounded to | ||
| * `1`, `0.4` and `-0.4` are rounded to `0`, `-0.5` and `-0.6` are rounded to | ||
| * `-1`. |
There was a problem hiding this comment.
Towards infinity (+infinity) means that 0.1 would be rounded to 1, -0.9 would be rounded to 0. This is rounding towards nearest integer (round half away from zero).
There was a problem hiding this comment.
I see your point, this name is indeed misleading. Wikipedia refers to it as round half away from zero in addition to "towards infinity". Typing it into some internet search engine also shows that "rounding away from zero" is the more widely used term then "towards infinity".
| { .val = { 0, 0, 12346 }, .unit = UNIT_M, .scale = 7 }, | ||
| { .val = { 3277, 3277, 3277 }, .unit = UNIT_NONE, .scale = 1 }, | ||
| { .val = { 32766, 32766, 32766 }, .unit = UNIT_NONE, .scale = 0 }, | ||
| { .val = { -3277, -3277, -3277 }, .unit = UNIT_NONE, .scale = 1 }, |
There was a problem hiding this comment.
Wrong behaviour IMO.
-32767 fits inside phydat_t::dat
Also, please add tests for -32768 (expect -32768), -32769 (expect -3277, scale +1), 32767 (expect 32767), 32768 (expect 3277, scale 1)
There was a problem hiding this comment.
This was actually a deliberate implementation decision for two reasons:
- Both positive and negative numbers can be treated identical if treating
-32768as out of range - Special handling of the scale estimation can be skipped if treating
32767(and thus also-32767) as out of range.
The special case for scale estimation is when the number is 32767 and has to be rounded up. By treating only [-32766, 32766] as target range no special treatment is required, which makes the code shorter, the ROM smaller and faster. Also, in 99.99542% of random numbers the result is the some, in the remaining 0.00458% of the cases some precision is lost.
But let me think about it. Maybe there is an elegant solution to use the full range.
There was a problem hiding this comment.
Good arguments. It makes sense to introduce this limitation to save on code size and simplify the implementation if a rationale is clearly described in the module documentation.
|
I agree the API usage was a bit hard to read before. This seem better in that regard. |
jnohlgard
left a comment
There was a problem hiding this comment.
Forgot to set request changes.
|
@gebart: I basically rewrote the Additionally, by adding |
|
@gebart: Could you please re-review? Thanks :-) |
jnohlgard
left a comment
There was a problem hiding this comment.
The change looks good. Tested working on native and frdm-kw41z.
This small diff saves a few hundred bytes of ROM on armv6 by using unsigned division instead of signed.
diff --git a/sys/phydat/phydat.c b/sys/phydat/phydat.c
index 245d8f89d9..56b37d7d59 100644
--- a/sys/phydat/phydat.c
+++ b/sys/phydat/phydat.c
@@ -48,7 +48,7 @@ static const int32_t lookup_table_negative[] = {
};
#endif
-static const int32_t divisors[] = {
+static const uint32_t divisors[] = {
100000,
10000,
1000,
@@ -61,7 +61,7 @@ static const int32_t divisors[] = {
void phydat_fit(phydat_t *dat, const int32_t *values, unsigned int dim)
{
assert(dim <= (sizeof(dat->val) / sizeof(dat->val[0])));
- int32_t divisor = 0;
+ uint32_t divisor = 0;
int32_t max = 0;
const int32_t *lookup = lookup_table_positive;
@@ -104,17 +104,17 @@ void phydat_fit(phydat_t *dat, const int32_t *values, unsigned int dim)
}
/* Applying scale and add half of the divisor for correct rounding */
- long divisor_half = divisor >> 1;
+ uint32_t divisor_half = divisor >> 1;
for (unsigned int i = 0; i < dim; i++) {
if (values[i] >= 0) {
- dat->val[i] = (values[i] + divisor_half) / divisor;
+ dat->val[i] = (uint32_t)(values[i] + divisor_half) / divisor;
}
else {
/* For negative integers the C standards seems to lack information
* on whether to round down or towards zero. So using positive
* integer division as last resort here.
*/
- dat->val[i] = -(((-values[i]) + divisor_half) / divisor);
+ dat->val[i] = -((uint32_t)((-values[i]) + divisor_half) / divisor);
}
}
}
sys/include/phydat.h
Outdated
| * @param[in] value value to rescale | ||
| * @param[in] index place the value at this position in the phydat_t::val array | ||
| * @param[in] prescale start by scaling the value by this exponent | ||
| * @warning The scale member in @p dat has to be initialized by the caller prior |
There was a problem hiding this comment.
you can use @pre for documenting preconditions and assumptions regarding parameters.
Optional extra: I think doxygen will generate a correct hyperlink if you write @ref phydat_t::scale instead of just scale
| * be used for both positive and negative values. As result, -32768 will be | ||
| * considered as out of range and scaled down. So statistically in 0.00153% | ||
| * of the cases an unneeded scaling is performed, when | ||
| * PHYDAT_FIT_TRADE_PRECISION_FOR_ROM is true. |
sys/phydat/phydat.c
Outdated
| 10, | ||
| }; | ||
|
|
||
| #define LOOKUP_LEN (sizeof(lookup_table_positive)/sizeof(int32_t)) |
There was a problem hiding this comment.
add spaces around the division operator. uncrustify will complain otherwise.
sys/phydat/phydat.c
Outdated
| dat->val[k] /= 10; | ||
|
|
||
| for (unsigned int i = 0; i < LOOKUP_LEN; i++) { | ||
| if (max > lookup[i]){ |
sys/phydat/phydat.c
Outdated
| } | ||
|
|
||
| /* Applying scale and add half of the divisor for correct rounding */ | ||
| long divisor_half = divisor >> 1; |
sys/phydat/phydat.c
Outdated
| long divisor_half = divisor >> 1; | ||
| for (unsigned int i = 0; i < dim; i++) { | ||
| if (values[i] >= 0) { | ||
| dat->val[i] = (values[i] + divisor_half) / divisor; |
There was a problem hiding this comment.
Do you know if this pulls in signed or unsigned division helpers on platforms without hardware division?
The preconditions before ensures that both operands are positive, but they are still signed types, so it is likely that this calls signed division (__divsi3, __aeabi_idiv). Unsigned division is cheaper in terms of both ROM usage and CPU cycles, at least on ARMv6, but I'm guessing it should be similar on smaller arches too e.g. AVR (for reference: #9738)
|
@gebart: Thanks for your review. I was completely unaware that signed division could be more expensive. Thanks for pointing that out :-) |
|
The failed Murdock build seems to be unrelated. Let me try if it works after a rebase. |
|
@gebart: The rebase solved indeed the Murdock issue :-). Could you check if I addressed your comments adequately? |
|
@gebart: Sorry to annoy you again. Could you please check if I addressed your comments? If so, I would squash and rebase against the current master. |
jnohlgard
left a comment
There was a problem hiding this comment.
I think all of my comments were addressed.
Please squash
The current phydat_fit implementation the following limitations:
- The API is way more complicated to use than needed
- It doesn't perform any rounding
- It uses `long` in a place where actual width (or better range) of the type
is pretty important.
This commit addresses these limitations and uses lookup-tables to reduce the
number of divisions required.
Before this commit code using it looked like this:
``` C
long values[] = { 100000, 2000000, 30000000 };
phydat_t dat = { .scale = 42, .unit = UNIT_V };
phydat_fit(&dat, values[0], 0, phydat_fit(&dat, values[1], 1, phydat_fit(&dat, values[2], 2, 0)));
```
Now it can be used like this:
``` C
int32_t values[] = { 100000, 2000000, 30000000 };
phydat_t dat = { .unit = UNIT_V, .scale = 42 };
phydat_fit(&dat, values, 3);
```
|
Squashed and rebased :-) @gebart: Thanks for your review and for pointing out the different ROM size of signed and unsigned divisions. |
|
Since this is an API change I don't want to merge this in hour zero of the feature freeze. Let's wait a bit, until the release branch is open. |
It is. Let's go! |
|
Thanks for your contribution and your patience @maribu! |
|
No problem. I learned something really useful, so it was well spent time :-) |
The current phydat_fit implementation has two limitations: First, the API is way more complicated to use than needed. Second, it doesn't perform correct rounding. This PR addresses both.
Before this commit code using it looked like this:
Now it can be used like this: