Skip to content

Commit f8ddd80

Browse files
author
dave
committed
#57 control over negative values in the large number support.
1 parent ab2c237 commit f8ddd80

File tree

3 files changed

+150
-54
lines changed

3 files changed

+150
-54
lines changed

src/EditableLargeNumberMenuItem.cpp

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ void LargeFixedNumber::clear() {
1212
negative = false;
1313
}
1414

15-
void LargeFixedNumber::setValue(uint32_t whole, uint32_t fraction, bool negative) {
15+
void LargeFixedNumber::setValue(uint32_t whole, uint32_t fraction, bool neg) {
1616
clear();
1717
convertToBcdPacked(fraction, 0, fractionDp);
18-
convertToBcdPacked(whole, fractionDp, LARGE_NUM_MAX_DIGITS);
19-
this->negative = negative;
18+
convertToBcdPacked(whole, fractionDp, totalSize);
19+
this->negative = neg;
2020
}
2121

2222
uint32_t LargeFixedNumber::fromBcdPacked(int start, int end) {
@@ -42,23 +42,50 @@ int LargeFixedNumber::getDigit(int digit) {
4242
if (digit > 11) return false;
4343
uint8_t r = bcdRepresentation[digit / 2];
4444
if ((digit % 2) == 0) {
45-
return r & 0x0f;
45+
return r & 0x0fU;
4646
}
4747
else {
48-
return (int)(r >> 4);
48+
return (int)(r >> 4U);
4949
}
5050
}
5151

52-
void LargeFixedNumber::setDigit(int digit, int val) {
52+
void LargeFixedNumber::setDigit(int digit, int v) {
53+
auto val = uint8_t(v);
5354
if (digit > 11) return;
5455
if ((digit % 2) == 0) {
5556
bcdRepresentation[digit / 2] = (bcdRepresentation[digit / 2] & 0xf0) | val;
5657
}
5758
else {
58-
bcdRepresentation[digit / 2] = (bcdRepresentation[digit / 2] & 0x0f) | (val << 4);
59+
bcdRepresentation[digit / 2] = (bcdRepresentation[digit / 2] & 0x0f) | (val << 4U);
5960
}
6061
}
6162

63+
float LargeFixedNumber::getAsFloat() {
64+
if(fractionDp == 0) return (float)getWhole();
65+
66+
float fraction = ((float)getFraction() / (float)dpToDivisor(fractionDp));
67+
float asFlt = (float)getWhole() + fraction;
68+
serdebugF3("fract, asFlt ", fraction, asFlt);
69+
if (negative) asFlt = -asFlt;
70+
return asFlt;
71+
}
72+
73+
void LargeFixedNumber::setFromFloat(float value) {
74+
bool neg = value < 0.0f;
75+
value = abs(value);
76+
uint32_t val;
77+
uint32_t frc;
78+
if(fractionDp == 0) {
79+
val = (uint32_t)value;
80+
frc = 0;
81+
}
82+
else {
83+
val = (uint32_t)value;
84+
frc = (value - (float) val) * float(dpToDivisor(fractionDp));
85+
}
86+
setValue(val, frc, neg);
87+
}
88+
6289
void EditableLargeNumberMenuItem::setLargeNumberFromString(const char* val) {
6390
int offset = 0;
6491
bool negative = false;
@@ -86,8 +113,10 @@ void wrapEditor(bool editRow, char val, char* buffer, int bufferSize) {
86113

87114
int largeNumItemRenderFn(RuntimeMenuItem* item, uint8_t row, RenderFnMode mode, char* buffer, int bufferSize) {
88115
if (item->getMenuType() != MENUTYPE_LARGENUM_VALUE) return 0;
89-
EditableLargeNumberMenuItem* numItem = reinterpret_cast<EditableLargeNumberMenuItem*>(item);
116+
auto numItem = reinterpret_cast<EditableLargeNumberMenuItem*>(item);
117+
auto negativeAllowed = numItem->isNegativeAllowed();
90118
LargeFixedNumber *num = numItem->getLargeNumber();
119+
int numParts = numItem->getNumberOfParts() - (negativeAllowed ? 1 : 0);
91120

92121
switch (mode) {
93122
case RENDERFN_VALUE: {
@@ -96,21 +125,22 @@ int largeNumItemRenderFn(RuntimeMenuItem* item, uint8_t row, RenderFnMode mode,
96125
bool hadNonZero = false;
97126
uint8_t editPosition = 0;
98127

99-
if (editingMode || num->isNegative()) {
128+
if (negativeAllowed && (editingMode || num->isNegative())) {
100129
wrapEditor(row == 1, num->isNegative() ? '-' : '+', buffer, bufferSize);
130+
row = row - 1;
101131
}
102132

103-
row = row - 2;
133+
row = row - 1;
104134

105-
for (int i = num->decimalPointIndex(); i < (numItem->getNumberOfParts() - 1); i++) {
135+
for (int i = num->decimalPointIndex(); i < numParts; i++) {
106136
char txtVal = num->getDigit(i) + '0';
107137
hadNonZero |= txtVal != '0';
108138
if (hadNonZero || editingMode) {
109139
wrapEditor(row == editPosition, txtVal, buffer, bufferSize);
110140
}
111141
editPosition++;
112142
}
113-
appendChar(buffer, '.', bufferSize);
143+
if(num->decimalPointIndex() != 0) appendChar(buffer, '.', bufferSize);
114144

115145
for (int i = 0; i < num->decimalPointIndex(); i++) {
116146
char txtVal = num->getDigit(i) + '0';
@@ -120,16 +150,23 @@ int largeNumItemRenderFn(RuntimeMenuItem* item, uint8_t row, RenderFnMode mode,
120150
return true;
121151
}
122152
case RENDERFN_GETRANGE: {
123-
return row == 1 ? 1 : 9;
153+
if(negativeAllowed) {
154+
return row == 1 ? 1 : 9;
155+
}
156+
else {
157+
return 9;
158+
}
124159
}
125160
case RENDERFN_SET_VALUE: {
126161
int idx = row - 1;
127-
if (idx == 0) {
128-
num->setNegative(buffer[0]);
129-
return true;
162+
if(negativeAllowed) {
163+
if (idx == 0) {
164+
num->setNegative(buffer[0]);
165+
return true;
166+
}
167+
idx--;
130168
}
131-
idx--;
132-
int dpIndex = (numItem->getNumberOfParts() - 1) - num->decimalPointIndex();
169+
int dpIndex = (numParts) - num->decimalPointIndex();
133170
int pos = idx >= dpIndex ? idx - dpIndex : idx + num->decimalPointIndex();
134171
num->setDigit(pos, buffer[0]);
135172
return true;
@@ -140,11 +177,13 @@ int largeNumItemRenderFn(RuntimeMenuItem* item, uint8_t row, RenderFnMode mode,
140177
}
141178
case RENDERFN_GETPART: {
142179
int idx = row - 1;
143-
if (idx == 0) {
144-
return num->isNegative();
145-
}
146-
idx--;
147-
int dpIndex = (numItem->getNumberOfParts() - 1) - num->decimalPointIndex();
180+
if(negativeAllowed) {
181+
if (idx == 0) {
182+
return num->isNegative();
183+
}
184+
idx--;
185+
}
186+
int dpIndex = (numParts) - num->decimalPointIndex();
148187
int pos = idx >= dpIndex ? idx - dpIndex : idx + num->decimalPointIndex();
149188
return num->getDigit(pos);
150189
}

src/EditableLargeNumberMenuItem.h

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,23 @@
1919
#define LARGE_NUM_ALLOC_SIZE (LARGE_NUM_MAX_DIGITS / 2)
2020

2121
/**
22-
* A structure for very large integers that can be edited using the multipart editor.
23-
* They are represented as binary coded decimal to 12 dp with as many fraction decimal
24-
* places as needed. The whole can be either negative or positive, while the fraction is
25-
* an unsigned number in 32 bit form. This provides numbers in the range of at least 9 digits before
26-
* the dp, and 9 after.
22+
* A structure for very large numbers that can be edited using the multipart editor. They are represented as binary
23+
* coded decimal to 12 dp with between 0 and 9 fraction decimal places if needed. The whole can be either negative or
24+
* positive between 1 and 9 decimal places. Both fraction and whole can be extracted as either a floating point, a
25+
* per digit value, or as integers containing whole, fraction and negative flag.
2726
*/
2827
class LargeFixedNumber {
2928
private:
3029
uint8_t bcdRepresentation[LARGE_NUM_ALLOC_SIZE];
3130
bool negative;
31+
uint8_t totalSize;
3232
uint8_t fractionDp;
3333
public:
3434
/**
35-
* Create a default instance with decimal places set to 4
35+
* Create a default instance with decimal places set to 4 and total size 12.
3636
*/
3737
LargeFixedNumber() {
38+
totalSize = 12;
3839
setPrecision(4);
3940
}
4041

@@ -44,16 +45,20 @@ class LargeFixedNumber {
4445
void clear();
4546

4647
/**
47-
* @return the number of decimal places needed.
48+
* @return the number of decimal places this represents.
4849
*/
4950
int decimalPointIndex() const { return fractionDp; }
5051

5152
/**
52-
* Set the number of decimal places and zero out any currently held value.
53+
* Set the number of decimal places and optionally the total size then zero out any currently held value. When
54+
* setting this value you should ensure that: fractionDp is not larger than 9, the difference between fractionDp
55+
* and maxDigits is not greater than 9.
5356
* @param dp the new number of decimal places
57+
* @param maxDigits the total number of digits needed.
5458
*/
55-
void setPrecision(uint8_t dp) {
59+
void setPrecision(uint8_t dp, uint8_t maxDigits = 12) {
5660
fractionDp = dp;
61+
totalSize = maxDigits;
5762
clear();
5863
}
5964

@@ -67,17 +72,10 @@ class LargeFixedNumber {
6772

6873
/**
6974
* Takes a floating point value and converts it into the internal representation.
70-
* This class can nearly always accurately represent a float value unless it exceeds
71-
* 12 digits.
75+
* This will represent the float within the bounds of the current total digits and decimal precision.
7276
* @param value the float value to convert
7377
*/
74-
void setFromFloat(float value) {
75-
bool neg = value < 0.0f;
76-
value = abs(value);
77-
uint32_t val = (uint32_t)value;
78-
uint32_t frc = (value - (float)val) * dpToDivisor(fractionDp);
79-
setValue(val, frc, neg);
80-
}
78+
void setFromFloat(float value);
8179

8280
/**
8381
* Converts from the BCD packed structure into an integer
@@ -88,7 +86,7 @@ class LargeFixedNumber {
8886
uint32_t fromBcdPacked(int start, int end);
8987

9088
/**
91-
* Converts from the BCD packed structure into an integer
89+
* Converts from an integer into BCD packed.
9290
* @param value the value to be encoded
9391
* @param start the index to start in the packed data
9492
* @param end will stop at one before this point
@@ -116,18 +114,12 @@ class LargeFixedNumber {
116114
*
117115
* @return the current represented value as a floating point number.
118116
*/
119-
float getAsFloat() {
120-
float fraction = ((float)getFraction() / (float)dpToDivisor(fractionDp));
121-
float asFlt = (float)getWhole() + fraction;
122-
serdebugF3("fract, asFlt ", fraction, asFlt);
123-
if (negative) asFlt = -asFlt;
124-
return asFlt;
125-
}
117+
float getAsFloat();
126118

127119
/**
128120
* @return true if negative otherwise false.
129121
*/
130-
bool isNegative() { return negative; }
122+
bool isNegative() const { return negative; }
131123

132124
/**
133125
* Sets the negative flag, if true the number becomes a negative value.
@@ -141,7 +133,7 @@ class LargeFixedNumber {
141133
* @return the whole part of the value
142134
*/
143135
uint32_t getWhole() {
144-
return fromBcdPacked(fractionDp, LARGE_NUM_MAX_DIGITS);
136+
return fromBcdPacked(fractionDp, totalSize);
145137
}
146138

147139
/**
@@ -167,20 +159,33 @@ int largeNumItemRenderFn(RuntimeMenuItem* item, uint8_t row, RenderFnMode mode,
167159

168160
/**
169161
* A multipart editor for very large numbers, either integer or fixed point decimal that exceed the usable range
170-
* of a rotary encoder or joystick to set their value. This class works by editing each digit in turn.
162+
* of a rotary encoder or joystick to set their value. This class works by editing each digit in turn. This is based
163+
* on LargeFixedNumber, see that for more details on the capabilities
164+
* @see LargeFixedNumber
171165
*/
172166
class EditableLargeNumberMenuItem : public EditableMultiPartMenuItem<LargeFixedNumber> {
167+
private:
168+
bool negativeAllowed;
173169
public:
174-
EditableLargeNumberMenuItem(RuntimeRenderingFn renderFn, uint16_t id, int maxDigits, int dps, MenuItem* next = NULL)
170+
EditableLargeNumberMenuItem(RuntimeRenderingFn renderFn, uint16_t id, int maxDigits, int dps, bool allowNeg, MenuItem* next = nullptr)
171+
: EditableMultiPartMenuItem(MENUTYPE_LARGENUM_VALUE, id, maxDigits + (allowNeg ? 1 : 0), renderFn, next) {
172+
data.setPrecision(dps, maxDigits);
173+
negativeAllowed = allowNeg;
174+
}
175+
176+
EditableLargeNumberMenuItem(RuntimeRenderingFn renderFn, uint16_t id, int maxDigits, int dps, MenuItem* next = nullptr)
175177
: EditableMultiPartMenuItem(MENUTYPE_LARGENUM_VALUE, id, maxDigits + 1, renderFn, next) {
176-
data.setPrecision(dps);
178+
data.setPrecision(dps, maxDigits);
179+
negativeAllowed = true;
177180
}
178181

179182
/** gets the large integer value that this class is using */
180183
LargeFixedNumber* getLargeNumber() { return &data; }
181184

182185
/** sets a number from a string in the form whole.fraction */
183186
void setLargeNumberFromString(const char* largeNum);
187+
188+
bool isNegativeAllowed() { return negativeAllowed; }
184189
};
185190

186191
#endif //_EDITABLE_LARGE_NUMBER_MENU_ITEM_H_

tests/tcMenuCoreTests/LargeNumberItemTests.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,56 @@ test(testSetLargeIntFromString) {
230230
assertFalse(editable.getLargeNumber()->isNegative());
231231
}
232232

233+
test(LargeNumWithNegativeNotAllowed) {
234+
EditableLargeNumberMenuItem editable(largeNumTestCb, 101, 6, 0, false);
235+
236+
editable.setLargeNumberFromString("15234");
237+
assertEqual(editable.getLargeNumber()->getWhole(), (uint32_t)15234);
238+
assertEqual(editable.getLargeNumber()->getFraction(), (uint32_t)0);
239+
assertNear(editable.getLargeNumber()->getAsFloat(), 15234.0F, 0.00001);
240+
assertFalse(editable.getLargeNumber()->isNegative());
241+
242+
char sz[32];
243+
editable.copyValue(sz, sizeof(sz));
244+
assertStringCaseEqual("15234", sz);
245+
246+
editable.beginMultiEdit();
247+
assertEqual(9, editable.nextPart());
248+
assertEqual(0, editable.getPartValueAsInt());
249+
editable.copyValue(sz, sizeof(sz));
250+
assertStringCaseEqual("[0]15234", sz);
251+
252+
assertEqual(9, editable.nextPart());
253+
assertEqual(1, editable.getPartValueAsInt());
254+
editable.copyValue(sz, sizeof(sz));
255+
assertStringCaseEqual("0[1]5234", sz);
256+
257+
assertEqual(9, editable.nextPart());
258+
assertEqual(5, editable.getPartValueAsInt());
259+
editable.copyValue(sz, sizeof(sz));
260+
assertStringCaseEqual("01[5]234", sz);
261+
262+
assertEqual(9, editable.nextPart());
263+
assertEqual(2, editable.getPartValueAsInt());
264+
editable.copyValue(sz, sizeof(sz));
265+
assertStringCaseEqual("015[2]34", sz);
266+
267+
assertEqual(9, editable.nextPart());
268+
assertEqual(3, editable.getPartValueAsInt());
269+
editable.valueChanged(6);
270+
editable.copyValue(sz, sizeof(sz));
271+
assertStringCaseEqual("0152[6]4", sz);
272+
273+
assertEqual(9, editable.nextPart());
274+
assertEqual(4, editable.getPartValueAsInt());
275+
editable.valueChanged(5);
276+
editable.copyValue(sz, sizeof(sz));
277+
assertStringCaseEqual("01526[5]", sz);
278+
assertEqual(0, editable.nextPart());
279+
280+
assertFalse(editable.isEditing());
281+
editable.copyValue(sz, sizeof(sz));
282+
assertStringCaseEqual("15265", sz);
283+
}
284+
233285
#endif // LARGE_NUMBER_ITEM_TESTS_H

0 commit comments

Comments
 (0)