Arithmetic issue

Go To Last Post
36 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi All,

 

I'm primarily a hardware designer who has been thrust into the role of an embedded programmer. That said, programming is really not my forte, so please excuse any questions that may seem obvious to you. Anyway, I'm having an issue with a block of code that is used to decode a data stream. The data stream is working properly. Using breakpoints I was able to validate that the four ints that are the argument to the function are correct. Here's the block of code;

 

void parseData(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4){
    uint8_t cmd;
    uint8_t chan;
    uint8_t exponent;
    uint8_t sign;
    uint8_t dataUpper;
    int dat;
    double val;
    
    cmd = b1/32;
    chan = b1 % 32;
    exponent = b2/16;
    sign = (b2 % 16)/8;
    dataUpper = b2 %8;
    dat = (int)dataUpper*2^16+(int)b3*2^8+(int)b4;
    val = ((-1)^sign)*dat*10^(exponent*-1);

}

 

cmd, chan, exponent and sign are all correct. Dat and val are not giving a correct values. I probably should be using bit shifting, rather than multiplying by powers of 2, but either should work. I feel like it's a data type issue, but I'm not sure. I'm sure this problem is a no-brainer for a programmer who is more skilled than me.

 

Thank you!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

What do you mean 'not correct'. Show us your input values and what comes out, and what you're expecting... 

:: Morten

 

(yes, I work for Atmel, yes, I do this in my spare time, now stop sending PMs)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

^ is xor, I believe you have to use math.h pow to get what you want.

 

use an online compiler to play with (pc compiler version gets you printf support in the web page)-

https://godbolt.org/z/58YvkB

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

^ is XOR, not pow... Use the pow function in math.h to do an exponent

 

... what curtvm said laugh

:: Morten

 

(yes, I work for Atmel, yes, I do this in my spare time, now stop sending PMs)

Last Edited: Tue. May 19, 2020 - 11:39 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
void parseData(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4){
    uint8_t cmd;
    uint8_t chan;
    uint8_t exponent;
    uint8_t sign;
    uint8_t dataUpper;
    long int dat;  //int dat;
    double val;
    
    cmd = (b1 >> 5);        //cmd = b1/32; extract bits 7..5
    chan = b1 & 0x1f;       //chan = b1 % 32; extract bits 0..4
    exponent = (b2 >> 4 );   // exponent = b2/16; extract bits 7..4
    sign = (b2 & 0x0f) >> 3; //sign = (b2 % 16)/8; extract bit 3
    dataUpper = (b2 & 0x07);    //extract bits 2..0 dataUpper = b2 %8;
    
    // here's where things go wrong - int is only 16 bits and you're multplying a value by 65536
    dat = (long int )dataUpper << 16 + ((long int)b3 << 8) + (long int) b4);
    //dat = (int)dataUpper*2^16+(int)b3*2^8+(int)b4;
    val = dat * pow(10.0,exponent);
    if (sign)
        {
            val*= -1.0;
        }
    //val = ((-1)^sign)*dat*10^(exponent*-1);
    
    //**NOTE ^ is the xor function in C!
    //**NOTE in avrgcc, double is actually only single precision
}


// if we assume that b1,2,3,4 are sequential, pass a pointer
// here's how I would write the code


//
//
//     convert the following floating point representation to a float value
//     msg[1]    msg[2]  msg[3]
//     XXXXSMMM MMMMMMMM MMMMMMMM
//     where X is the exponent, S = sign and M = mantissa
//
double parseFloat(uint8_t *msg)
{
    
    uint8_t exponent;
    uint8_t sign;
    uint8_t dataUpper;
    long int mantissa;  //int dat;
    double val;
    
    exponent = (msg[1] >> 4 );   // exponent = b2/16; extract bits 7..4
    sign = (msg[1] & 0x0f) >> 3; //sign = (b2 % 16)/8; extract bit 3
    
    // here's where things go wrong - int is only 16 bits and you're multplying a value by 65536

    mantissa = (long int )(msg[1] & 7) << 16 + ((long int)msg[2] << 8) + (long int) msg[3]);

    //dat = (int)dataUpper*2^16+(int)b3*2^8+(int)b4;

    val = mantissa * pow(10.0,exponent); //might need some attention!
    if (sign)
        {
            val*= -1.0;
        }
    //val = ((-1)^sign)*dat*10^(exponent*-1);
    return val;
}

uint8_t parseCmd(uint8_t *msg)
{
    return  (msg[0] >> 5);
}

uint8_t parseChan(uint8_t msg)
{
    return chan = msg[0] & 0x1f;
}

I gather what you're wanting to do is convert a specific floating point representation. The above is my attempt at reverse engineering your code. I've not thought too much about how you convert the mantissa, so you'd want to check my math.

Some hints:

1. comment your code - your function should have some comments about the format of the incoming data and what you want to achieve.

2. learn about bit operations.

 

 

Usually the sign is the most significant bit  - so this is an 'interesting' fp representation.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi All,

 

I designed this fp representation. It's not particularly efficient, but it works for our purpose. If ^ is xor, then that would certianly explain why it isn't working. I've included the math.h header, but the pow() function can't be found. I could iterate through dividing by 10 (or multiplying by 0.1), but I'd much prefer to get the pow function working. Does anyone have any idea what could be wrong with the math header?

 

Also thanks for the tips about the bitwise and boolean operations. I'm sure they're much faster than division and modulo.

 

Thank you!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I tried the code again using iteration instead of pow() and it seems to work. I noticed that instead of "1.7", the compiler was giving 1.7000000000000002. While this is close enough that it won't cause problems, is this beccause of IEEE754's inabliity to accurately display numbers, and 1.7000000000000002 was the closest number to 1.7 that IEEE754 could display?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

.. can't be found... Why not show us the build log and the relevant code, instead of doesn't work and can't be found?

:: Morten

 

(yes, I work for Atmel, yes, I do this in my spare time, now stop sending PMs)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A.MacDonald wrote:
If ^ is xor, then that would certianly explain why it isn't working. I've included the math.h header, but the pow() function can't be found.

 

Don't even think about using `pow` function in this application! It is hard to imagine a more prominent example of rampant rabid incompetence than an attempt/suggestion to use `pow` here.

 

Even though your are formally working with a floating-point format, what you are doing is a conversion of binary representation. All your "power" operations are integral (and mostly boil down to bit shifts). You'll can (and will have to) do without `pow`. There's nothing difficult about it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AndreyT wrote:

A.MacDonald wrote:
If ^ is xor, then that would certianly explain why it isn't working. I've included the math.h header, but the pow() function can't be found.

 

Don't even think about using `pow` function in this application! It is hard to imagine a more prominent example of rampant rabid incompetence than an attempt/suggestion to use `pow` here.

 

Even though your are formally working with a floating-point format, what you are doing is a conversion of binary representation. All your "power" operations are integral (and mostly boil down to bit shifts). You'll can (and will have to) do without `pow`. There's nothing difficult about it.

 

The base is 10, not 2.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

meolsen wrote:
.. can't be found... Why not show us the build log and the relevant code, instead of doesn't work and can't be found?

 

There was a little red squigly line under pow(). It built without errors and it worked.

 

Quick question for y'all: i noticed that the results of val = (double) dat*0.1*0.1 were not equal to val = (double) dat/pow(10,2). The first one was  1.700000000000000, and the second was 1.7 even. I know the two operations are mathematically the same, but they're clearly not the same computationally. What is the difference? Which one is faster? What about val = (double) dat*pow(0.1,2)? How would this change if the exponent increased? Just curious and thought you might know. Anyway, the problem is solved. Thanks for all your help!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AndreyT wrote:

It is hard to imagine a more prominent example of rampant rabid incompetence than an attempt/suggestion to use `pow` here.

 

Why are you so rude?

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A.MacDonald wrote:
I know the two operations are mathematically the same, but they're clearly not the same computationally

 

Take a look at #5 in Brian's signature!

 

wink

 

https://floating-point-gui.de/

 

https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

 

the problem is solved

Then please mark the solution - see Tip #5 in my signature.

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was interested in this thread because I tried to do something similar, except I had only 16-bits into which I tried to coerce a 32-bit IEEE754 float.

I only wanted 2 decimal places and thought it would be easy; however I found edge cases where I lost resolution, so gave up. I multiplied by 100 and offset instead.

 

Anyway; at the risk of being rude. I have re factored your code.

  • I returned a value (returning void makes no sense)
  • I guessed at the float format
  • I removed the first byte because it wasn't used
  • Added a function comment block

 

/**
 * @warning	** THIS CODE IS FAULTY - DO NOT USE **
 * @brief	Decode a 24-bit floating point number in MacDonald format as bytes into IEEE754
 * @note	This format is [ e3 e2 e1 e0 sign m18 m17 m16   m15 m14 m13 m12 m11 m10 m9 m8   m7 m6 m5 m4 m3 m2 m1 m0 ]
 * @param	b0 The byte containing the exponent
 * @param	b1 The byte containing mantissa m15:m8
 * @param	b2 The byte containing mantissa  m7:m0
 * @return	Data bytes converted into IEEE754 double.
 */
double parseMacDonald (uint8_t b0, uint8_t b1, uint8_t b2)
{
    uint8_t exponent = b0 / 16;
    uint8_t sign = (b0 % 16) / 8;
    uint8_t dataUpper = b0 % 8;
    uint32_t mantissa = (int)dataUpper * 2 ^ 16 + (int)b1 * 2 ^ 8 + (int)b2;
    double val = mantissa * pow(10, (-exponent));

    return sign ? -val : val;
}

And now to my main point - I cannot test it. You didn't provide any test cases for us freaks to follow along for either the original failure in #1 nor the 1. 7000000000000002 issue.

 

BEWARE: This code doesn't work as discussed in later posts. See #32  for corrected version.

 

Last Edited: Thu. May 21, 2020 - 11:48 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A.MacDonald wrote:
2^16
A.MacDonald wrote:
2^8
If we try to skirt around the fact that ^ is not "raise to the power of" for a moment then I would point out that 2^16 is 65535 and 2^8 is 256 so while the compiler will flatten these anyway it might have been simpler to just multiply by those constants anyway. But if you were going to do * 65536 and * 256 you may also recognise that any multiplication by a power of 2 in binary terms is just a bit shift anyway. So again, while the compiler may be smart enough to spot this fact anyway you could further help it along it's way with simply <<16 and << 8. In which case I guess:

dat = (int)dataUpper*2^16+(int)b3*2^8+(int)b4;

becomes:

dat = (dataUpper << 16) + (b3 << 8) + b4;

again you can look at hte constraints on these (being uint8_t size) to realise that | could be used in place of +.

 

One thing you have to watch out for is that "int" in an AVR8 is 16 bits so when you start doing <<16's you risk overflow so you might want to cast things up to (uint32_t) to be sure there's enough width to hold the result.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This makes me nervous

 

uint8_t exp

pow(10, -exp)

 

Since exp is promoted to int for the unary minus operation, you get a signed value as intended, which is then converted to double as a parameter to pow, so no problem.

But if you used uint16_t exp, and have 16 bit ints, you would get a very different answer (a large unsigned value).

 

Maybe int8_t exp is more appropriate.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Please consider a pardon for my C OCD wink

N.Winterbottom wrote:
...  I cannot test it.
Ran through a linter that produced one warning

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Now this is interesting - I compiled that code in Microsoft Visual Studio merely by adding it into the project I'm currently working on. It compiles without warning.

BUT simulating the bug MrKendo suggested by declaring

	uint32_t exponent = b0 / 16;

Makes MSVC produce :

1>c:\users\nigel\project\k970\sw19719_win32\source\util.c(207): warning C4146: unary minus operator applied to unsigned type, result still unsigned

 

It pains me to say it but - Well Done Microsoft.

They suppressed the warning when there wasn't a problem and clearly explained the warning when there was a problem.

 

<edit>

OK I'll fess up; I totally missed the XOR thing.

</edit>

 

Last Edited: Wed. May 20, 2020 - 01:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
^ is xor, I believe you have to use math.h pow to get what you want.
On this site, ^ is often used to mean exponentiation in mathematical expressions.

To avoid deceiving people who do not know better,

I steal fortran's ** for exponentiation.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

N.Winterbottom wrote:
It pains me to say it but - Well Done Microsoft.
fyi, the one's at Microsoft also have a file scope linter that's an excellent value (zero price, lots of defect patterns)

-analyze (Code Analysis) | Microsoft Docs

Gamasutra - In-Depth: Static Code Analysis

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If one wanted to play a particularly mean game I could envisage a C++ class where you overload the ^ operator so it does indeed do exponentiation. That could make for some pretty obfuscated code though !

 

"normal" programmers might sit scratching their heads wondering "how on earth does that work? Surely ^ is exclusive OR??"

 

EDIT: as always it seems no idea in this life is ever really new...  https://stackoverflow.com/questions/10511314/overloading-operator-for-integer-type (and yes, I already realised the thing about standard types - you'd need to introduce some kind of "new integers" to make this work)

Last Edited: Wed. May 20, 2020 - 02:10 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
On this site, ^ is often used to mean exponentiation in mathematical expressions.

not just this site - it's pretty widely used where fancy formatting is unavailable (or users just don't know it)

 

To avoid deceiving people who do not know better, I steal fortran's ** for exponentiation.

but putting that into some 'C' code could equally cause some weird results ...

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:
skeeve wrote:
On this site, ^ is often used to mean exponentiation in mathematical expressions.

not just this site - it's pretty widely used where fancy formatting is unavailable (or users just don't know it)

 

To avoid deceiving people who do not know better, I steal fortran's ** for exponentiation.

but putting that into some 'C' code could equally cause some weird results ...

The usual result should be an error message.

With numerical expressions on either side, neither asterisk should be taken to mean dereferencing.

If, presumably by mistake, the right operand is a pointer to number, the code would compile,

but presumably not do what the user intended.

That was OP's situation.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

N.Winterbottom wrote:

I was interested in this thread because I tried to do something similar, except I had only 16-bits into which I tried to coerce a 32-bit IEEE754 float.

I only wanted 2 decimal places and thought it would be easy; however I found edge cases where I lost resolution, so gave up. I multiplied by 100 and offset instead.

 

Anyway; at the risk of being rude. I have re factored your code.

  • I returned a value (returning void makes no sense)
  • I guessed at the float format
  • I removed the first byte because it wasn't used
  • Added a function comment block

 

/**
 * @brief	Decode a 24-bit floating point number in MacDonald format as bytes into IEEE754
 * @note	This format is [ e3 e2 e1 e0 sign m18 m17 m16   m15 m14 m13 m12 m11 m10 m9 m8   m7 m6 m5 m4 m3 m2 m1 m0 ]
 * @param	b0 The byte containing the exponent
 * @param	b1 The byte containing mantissa m15:m8
 * @param	b2 The byte containing mantissa  m7:m0
 * @return	Data bytes converted into IEEE754 double.
 */
double parseMacDonald (uint8_t b0, uint8_t b1, uint8_t b2)
{
	uint8_t exponent = b0 / 16;
	uint8_t sign = (b0 % 16) / 8;
	uint8_t dataUpper = b0 % 8;
	uint32_t mantissa = (int)dataUpper * 2 ^ 16 + (int)b1 * 2 ^ 8 + (int)b2;
	double val = mantissa * pow(10, (-exponent));

	return sign ? -val : val;
}

And now to my main point - I cannot test it. You didn't provide any test cases for us freaks to follow along for either the original failure in #1 nor the 1. 7000000000000002 issue.

 

 

Just for the record, for our intents and purposes, 1. 7000000000000002 is close enough. I was just curious about the computational differences.

 

I guess I'll start by explaining the protocol.

 

Byte 1, bits 0-4 - channel: the device has 32 channels, and these bits indicate which channel to change.

Byte 1, bits 5-7 - command: there are several different commands and these bits indicate that

Byte 1 doesn't have anyting to do with the floating point number

 

Byte 2, bits 0-2 - bits 16-18 of the mantissa

Byte 2, bit 3 - sign bit, 0 = positive, 1 = negative

Byte 2, bits 4-7 - exponent. For our purposes, the exponent will always be negative

 

Bye 3 - bits 8-15 of the mantissa

 

Byte 4 - bits 0-7 of the mantissa

 

The mantissa and exponent are both considered unsigned.

 

The calculation for the floating point value is: val = (-1)^sign * mantissa * 10^(exponent*-1)

 

So if the mantissa is 185, and the exponent is 2, the value would be 1.85. I would point out that I'm aware that there are multiple ways to display some numbers. For example, 1.4 could be (14,1), (140,2), (1400,3), (14000,4), or (140000,5). I never said the protocol was efficient, but it is simple and provides more than enough precision for our purposes. We don't need to display a wide range of numbers, but we need pecision down to 1 mV/1 uA. The largest absolute value that we'll need to display is 125.

 

In the main body of the code, I switched dividing by powers of 2 and modulo for right shift bitwise operator and boolean and (unquestionably more efficient), but I could not do that when calculating the value because the base is 10 and not 2.

 

I tried calculating val two different ways, curiously with different results. One was;

 

val= mantissa/pow(10, exponent);

if(sign == 1){

     val = val*-1;

}

 

The other was:

val = mantissa;

i = 1;

while(i<=exponent){

     val = val*0.1;

     i++;

}

 

Both worked. The difference between the results was so small that it won't make a difference for our purposes. The difference is far smaller than the resolution of our 12 bit DAC or our 29 bit ADC (yeah, that's right, 29 bits), so I'm not concerned. I was just curious about the computational differences.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A.MacDonald wrote:
I never said the protocol was efficient

It looks fine to me.

 

Not too dissimilar to this:

https://en.wikipedia.org/wiki/Decimal32_floating-point_format

I had to struggle with this once. I found testing difficult due to the difficulty of generating test cases as I mentioned previously .

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A.MacDonald wrote:
val = val*0.1;
I guess you know this but just to point out that 0.1 is not a happy binary number!

 

You know how in decimal we have real problems expressing 1/3rd? (is it 0.33 or 0.3333 or 0.3333333333 or ....?)

 

The same is true for 1/10 (a seemingly innocuous looking fraction) in binary. Its an infinitely recurring fraction so you can get close to 1/10 (or 0.1 if you prefer) but no matter how many bits you throw at it you'll never accurately represent it. (it is 1100110011001100..... to infinity and beyond)

 

It's why in he very first calculators if you did 17 / 10 * 10 you might not get back to 17. (probably 16.999998 or something)

 

The inaccuracy may or may not matter.

Last Edited: Wed. May 20, 2020 - 04:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Brian Fairchild wrote:

AndreyT wrote:

 

It is hard to imagine a more prominent example of rampant rabid incompetence than an attempt/suggestion to use `pow` here.

 

Why are you so rude?

 

"Rude"? The word you are looking for is "curt", in its dry, pronouncedly professional meaning.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
I guess you know this but just to point out that 0.1 is not a happy binary number!

Oooh - That's a very valid point actually - especially now; because you've been chastised for using pow() and are doing val = val*0.1; in a loop. This will only compound any error.

 

You could do a small (16 entries) look-up-table for the powers of ten and divide only once.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AndreyT wrote:
The word you are looking for is "curt"
Nope. 
AndreyT wrote:
rabid incompetence
Rude.

 

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ignoring the emotive words 'rabid' and 'incompetence', I'm still trying to figure out what is so bad about using pow()? Sure, it's not the most efficient function, but it does what it says on the wrapper. If pow() is so bad, I wonder what other functions I must avoid, lest I give the appearance of incompetence?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Pow() does floating point math for floating point base and exponent, essentially by doing exp(exponent*log(base))

if the exponent and base are integers (and especially if the exponent is smallish), this is almost certainly far slower than an integer multiply in a loop, and is somewhat likely to introduce rounding errors.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I thought there may be inaccuracies introduced, but I tested it for various exponents within the limited range and found none.

This was using powf() on MSVC however. I suspect there's some special cases detected for powf(10, integer);

 

Anyway I don't like leaving faulty code around on avrfreaks so I'm compelled to correct my #14.

 

Notes:

  • I didn't use pow() because it adds 1400 flash bytes, so used a LUT instead.
  • I converted to float because I couldn't see any double support on avr-gcc within libm.a.

 

/**
 * @brief	Decode a 24-bit floating point number in MacDonald format as bytes into IEEE754
 * @note	This format is [ e3 e2 e1 e0 sign m18 m17 m16   m15 m14 m13 m12 m11 m10 m9 m8   m7 m6 m5 m4 m3 m2 m1 m0 ]
 * @param	b0 The byte containing the exponent
 * @param	b1 The byte containing mantissa m15:m8
 * @param	b2 The byte containing mantissa  m7:m0
 * @return	Data bytes converted into IEEE754 float.
 */
float parseMacDonald (uint8_t b0, uint8_t b1, uint8_t b2)
{
	static const float __flash pwrTen[16] =	{
		1e+0f, 1e+1f, 1e+2f,  1e+3f,  1e+4f,  1e+5f,  1e+6f,  1e+7f,
		1e+8f, 1e+9f, 1e+10f, 1e+11f, 1e+12f, 1e+13f, 1e+14f, 1e+15f,
	};

	uint8_t exponent = b0 >> 4;
	uint8_t sign = (b0 & 0x08); // I only need 0 or non-zero here.
	uint8_t dataUpper = b0 & 0x07;
	uint32_t mantissa = (uint32_t)dataUpper * 65536ul + ((uint16_t)b1 * 256 + b2);

	float val = mantissa / pwrTen[exponent];

	return sign ? -val : val;
}

 

Last Edited: Thu. May 21, 2020 - 01:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

N.Winterbottom wrote:

  • I converted to float because I couldn't see any double support on avr-gcc within libm.a.

There's some in-work activity on that.

Libf7 | avr-gcc - GCC Wiki

avr-gcc + 64-bit double | AVR Freaks

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 I thought there may be inaccuracies introduced, but I tested it for various exponents within the limited range and found none.

This was using powf() on MSVC however. I suspect there's some special cases detected for powf(10, integer);

For example: On the Arduino Forums  (first thing to show up in a search for "pow" there!)

They usually boil down to the avr single-precision (only 24bits of mantissa - about 7 digits worth) yield results of 99.99999 instead of 100.  If the calculations had proceded with floating point, the answer probably would have come out ok, but immediate and implicit conversions back to int (without rounding!) yield wrong answers.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A.MacDonald wrote:
Quick question for y'all: i noticed that the results of val = (double) dat*0.1*0.1 were not equal to val = (double) dat/pow(10,2). The first one was  1.700000000000000, and the second was 1.7 even. I know the two operations are mathematically the same, but they're clearly not the same computationally. What is the difference? Which one is faster? What about val = (double) dat*pow(0.1,2)? How would this change if the exponent increased?
The generic answer is that though useful exceptions exist,

arithmetic operations on floating point numbers rarely produce floating point numbers of the same format.

The result needs to be rounded.

Because of this, operations that are associative on real numbers are not when attempted on floating point numbers:

(a+b)+c need not equal a+(b+c).

For an obvious example consider a=1, b=-1, c=2**-66 .

 

At one time, neither IEEE nor C guaranteed any accuracy for pow.

My man page for pow mentions some special cases,

but otherwise says nothing about pow's accuracy.

On any except the most weird and possibly nonexistent platforms,

x*x will be at least as accurate as pow(x, 2) for the type of x.

On most platforms, x*x will be rounded to nearest.

pow cannot do better.

 

IEEE does require sqrt(x) be good to the last bit.

pow(x, 0.5) could not be better.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"Dare to be naïve." - Buckminster Fuller