|
- /* FatLib Library
- * Copyright (C) 2013 by William Greiman
- *
- * This file is part of the FatLib Library
- *
- * This Library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This Library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with the FatLib Library. If not, see
- * <http://www.gnu.org/licenses/>.
- */
- #include <avr/pgmspace.h>
- #include <FmtNumber.h>
- // Use Stimmer div/mod 10 on avr
- #ifdef __AVR__
- #define USE_STIMMER
- #endif // __AVR__
- //------------------------------------------------------------------------------
- // Stimmer div/mod 10 for AVR
- // this code fragment works out i/10 and i%10 by calculating
- // i*(51/256)*(256/255)/2 == i*51/510 == i/10
- // by "j.k" I mean 32.8 fixed point, j is integer part, k is fractional part
- // j.k = ((j+1.0)*51.0)/256.0
- // (we add 1 because we will be using the floor of the result later)
- // divmod10_asm16 and divmod10_asm32 are public domain code by Stimmer.
- // http://forum.arduino.cc/index.php?topic=167414.msg1293679#msg1293679
- #define divmod10_asm16(in32, mod8, tmp8) \
- asm volatile( \
- " ldi %2,51 \n\t" \
- " mul %A0,%2 \n\t" \
- " clr %A0 \n\t" \
- " add r0,%2 \n\t" \
- " adc %A0,r1 \n\t" \
- " mov %1,r0 \n\t" \
- " mul %B0,%2 \n\t" \
- " clr %B0 \n\t" \
- " add %A0,r0 \n\t" \
- " adc %B0,r1 \n\t" \
- " clr r1 \n\t" \
- " add %1,%A0 \n\t" \
- " adc %A0,%B0 \n\t" \
- " adc %B0,r1 \n\t" \
- " add %1,%B0 \n\t" \
- " adc %A0,r1 \n\t" \
- " adc %B0,r1 \n\t" \
- " lsr %B0 \n\t" \
- " ror %A0 \n\t" \
- " ror %1 \n\t" \
- " ldi %2,10 \n\t" \
- " mul %1,%2 \n\t" \
- " mov %1,r1 \n\t" \
- " clr r1 \n\t" \
- :"+r"(in32), "=d"(mod8), "=d"(tmp8) : : "r0")
-
- #define divmod10_asm32(in32, mod8, tmp8) \
- asm volatile( \
- " ldi %2,51 \n\t" \
- " mul %A0,%2 \n\t" \
- " clr %A0 \n\t" \
- " add r0,%2 \n\t" \
- " adc %A0,r1 \n\t" \
- " mov %1,r0 \n\t" \
- " mul %B0,%2 \n\t" \
- " clr %B0 \n\t" \
- " add %A0,r0 \n\t" \
- " adc %B0,r1 \n\t" \
- " mul %C0,%2 \n\t" \
- " clr %C0 \n\t" \
- " add %B0,r0 \n\t" \
- " adc %C0,r1 \n\t" \
- " mul %D0,%2 \n\t" \
- " clr %D0 \n\t" \
- " add %C0,r0 \n\t" \
- " adc %D0,r1 \n\t" \
- " clr r1 \n\t" \
- " add %1,%A0 \n\t" \
- " adc %A0,%B0 \n\t" \
- " adc %B0,%C0 \n\t" \
- " adc %C0,%D0 \n\t" \
- " adc %D0,r1 \n\t" \
- " add %1,%B0 \n\t" \
- " adc %A0,%C0 \n\t" \
- " adc %B0,%D0 \n\t" \
- " adc %C0,r1 \n\t" \
- " adc %D0,r1 \n\t" \
- " add %1,%D0 \n\t" \
- " adc %A0,r1 \n\t" \
- " adc %B0,r1 \n\t" \
- " adc %C0,r1 \n\t" \
- " adc %D0,r1 \n\t" \
- " lsr %D0 \n\t" \
- " ror %C0 \n\t" \
- " ror %B0 \n\t" \
- " ror %A0 \n\t" \
- " ror %1 \n\t" \
- " ldi %2,10 \n\t" \
- " mul %1,%2 \n\t" \
- " mov %1,r1 \n\t" \
- " clr r1 \n\t" \
- :"+r"(in32), "=d"(mod8), "=d"(tmp8) : : "r0")
- //------------------------------------------------------------------------------
- /*
- // C++ code is based on this version of divmod10 by robtillaart.
- // http://forum.arduino.cc/index.php?topic=167414.msg1246851#msg1246851
- // from robtillaart post:
- // The code is based upon the divu10() code from the book Hackers Delight1.
- // My insight was that the error formula in divu10() was in fact modulo 10
- // but not always. Sometimes it was 10 more.
- void divmod10(uint32_t in, uint32_t &div, uint32_t &mod)
- {
- // q = in * 0.8;
- uint32_t q = (in >> 1) + (in >> 2);
- q = q + (q >> 4);
- q = q + (q >> 8);
- q = q + (q >> 16); // not needed for 16 bit version
-
- // q = q / 8; ==> q = in *0.1;
- q = q >> 3;
-
- // determine error
- uint32_t r = in - ((q << 3) + (q << 1)); // r = in - q*10;
- div = q + (r > 9);
- if (r > 9) mod = r - 10;
- else mod = r;
- }
- // Hackers delight function is here:
- // http://www.hackersdelight.org/hdcodetxt/divuc.c.txt
- // Code below uses 8/10 = 0.1100 1100 1100 1100 1100 1100 1100 1100.
- // 15 ops including the multiply, or 17 elementary ops.
- unsigned divu10(unsigned n) {
- unsigned q, r;
-
- q = (n >> 1) + (n >> 2);
- q = q + (q >> 4);
- q = q + (q >> 8);
- q = q + (q >> 16);
- q = q >> 3;
- r = n - q*10;
- return q + ((r + 6) >> 4);
- // return q + (r > 9);
- }
- */
- //------------------------------------------------------------------------------
-
- static const float m[] PROGMEM = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32};
- static const float p[] PROGMEM = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32};
- // scale float v by power of ten. return v*10^n
- float scale10(float v, int8_t n) {
- const float *s;
- if (n < 0) {
- n = -n;
- s = m;
- } else {
- s = p;
- }
- n &= 63;
- for (uint8_t i = 0; n; n >>= 1, i++) {
- if (n & 1) v *= pgm_read_float(&s[i]);
- }
- return v;
- }
- //------------------------------------------------------------------------------
- // Format 16-bit unsigned
- char* fmtDec(uint16_t n, char* p) {
- while (n > 9) {
- #ifdef USE_STIMMER
- uint8_t tmp8, r;
- divmod10_asm16(n, r, tmp8);
- #else // USE_STIMMER
- uint16_t t = n;
- n = (n >> 1) + (n >> 2);
- n = n + (n >> 4);
- n = n + (n >> 8);
- // n = n + (n >> 16); // no code for 16-bit n
- n = n >> 3;
- uint8_t r = t - (((n << 2) + n) << 1);
- if (r > 9) {
- n++;
- r -= 10;
- }
- #endif // USE_STIMMER
- *--p = r + '0';
- }
- *--p = n + '0';
- return p;
- }
- //------------------------------------------------------------------------------
- // format 32-bit unsigned
- char* fmtDec(uint32_t n, char* p) {
- while (n >> 16) {
- #ifdef USE_STIMMER
- uint8_t tmp8, r;
- divmod10_asm32(n, r, tmp8);
- #else // USE_STIMMER
- uint32_t t = n;
- n = (n >> 1) + (n >> 2);
- n = n + (n >> 4);
- n = n + (n >> 8);
- n = n + (n >> 16);
- n = n >> 3;
- uint8_t r = t - (((n << 2) + n) << 1);
- if (r > 9) {
- n++;
- r -= 10;
- }
- #endif // USE_STIMMER
- *--p = r + '0';
- }
- return fmtDec((uint16_t)n, p);
- }
- //------------------------------------------------------------------------------
- char* fmtFloat(float value, char* p, uint8_t prec) {
- char sign = value < 0 ? '-' : 0;
- if (sign) value = -value;
-
- if (isnan(value)) {
- *--p = 'n';
- *--p = 'a';
- *--p = 'n';
- return p;
- }
- if (isinf(value)) {
- *--p = 'f';
- *--p = 'n';
- *--p = 'i';
- return p;
- }
- if (value > 4294967040.0) {
- *--p = 'f';
- *--p = 'v';
- *--p = 'o';
- return p;
- }
- if (prec > 9) prec = 9;
- value += scale10(0.5, -prec);
-
- uint32_t whole = value;
- if (prec) {
- char* tmp = p - prec;
- uint32_t fraction = scale10(value - whole, prec);
- p = fmtDec(fraction, p);
- while (p > tmp) *--p = '0';
- *--p = '.';
- }
- p = fmtDec(whole, p);
- if (sign) *--p = sign;
- return p;
- }
- //------------------------------------------------------------------------------
- /** Print a number followed by a field terminator.
- * \param[in] value The number to be printed.
- * \param[in] ptr Pointer to last char in buffer.
- * \param[in] prec Number of digits after decimal point.
- * \param[in] expChar Use exp format if non zero.
- * \return Pointer to first character of result.
- */
- char* fmtFloat(float value, char* ptr, uint8_t prec, char expChar) {
- bool neg = value < 0;
- if (neg) value = -value;
-
- // check for nan inf ovf
- if (isnan(value)) {
- *--ptr = 'n';
- *--ptr = 'a';
- *--ptr = 'n';
- return ptr;
- }
- if (isinf(value)) {
- *--ptr = 'f';
- *--ptr = 'n';
- *--ptr = 'i';
- return ptr;
- }
- if (!expChar && value > 4294967040.0) {
- *--ptr = 'f';
- *--ptr = 'v';
- *--ptr = 'o';
- return ptr;
- }
- if (prec > 9) prec = 9;
- float round = scale10(0.5, -prec);
- if (expChar) {
- int8_t exp = 0;
- bool expNeg = false;
- if (value) {
- while (value > 10.0) {
- value *= 0.1;
- exp++;
- }
- while (value < 1.0) {
- value *= 10.0;
- exp--;
- }
- value += round;
- if (value > 10.0) {
- value *= 0.1;
- exp++;
- }
- expNeg = exp < 0;
- if (expNeg) exp = -exp;
- }
- ptr = fmtDec((uint16_t)exp, ptr);
- if (exp < 10) *--ptr = '0';
- *--ptr = expNeg ? '-' : '+';
- *--ptr = expChar;
- } else {
- // round value
- value += round;
- }
- uint32_t whole = value;
- if (prec) {
- char* tmp = ptr - prec;
- uint32_t fraction = scale10(value - whole, prec);
- ptr = fmtDec(fraction, ptr);
- while (ptr > tmp) *--ptr = '0';
- *--ptr = '.';
- }
- ptr = fmtDec(whole, ptr);
- if (neg) *--ptr = '-';
- return ptr;
- }
- //------------------------------------------------------------------------------
- char* fmtHex(uint32_t n, char* p) {
- do {
- uint8_t h = n & 0XF;
- *--p = h + (h < 10 ? '0' : 'A' - 10);
- n >>= 4;
- } while (n);
- return p;
- }
- //------------------------------------------------------------------------------
- float scanFloat(const char* str, char** ptr) {
- int16_t const EXP_LIMIT = 100;
- bool digit = false;
- bool dot = false;
- uint32_t fract = 0;
- int fracExp = 0;
- uint8_t nd = 0;
- bool neg;
- int c;
- float v;
- const char* successPtr;
-
- if (ptr) *ptr = const_cast<char*>(str);
-
- while (isspace((c = *str++))) {}
- neg = c == '-';
- if (c == '-' || c == '+') c = *str++;
- // Skip leading zeros
- while (c == '0') {
- c = *str++;
- digit = true;
- }
- for (;;) {
- if (isdigit(c)) {
- digit = true;
- if (nd < 9) {
- fract = 10*fract + c - '0';
- nd++;
- if (dot) fracExp--;
- } else {
- if (!dot) fracExp++;
- }
- } else if (c == '.') {
- if (dot) goto fail;
- dot = true;
- } else {
- if (!digit) goto fail;
- break;
- }
- successPtr = str;
- c = *str++;
- }
- if (c == 'e' || c == 'E') {
- int exp = 0;
- c = *str++;
- bool expNeg = c == '-';
- if (c == '-' || c == '+') {
- c = *str++;
- }
- while (isdigit(c)) {
- if (exp > EXP_LIMIT) goto fail;
- exp = 10*exp + c - '0';
- successPtr = str;
- c = *str++;
- }
- fracExp += expNeg ? -exp : exp;
- }
- if (ptr) *ptr = const_cast<char*>(successPtr);
- v = scale10(static_cast<float>(fract), fracExp);
- return neg ? -v: v;
-
- fail:
- return 0;
- }
-
|