Home AMX User Forum AMX General Discussion

Ieee 754

I know this subject have been discussed in other threads:
http://www.amxforums.com/showthread.php?t=2809&highlight=floating

but my question is different (i think...)

I'm getting a 32bit hex code that represents a temperature.
I need to convert it to a float number. i have been told that i need to use IEEE754.

Any idea or example?

Comments

  • jweatherjweather Posts: 320
    First step is to make sure you're really getting valid values back from the device. You can do this without writing any code by checking the hex value at http://www.rit.edu/~meseec/eecc250-winter99/IEEE-754hex32.html -- does it come back with the value you're expecting from the device? If not, you need to figure out the formatting issues first. One quick thing to try would be swapping the first and second bytes.

    If it really is an IEEE 754 32-bit ("single precision") value, here is some code to convert it. It does not handle INF, NaN, or any other "denormalized" numbers, but hopefully you won't encounter those from your thermostat unless somebody's been playing with set_outdoor_temperature again. Code is verbose to help you understand it and to help the NetLinx compiler understand it -- it seems to throw a fit about multiple type_casts on a line.
    DEFINE_FUNCTION float parsefloat(long input) {
        integer exp, signbit;
        long fraction, bit;
        integer e, i;
        float sign, pow, m, b, v;
    
        exp = type_cast((input >> 23) & $ff);
        fraction = input & $7fffff;
        signbit = type_cast(input >> 31);
    
        if (signbit)
    	sign = -1.0;
        else
    	sign = 1.0;
    
        if (exp == 127) return 1.0;
        if (exp < 127)
    	e = 127 - exp;
        else
    	e = exp - 127;
        pow = type_cast(1 << e);
        if (exp < 127)
    	pow = 1 / pow;
        
        m = 1.0;
        b = 1.0;
        for (i=23; i>0; i--) {
    	b = b / 2.0;
    	bit = 1 << (i-1);
    	if ((fraction & bit) != 0) {
    	    m = m + b;
    	}
        }
        v = sign * pow * m;
        return v;
    }
    
  • RonenRonen Posts: 55
    Works great

    jweather,
    I have tested the function and it gives the correct values as the thermostats.

    Thanks for the help, saved me a big headache, and the best thing is that I learned something
    new :)

    Thank you
  • jweatherjweather Posts: 320
    You're welcome. I learned something, too... I didn't know anybody actually used IEEE 754 in a serial protocol. Seems like a pretty clear case where representing the number to ASCII would be preferable, or even a fixed-decimal representation (how many significant figures do you need for a HVAC temperature?) What make/model t-stat was this, by the way?

    Forgot to link this last time: http://en.wikipedia.org/wiki/IEEE_754 has some nice step-by-step explanations of the conversion process, and should help the code above to make more sense.
  • RonenRonen Posts: 55
    More detailes

    The Company is located here in Israel and is called "Dalkina" and the product name is "UniSense", its not a normal thermostat as you can understand... it also watches water boiler temp, pool temp and presures in the pipes around the house, also Rack room voltage and Amp usage.
    You can speak with the Unisense CPU with Modbus Tcp which is pretty easy, but, you have to use hex values, that's why they used this Ieee thingy.

    i can show on the amx panel almost everything that is going on in the house with this system... pretty cool.

    If you want more details about the company just tell me.
  • Joe HebertJoe Hebert Posts: 2,159
    jweather wrote:
    I didn't know anybody actually used IEEE 754 in a serial rotocol.
    Johnson Controls Metasys N2 Protocol uses IEEE-754 for their analog points (e.g. setpoints, temperature) or at least they did at one time. It?s been many moons since I worked with it. If I ever run into again the links and code you posted will come in handy.
  • AMXJeffAMXJeff Posts: 450
    Very good!!!!! Plus nice code!!!!!
    jweather wrote:
    First step is to make sure you're really getting valid values back from the device. You can do this without writing any code by checking the hex value at http://www.rit.edu/~meseec/eecc250-winter99/IEEE-754hex32.html -- does it come back with the value you're expecting from the device? If not, you need to figure out the formatting issues first. One quick thing to try would be swapping the first and second bytes.

    If it really is an IEEE 754 32-bit ("single precision") value, here is some code to convert it. It does not handle INF, NaN, or any other "denormalized" numbers, but hopefully you won't encounter those from your thermostat unless somebody's been playing with set_outdoor_temperature again. Code is verbose to help you understand it and to help the NetLinx compiler understand it -- it seems to throw a fit about multiple type_casts on a line.
    DEFINE_FUNCTION float parsefloat(long input) {
        integer exp, signbit;
        long fraction, bit;
        integer e, i;
        float sign, pow, m, b, v;
    
        exp = type_cast((input >> 23) & $ff);
        fraction = input & $7fffff;
        signbit = type_cast(input >> 31);
    
        if (signbit)
    	sign = -1.0;
        else
    	sign = 1.0;
    
        if (exp == 127) return 0.0;
        if (exp < 127)
    	e = 127 - exp;
        else
    	e = exp - 127;
        pow = type_cast(1 << e);
        if (exp < 127)
    	pow = 1 / pow;
        
        m = 1.0;
        b = 1.0;
        for (i=23; i>0; i--) {
    	b = b / 2.0;
    	bit = 1 << (i-1);
    	if ((fraction & bit) != 0) {
    	    m = m + b;
    	}
        }
        v = sign * pow * m;
        return v;
    }
    
  • jweatherjweather Posts: 320
    Someone reading this thread asked for code to convert from a float to a 32-bit IEEE754 value. Here's some quick and dirty code to handle values up to +/-128, with a resolution of about 0.01. Plenty for temperature, but you may want to adapt the code for other applications if you need more range or accuracy.

    You may need to change the byte order of outp if the device wants big-endian values.
    DEFINE_FUNCTION long encodefloat(float inp) {
        long outp;
        float factor, frac;
        sinteger exp;
        integer sign, expabs, mbit;
        
        sign = 0;
        if (inp < 0.0) { sign = 1; inp = inp * -1.0; }
        
        exp = 7;
        factor = 128.0;
        frac = inp;
    
        while (factor > 0.01) {
    	if (frac >= factor) {
    	    frac = frac - factor;
    	    factor = factor / 2.0;
    	    break;
    	}
    	exp = exp - 1;
    	factor = factor / 2.0;
        }
    
        expabs = type_cast(exp + 127);
    
        outp = expabs << 23;
        outp = outp | (sign << 31);
    
        mbit = 22;
        while (factor > 0.01) {
    	if (frac >= factor) {
    	    frac = frac - factor;
    	    outp = outp | (1 << mbit);
    	}
    	mbit = mbit - 1;
    	factor = factor / 2.0;
        }
    
        return outp;
    }
    

    I'm not sure if there's an easy way to put a long into a string as four bytes. Here's how I would do it:

    send_string DevDV, "outp>>24, (outp>>16)&$FF, (outp>>8)&$FF, (outp&$FF)";

    Or if it wants big-endian byte order:

    send_string DevDV, "(outp&$FF), (outp>>8)&$FF, (outp>>16)&$FF, outp>>24";

    Again, remember the online IEEE754 converter tool for testing your inputs and outputs (new URL): http://people.rit.edu/meseec/eecc250-winter99/IEEE-754hex32.html
  • markbsuremarkbsure Posts: 44
    JWeather

    Thanks for posting your code to deal with IEEE-754 conversions.

    I'm in the process of writing a module to control an HVAC system called Cylon, which within its protocol uses IEEE-754 to send and receive analog values (primarily temperature).

    Within a standard data string, alongside hex values for 'site number', 'unit address' etc, there will be 4 hex numbers which are the IEEE-754 representation of the temperature as a float data type.

    I've checked some example values the HVAC system sends to me using IEEE-754 website tool you referenced and it's definitely IEEE-754.

    I'm a little confused how I would take the 4 hex values I am receiving as a string, and pass them into your first function "parseFloat", as it requires a LONG data type....

    Thanks
  • PhreaKPhreaK Posts: 966
    The four bytes that you receive will be the components that make up the IEEE-754 encoded float - bits 0..7, 8..15, 16..23 and 24..31.

    You might be interested in some of the encoding/decoding functions from the NetLinx Common Libraries math library. It takes advantage of the string_to_variable internal function for the IEEE-754 conversion so it all takes place outside of NetLinx land. Check out the the raw_be_to_long function for conversion from the byte array to a long before decoding the floating point value.
  • a_riot42a_riot42 Posts: 1,624
    jweather wrote: »
    DEFINE_FUNCTION float parsefloat(long input) {
        integer exp, signbit;
        long fraction, bit;
        integer e, i;
        float sign, pow, m, b, v;
    
        exp = type_cast((input >> 23) & $ff);
        fraction = input & $7fffff;
        signbit = type_cast(input >> 31);
    
        if (signbit)
    	sign = -1.0;
        else
    	sign = 1.0;
    
        if (exp == 127) return 0.0;
        if (exp < 127)
    	e = 127 - exp;
        else
    	e = exp - 127;
        pow = type_cast(1 << e);
        if (exp < 127)
    	pow = 1 / pow;
        
        m = 1.0;
        b = 1.0;
        for (i=23; i>0; i--) {
    	b = b / 2.0;
    	bit = 1 << (i-1);
    	if ((fraction & bit) != 0) {
    	    m = m + b;
    	}
        }
        v = sign * pow * m;
        return v;
    }
    

    Where does this code come from? Is it in the public domain for all to use?
    Paul
  • markbsuremarkbsure Posts: 44
    Thankyou Kim!

    That is a very useful math library, I'll test it out later today.
  • jweatherjweather Posts: 320
    a_riot42 wrote: »
    Where does this code come from? Is it in the public domain for all to use?
    Paul

    I wrote it, translating the Wikipedia entry on IEEE754, which is based on the spec. Public Domain works for me.
    markbsure wrote:
    I'm a little confused how I would take the 4 hex values I am receiving as a string, and pass them into your first function "parseFloat", as it requires a LONG data type....

    Each of the four bytes is 8 bits of the 32-bit LONG value, so you just need to shift them into position and bitwise OR them together:
    CHAR recvd[4]
    parseFloat(recvd[1]<<24 | recvd[2]<<16 | recvd[3]<<8 | recvd[4]) // big endian
    or
    parseFloat(recvd[4]<<24 | recvd[3]<<16 | recvd[2]<<8 | recvd[1]) // little endian
    
  • i think i've found a problem with the "parseFloat" function.

    when i receive a 32bit Long value below 2 (i havent fully checked the range here, but Long values of 2 and above seem to work ok!), if I run it through the "parseFloat" function, the returning Float value is always 0 (zero).

    Example, I receive my 32bit Long value in hex form: $3F,$80,$00,$00
    After shifting each of the 4 bytes together I get:
    Binary : 111111100000000000000000000000
    Decimal: 1065353216

    Yet running this Long value through the "parseFloat" function returns 0.

    any ideas?
  • jweatherjweather Posts: 320
    markbsure wrote: »
    i think i've found a problem with the "parseFloat" function.

    when i receive a 32bit Long value below 2 (i havent fully checked the range here, but Long values of 2 and above seem to work ok!), if I run it through the "parseFloat" function, the returning Float value is always 0 (zero).

    Example, I receive my 32bit Long value in hex form: $3F,$80,$00,$00

    Yup, you found a bug with the special case of exponent=127, which is how you encode a value of 1. I will edit the code above to correct this in case anybody else runs into it, but where it used to read: "if (exp == 127) return 0.0;" change it to "if (exp == 127) return 1.0;" Because 2^0 == 1, not 0. :/
  • thanks JWeather
    much appreciated
  • Using this for Daikin now

    First, thank you very much to JWeather for posting these conversion functions. I compared the values I was receiving from the Daikin and the IEEE 754 converter URL, then I ran through an example calculating all of it by hand following through the code he posted below and my hand calculations came up the same. I'm still programming my Daikin code, but the temperature conversions were definitely a hurdle. And this code was quite helpful.
    jweather wrote: »
    Someone reading this thread asked for code to convert from a float to a 32-bit IEEE754 value...
    DEFINE_FUNCTION long encodefloat(float inp) {
        long outp;
        float factor, frac;
        sinteger exp;
        integer sign, expabs, mbit;
        
        sign = 0;
        if (inp < 0.0) { sign = 1; inp = inp * -1.0; }
        
        exp = 7;
        factor = 128.0;
        frac = inp;
    
        while (factor > 0.01) {
    	if (frac >= factor) {
    	    frac = frac - factor;
    	    factor = factor / 2.0;
    	    break;
    	}
    	exp = exp - 1;
    	factor = factor / 2.0;
        }
    
        expabs = type_cast(exp + 127);
    
        outp = expabs << 23;
        outp = outp | (sign << 31);
    
        mbit = 22;
        while (factor > 0.01) {
    	if (frac >= factor) {
    	    frac = frac - factor;
    	    outp = outp | (1 << mbit);
    	}
    	mbit = mbit - 1;
    	factor = factor / 2.0;
        }
    
        return outp;
    }
    

    I'm not sure if there's an easy way to put a long into a string as four bytes. Here's how I would do it:

    send_string DevDV, "outp>>24, (outp>>16)&$FF, (outp>>8)&$FF, (outp&$FF)";

    Or if it wants big-endian byte order:

    send_string DevDV, "(outp&$FF), (outp>>8)&$FF, (outp>>16)&$FF, outp>>24";

    Here's how I handled that:
    lSP = encodefloat(fSP)
    cB1 = lSP % $100
    cB2 = lSP / $100 % $100
    cB3 = lSP / $10000 % $100
    cB4 = lSP / $1000000

    Then if you want big endian you just order the bytes in descending order. But Daikin uses little endian.
Sign In or Register to comment.