Floating Point


  • Hero Member

    I gather that floating point is still converted to/from a string representation in 1.4

    A quick scan seems to indicate that the GCC software 4 byte floating point representation for the Arduino is IEEE compatible, as is RPi and Intel chip based software. Maybe I missed something, tho.

    I'm not sure about endian-ness for all the platforms (in Floating Point format, which could differ from integers). But I'd think that could be adjusted for a MySensor OTA standard if need be.

    So I'm probably missing something, but it would comfort my mind to understand this better... why can't MySensors transmit binary floating point?

    Thanks


  • Mod

    @Zeph The issue here is called serialization -- how to store data in a defined way which can be exchanged between different hardware/platforms.
    If data is just being exchanged between ATMega's then I don't see a problem in just storing the float value directly in the native format. When exchanging between different architectures we should take endianness & floating point format into account.
    This http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#serialization describes some simple code to store floating point in IEEE-754 format, but I think it is still too much overhead for ATMega's...

    I think the focus should lie on fast (de)serialization for the Arduino platform, to assure conversion between sensor and gateway has little overhead (both computational & storage).

    IMHO we should just store floating point values in the format of the primary MySensors platform (Arduino with ATMega328) and convert on other platforms when required. In general other platforms will have more processing power, so it makes sense to let them 'feel the pain' of conversion, and not have the default platform pay all the time for standardization.


  • Plugin Developer

    I believe the pi is bi (ARM), but current default implementation is little-endian just like the Arduino (if i remember correct).


  • Hero Member

    @Yveaux
    That's pretty much what I was leading to as well.

    The primary platform currently is GCC-AVR software floating point from AVR to AVR over the air. Since this is the lowest powered node type and the most ubiquitous, it's floating point format would
    seem natural for the OTA standard. We don't tend to need doubles in this niche.

    And what I was reading was that this is IEEE standard format, so the only question in binary adjustment to another IEEE floating point system should be possibly changing endianness.

    Even if a Raspberrry Pi were used as the wireless hub directly, what is the issue? Does it store 4 byte floating point numbers differently than the Arduino compiler? If so can it not swap bytes as needed? (As you say, it has more processing and memory resources).

    So I was curious why floats are not already sent in binary format over the air.


    I've come up with one hypothesis. Even if we send floats OTA as binary, they may need to be converted to text strings for the API. I do see that there is a role for describing how many digits of precision make sense to avoid temperatures like 23.4999987 degrees. So keeping the precision of a variable as a hint could be useful. But I'm not yet seeing a good reason to accept the overhead of converting to and from a text string for OTA floats.

    (Of course, I'm thinking that if you are tracking the type and name of a variable, and for floats the precision, then you could similarly track a scaling factor for variables transferred as integers. Temp could be reported as a float with 1 decimal place of accuracy, or as a two byte integer with a scaling factor of 0.1)


  • Mod

    @Zeph Maybe @hek can comment on this; after all he is the architect 😉


  • Admin

    @Yveaux

    Yes, I had worries on how the RPi compiler treated IEEE754 with respect to negative numbers and byte order. If someone can swear on their mothers grave that RPi uses the same standard we could send them in a binary format over the air. But we still need a conversion over serial line protocol (which must be handled by gateway).


  • Mod

    @hek said:

    we still need a conversion over serial line protocol (which must be handled by gateway)

    It's better to have the gateway do it, then the sensor nodes. Sensor nodes are low power and have limited resources (w.r.t. gateway and application hardware) and should not have to worry about floating point conversions.


  • Plugin Developer

    The pi uses little-endian as default. And:

    pi@pidome-server ~ $ readelf /usr/bin/gcc -A
    Attribute Section: aeabi
    File Attributes
    Tag_CPU_name: "6"
    Tag_CPU_arch: v6
    Tag_ARM_ISA_use: Yes
    Tag_THUMB_ISA_use: Thumb-1
    Tag_FP_arch: VFPv2
    Tag_ABI_PCS_wchar_t: 4
    Tag_ABI_FP_denormal: Needed
    Tag_ABI_FP_exceptions: Needed
    Tag_ABI_FP_number_model: IEEE 754
    Tag_ABI_align_needed: 8-byte
    Tag_ABI_align_preserved: 8-byte, except leaf SP
    Tag_ABI_enum_size: int
    Tag_ABI_HardFP_use: SP and DP
    Tag_ABI_VFP_args: VFP registers
    Tag_DIV_use: Not allowed


  • Hero Member

    The Raspberry Pi definitely does use IEEE 754 floating point.

    I'm not certain about the byte order, but that should be easy to test.

    union test_t {
       float f;
       uint8_t b[4];
    } myUnion;
    
    myUnion.f = 3.14159265;
    for(int i = 0; i < 4; i++) {
        Serial.print(myUnion.b[i], HEX);
        Serial.print(" ");
    }
    Serial.println();
    

    Swap out a printf on the Rpi

    (Note: I am not assuming that an architecture which is little endian for integers must be little endian for floats - tho it usually is)


  • Mod

    I would also like you to think about a fixed point format (http://en.wikipedia.org/wiki/Fixed-point_arithmetic).
    A lot of sensor values can be represented in e.g. 8.8 or even 16.16. Think of e.g. temperatures (range -127..+127 is usually sufficient and I've yet to see an affordable sensor reporting in more than 256 steps per degree ), humidity (0..100%, 256 steps/degree), battery level (0..100%) etc.
    Fixed point arithmetic requires very little resources compared to floating point and has no trouble converting between different architectures (apart from endianness).
    For full efficiency sensor libraries should also support this format, as it doesn't make much sense to have a sensor library report in floating point and convert this to fixed point by MySensors.
    A good Arduino-style fixed point library would help IMHO. Did a quick google on the subject, but didn't find much (apart from https://code.google.com/p/libfixmath/)

    I think support for real floating point values should still be possible, but this could be an interesting addition.

    What do you think?


  • Plugin Developer

    @Zeph
    -mlittle-endian
    Generate code for a processor running in little-endian mode. This is the default for all standard configurations.
    -mbig-endian
    Generate code for a processor running in big-endian mode; the default is to compile code for a little-endian processor.

    At: https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html

    In my Java app (but then again on the PI is also default little-endian) The below is default used with Arduino based connected devices and on purpose hinted little-endian, just in case.

    public static float byteArrayToFloat(byte[] bytes) {   // Byte to float conversion
        return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getFloat();   
    }

  • Hero Member

    @hek
    @Yveaux said:

    I would also like you to think about a fixed point format

    See my suggestion above about defining an optional scaling factor for each variable, as we can define a precision for each floating variable now.

    Simple decade scaling: integer factors of ten, using the same configured integer that would define precision for floats.

    or

    Flexible scaling: configure a floating point factor by which the integer OTA value would be multiplied to give real world units.

    So a temperature of 29.7 could be sent as a 16 bit integer 297 with the gateway knowing (from configuratiion) that it must be multiplied by 0.1 before being converted to a string. Either approach could handle that.

    The flexible version could also handles things like angles that are reported in degrees, or 0..1023 or radians or whatever, with the full accuracy of the sensor.


    I think you are suggesting that we adopt one or more standard fixed point formats as additional OTA variable formats (as well as supporting the math function in the node library). I can certainly see value in that, as well. Like floating point, you would need to configure a precision when converting to decimal values (since 0.1 is not an accurate binary fraction in fixed point either). So 29.7 degrees would be encoded as 29*256 + ((256 * 7) / 10). = 7603, and converted to a float that would be 29.69921875. If like a float this fixed point value was configured with one digit of decimal accuracy, we'd get the 29.7 value again.


    Either of these might sometimes let a node avoid the floating point libraries entirely in many cases, even when it wants to report non-integer values.

    Could be handy if we ever get ATtiny nodes.


  • Admin

    @Yveaux said:

    http://en.wikipedia.org/wiki/Fixed-point_arithmetic

    Yep, would also be a good addition. Floating points is really crazy inefficient to use on an Arduino.
    I will look at sending floats binary now... my secret knock sensor will have to wait :(.
    Does anyone want to help on the fixed point stuff?


  • Mod

    @hek said:

    Does anyone want to help on the fixed point stuff?

    I can try to put some skeleton together to get the interface right, as this is probably the hardest part.


  • Admin

    @Yveaux said:

    I can try to put some skeleton together to get the interface right, as this is probably the hardest part.

    Super.


  • Hero Member

    @hek @Yveaux
    Let me see if I am understanding.

    The payload types would be enhanced.

    typedef enum {
        P_STRING, P_BYTE, P_INT16, P_UINT16, P_LONG32, P_ULONG32, P_CUSTOM
    } payload;
    

    to add an 8:8 and/or 16:16 fixed point formats, eg: P_FIX8P8 or P_FIX16P16. I'm guessing only signed fixed point, no unsigned?

    And to add a 4 byte binary floating point P_FLOAT32?

    One small suggestion: put P_CUSTOM first, so its numeric code doesn't change when you add additional formats. Or if it's too late for that, we can skip over P_CUSTOM for these new formats.

    And also the payload setters:

    MyMessage& set(const char* value);
    MyMessage& set(uint8_t value);
    MyMessage& set(double value, uint8_t decimals);
    MyMessage& set(unsigned long value);
    MyMessage& set(long value);
    MyMessage& set(unsigned int value);
    MyMessage& set(int value);
    

    would be enhanced to support these types.

    MyMessage& set(double value, uint8_t decimals);
    

    could be unchanged as seen by the user even if the underlying OTA representation became binary. But we might add something like:

    MyMessage& set(fix8p8 value, uint8_t: decimals);
    MyMessage& set(fix16p16 value, uint8_t: decimals);
    

    (the decimals parameter is needed for this like for floats, as described earlier)

    This implies creating new C++ types, in this example "fix8p8", which is basically a int16_t with an implicit radix point in the middle.

    Adding is simple. Multiply of fix8p8 is easy because you can use a long as temp before renormalizing, but multiply of fix16p16 gets trickier, of course.


    Another discussion to have before it's set in stone...

    Adding fixed point support both OTA and within library code has value, so I'm not against it. Getting the library right and educating users is going to be some work, tho. I use fixed point math fairly often, but it definitely has some gotchas that we are biting off.

    The concept of having a scaling factor (see a few messages back) may be an easier step to implement and educate. It's an easy concept: every integer step represents X units, so the integer value must be multiplied by the scaling factor to get the real value in units. Default = 1.0 with both the same, as now.

    The simplest version just scales by factors of 10, and could be implemented by adding a second integer to the set() function. In this version to send -12.5 you use set(-125,1), or to send 0.14 you use set(14,2). This can be interpreted into a string without even using floating point math, you just adjust where to insert a decimal point in a printed integer.

    (The slightly more complex version would use an arbitrary floating point factor as the scale, so you could use 0.1 or 3.56 or whatever).

    (Aside: from the viewpoint of the gateway, the fixed point enhancement is equivalent to having a fixed scaling factor of 2*-8 or 2^-16)

    These enhancements are not mutually exclusive, but I would think that one of the scaled integer version might be easier to implement and understand as a first step.


  • Mod

    @Zeph said:

    (the decimals parameter is needed for this like for floats, as described earlier)

    I don't see why the decimals parameter is needed. Currently it is used for the amount of decimals converted to textual presentation. This is not required for fixed point presentation (unless you want a scaling factor).
    IMHO scaling just complicates things too much -- you also need to exchange the scaling factor with the gateway.

    This implies creating new C++ types, in this example "fix8p8", which is basically a int16_t with an implicit radix point in the middle.

    Adding is simple. Multiply of fix8p8 is easy because you can use a long as temp before renormalizing, but multiply of fix16p16 gets trickier, of course.

    My idea is to just wrap the new types in a class library, which allows for easy conversion and maths with these new fixedpt types.

    Getting the library right and educating users is going to be some work, tho. I use fixed point math fairly often, but it definitely has some gotchas that we are biting off.

    The library should shield regular users from the internals and pitfalls of fixed point. Most sketches just get a value from a sensor library and pass it on to MySensors, without modifying the value.
    As part of this exercise we also have to modify these libraries which return their values in float-format, as it doesn't make sense to keeps floats in partly...


  • Admin

    @Zeph said:

    The payload types would be enhanced.

    typedef enum {
        P_STRING, P_BYTE, P_INT16, P_UINT16, P_LONG32, P_ULONG32, P_CUSTOM
    } payload;
    

    Darn, just realized we only got 3 bits to describe payload type. We need another one to fit the new ones.

    MyMessage& set(double value, uint8_t decimals);
    

    Shouldn't this be set(float, uint8_t). Wouldn't it be confusing to have double-argument when only sending 32-bit float?


  • Mod

    @hek

    P_INT16, P_UINT16, P_LONG32, P_ULONG32

    Do you really need the unsigned versions UINT16 and UINT32 ?
    I would say stick to the signed ones -- this frees 2 values for float and fixed point.


  • Plugin Developer

    @hek

    Shouldn't this be set(float, uint8_t). Wouldn't it be confusing to have double-argument when only sending 32-bit float?

    Also the Atmega based boards do not support double, well they do in naming but are float precisions


  • Mod

    @John then let's call them float, for clarity


  • Plugin Developer

    @Yveaux said:

    then let's call them float, for clarity

    Agree


  • Admin

    Just pushed the float changes.

    To free up some bits in header for the new fixed point types (and simplify things) I'm considering reducing the commandTypes to just 3 values (SET, REQ, INTERNAL) the rest (PRESENTATION, STREAM) will be moved to be INTERNAL messages.
    I could make serial interface unaffected by this change. But I'd rather remove it there as well.

    @Yveaux . Regarding remove the unsigned variant (e.g. ULONG). It is actually good to keep this. As there actually are some sensors reporting large numbers like meter-ticks which can be huge.


  • Mod

    @hek Maybe not for 1.4, but you should consider removing a lot of the data from the header and leave only the routing info & message type in.
    Depending on message type you then get a 'nested' header which tells you about the message-type specifics.
    This also will help in the struggle to store data format types, for which you've now reserved 3 bits. They are always sent, also when there's no SET/GET data present in the message. Then you simply reserve e.g. a byte which will go a long way...


  • Admin

    @Yveaux said:

    This also will help in the struggle to store data format types, for which you've now reserved 3 bits. They are always sent, also when there's no SET/GET data present in the message. Then you simply reserve e.g. a byte which will go a long way...

    Darn... you are so right..


  • Mod

    @hek Hey, its my job 😉


  • Hero Member

    @Yveaux said:

    @Zeph said:

    (the decimals parameter is needed for this like for floats, as described earlier)

    I don't see why the decimals parameter is needed. Currently it is used for the amount of decimals converted to textual presentation.

    I was think of when packets are converted to the comma separated textual representation for the API.

    Convert 25.7 degrees to fixed point at the node, then at the gateway convert the fixed point it to a text string, and you'll see what I mean. It's not for the scaling option.

    IMHO scaling just complicates things too much -- you also need to exchange the scaling factor with the gateway.

    There are tradeoffs either way. In the current architecture, to support scaling you'd need to at least tell the gateway the scaling factor as part of the one-time presentation configuration. (Alternately, the gateway could retrieve the scaling factor along with type and name from local configuration, rather than receiving all of those OTA, but that's another discussion)

    Beyond that there's no need for new libraries, and it's easy to explain.

    However, I understand that you are excited by the fixed point functionality (which could be useful for more than just OTA encoding). I don't want to discourage that exploration. I look forward to some examples of encoding at the node end, and decoding at the gateway end, for some sensors like the DHT-22 or 18B20.

    This implies creating new C++ types, in this example "fix8p8", which is basically a int16_t with an implicit radix point in the middle.

    Adding is simple. Multiply of fix8p8 is easy because you can use a long as temp before renormalizing, but multiply of fix16p16 gets trickier, of course.

    My idea is to just wrap the new types in a class library, which allows for easy conversion and maths with these new fixedpt types.

    Yes, that was what I was guessing. Go for it!

    As part of this exercise we also have to modify these libraries which return their values in float-format, as it doesn't make sense to keeps floats in partly...

    Agreed. It would be nice if we rarely needed to even link the floating point library in nodes. (And it would make an ATtiny based node more feasible someday).


  • Hero Member

    @hek said:

     MyMessage& set(double value, uint8_t decimals);
    

    Shouldn't this be set(float, uint8_t). Wouldn't it be confusing to have double-argument when only sending 32-bit float?

    I was quoting an excerpt of the current system. I would tend to agree with changing that to float, just for clarity of intent, even though they are the same in GCC for the AVR


  • Mod

    @Zeph said:

    to support scaling you'd need to at least tell the gateway the scaling factor as part of the one-time presentation configuration

    This method (and the same holds for the decimals-parameter) seems attrictive, but has a few drawbacks:

    • The presentation message currently has no 'guaranteed' delivery; we would need to change that as without this info the gateway cannot interpret the incoming data
    • It also has to be sent to the sensor (actuator actually) nodes from the gateway when data goes the other way. No 'presentation' mechanism from gateway to sensor currently exists.
    • It places an administration burden on the gateway, and possibly on actuators

    Beyond that there's no need for new libraries, and it's easy to explain.

    Possibly, but when you start mixing up values with different scaling factors or want to do (simple) maths on them the story changes completely...


  • Mod

    I've been google'ing around looking for existing fixed point c++ libraries which fit the ATMega and tend to try out the following: https://code.google.com/p/libfixmath
    It has regular updates, a unit test suite, impressive performance advantages (especially addition/substract, see https://code.google.com/p/libfixmath/wiki/Benchmarks), has been tested on ATMega and uses an MIT license.
    It only supports 16.16, but other derivates like 8.8 seems doable.

    Rolling my own from start is too much work for me, as implementation is tricky at some points (unit tests are a requirement IMHO)

    Anyone has a better suggestion?


  • Admin

    @Yveaux

    First impression: looks good!


  • Plugin Developer

    @hek

    Just pushed the float changes.

    Just used the updated lib. I Can confirm the floats.


  • Mod

    I 'ported' libfixmath to the Arduino (see https://github.com/Yveaux/Arduino_fixpt)
    Ran into a lot of internal compiler error issues and had to convert the unittests to C++ variants (no need to test C-implementation only) which revealed some issues in the C++ wrapper.

    Anyway, stuff is running now and I have some preliminary benchmark results.
    I create a benchmark sketch which runs a number of multiplications/divisions/additions/subtractions/sqrt.

    Code size results:

    Bare (no float/fix16)    450
    double                   2514
    fix16                    2956
    

    So code size slightly increases with fixed point calculations.

    Calculation performance results:

    with overflow detection and rounding:

    Op      double	fixpt      speed improvement fixpt over double
    Mult    870448    1708304	50.95%
    Div     2303348   2986484	77.13%
    Add     858280	296760     289.22%
    Sub     858108	296644     289.27%
    Sqrt    13164     15444      85.24%
    

    without overflow detection and rounding (FIXMATH_NO_OVERFLOW & FIXMATH_NO_ROUNDING defined)

    Op      double	fixpt	  speed improvement fixpt over double
    Mult    870452    1699392	51.22%
    Div     2303348   4794636    48.04%
    Add     858280	82568	  1039.48%
    Sub     858108	72776	  1179.11%
    Sqrt    13168     15528	  84.80%
    

    So additions & subtractions are significantly faster using Fix16 (we can probably live without overflow detection & rounding) and mul/div/sqrt are slower...

    First conclusion: we don't gain in flash code space and don't gain (on average) in code execution speed. Maybe very specific applications can benefit from Fix16 implementation on AVR, but I seriously doubt if it's worth all the effort....

    Seems like the AVR floating point library is very efficient, both in code size and execution speed.

    Please review my code as I might be missing something...


  • Admin

    @Yveaux

    Ok, good investigation! As you say. It might not be worth it with this small gain.


  • Mod

    @hek I implemented a basic version of the 8.8 fixed point version of the library.
    It doesn't pass the unittests completely yet, but here are the first results (sqrt not implemented yet):

    Code size results:

    Bare (no float/fix8)    450
    double                  1214
    fix16                   2062
    

    So, again code size slightly increases with fixed point calculations.

    with overflow detection and rounding:

    Op      double	fixpt	speed improvement fixpt over double
    Mult    840016	291008	288.66%
    Div     2240552   812044	275.92%
    Add     792564	133344	594.38%
    Sub     801984	136676	586.78%
    

    without overflow detection and rounding (FIXMATH_NO_OVERFLOW & FIXMATH_NO_ROUNDING defined)

    Op      double	fixpt	speed improvement fixpt over double
    Mult    840012	312440	268.86%
    Div     2240548   790992	283.26%
    Add     792564	56204	1410.16%
    Sub     801972	52960	1514.30%
    

    This is a very nice speed increase in all cases, especially when ignoring overflow detection and rounding.

    Conclusion: Using 8.8 fixed point can definately bring the calculation time and therefore power consumption down! The use-case for 8.8 values is however limited, but for e.g. a temperature or humidity sensors with limited range & accuracy it seems usable.
    Using 16.16 fixed point values has no clear advantage over using floating point values.


  • Admin

    @Yveaux

    Ok, so 8.8 might serve a purpose then...


  • Mod

    @hek Yep.
    So, what shall I do? Invest some more time and get the 8.8 in (and modify some MySensors examples/libraries), or shall I park it for possible future usage?


  • Admin

    @Yveaux

    Maybe we could park this for now (if you don't have an super urge to get it in to 1.4). We can all see the benefits and have it in the pipe for future versions.


  • Mod

    @hek Ok, agree. Doesn't bring enough right now to justify the effort.


  • Hero Member

    Too bad the 16p16 is so slow and large, cool that 8p8 is better.
    I suspect that either could be far faster if done in assembly, as floating point must be.

    @Yveaux - can you share the test program you used?


    Let's not throw the baby out with the bathwater. There are two problem domains here.

    1. Calculations using non-integer values as operands and results (plus/minus/divide/multiply/sqrt).

    2. Representing non-integer values over a network, and converting between this representation and the textual display used by humans (and whatever format is used by sensors).

    Fixed point can be great for the first domain, although in this case the C/C++ implementation has some tradeoffs in size and speed compared to the compiler supported floating point.

    But fixed point math is not as well targeted at the second domain. For a sensor value like 29.7 degrees, fixed point has an inexact and sometimes cumbersome representation. To see what I mean, follow the whole chain of converting a DHT-22 value into fixed point for OTA transport, and then into a comma separated string representation for consumption by a home automation interface (and display to a user). The time and library size will be dominated not by basic arithmetic operations among fixed point values, but by the conversions into and out of that format. The DHT-22 value can be relatively efficiently converted to a 2 byte integer 297 and a one byte scaling factor 1; and those can be converted to the string "297" and then "29.7" without a large library.

    This is NOT to disparage fixed point math where it works well.


  • Mod

    @Zeph said:

    can you share the test program you used?

    You can find it at https://github.com/Yveaux/Arduino_fixpt


Log in to reply
 

Suggested Topics

0
Online

11.2k
Users

11.1k
Topics

112.5k
Posts