Shop OBEX P1 Docs P2 Docs Learn Events
Help on multipling a variable by a fractional decimal — Parallax Forums

Help on multipling a variable by a fractional decimal

treborz17treborz17 Posts: 76
edited 2011-04-04 13:35 in Propeller 1
How do I code this in Spin? Hopfully someone will tolorate my lack of learning the basics first.

VAR

Long CH0 ' Should contain an integer from approximately 1 to 4000 max.

PUB

CH0 := MCP3202 ' Return from an ADC Driver chip.

CH0 := 100 - (CH0 * 0.05) ' THIS LINE IS MY PROBLEM CODE -- This is the first time I've worked fractional decimals.
' I'm trying to change ADC results to something that I can use.


Would really appreciate any help,

Robert

Comments

  • martinhmartinh Posts: 58
    edited 2011-04-03 19:35
    Depends of course on your precision requirements. In that case instead of *0.05 you can simply divide by 20. This will truncate if you need it rounded calculate (CH0+10)/20 (think about why). If that's not good enough report back.
  • martinhmartinh Posts: 58
    edited 2011-04-03 19:41
    Just one additional thought: Since you convert with your formula a value range 1..4000 (roughly 12 bit) to a range -100..100 (roughly 8 bit), your code destroys 4 bits of accuracy, I hope you are aware of that!
  • ElectricAyeElectricAye Posts: 4,561
    edited 2011-04-03 19:56
    SPIN can't do decimal math outright. By itself SPIN can only do integer operations. Instead of (Variable*0.05), you might try something like (Variable*5)/100. But in that case Variable needs to be at least 20 so that your end result doesn't get truncated to zero. For floating math operations, you really need to use an object like FloatMath. See the attachment.

    Maybe have a look at these in the OBEX:

    http://obex.parallax.com/objects/689/

    http://obex.parallax.com/objects/53/
  • Heater.Heater. Posts: 21,230
    edited 2011-04-03 22:29
    treborz17,

    Multiplying by 0.05 is the same as dividing by 20 as previously noted.
    Dividing a value in the range 1 to 4000 by 20 will lose some precision. For example 35 / 20 results in 1 instead of 1.75 because we are working with whole numbers in Spin.
    That may or may not be a problem depending on your application and the desired degree of accuracy.
    If it is a problem you could store the variable CH0 as a number in the range 20 to 80000 instead. That is to say the units of CH0 are twentieths instead of ones.
    Then when you divide by 20 you still have full precision in the result, the example above becomes 700 / 20 = 35. Where the 35 is also twentieths instead of ones and still represents an actual value of 1.75.
    This is called fixed point arithmetic and is often used instead of floating point which can be a lot slower and take a lot of space in a small MCU like the Prop.

    In general one would scale ones variables up by a power of 2 like 16, 32, 64, 128, 256 etc because this can be done with shift operators "<<" and ">>" which is faster than multiplies and divides.
  • kwinnkwinn Posts: 8,697
    edited 2011-04-03 22:32
    treborz17 wrote: »
    How do I code this in Spin? Hopfully someone will tolorate my lack of learning the basics first.

    VAR

    Long CH0 ' Should contain an integer from approximately 1 to 4000 max.

    PUB

    CH0 := MCP3202 ' Return from an ADC Driver chip.

    CH0 := 100 - (CH0 * 0.05) ' THIS LINE IS MY PROBLEM CODE -- This is the first time I've worked fractional decimals.
    ' I'm trying to change ADC results to something that I can use.


    Would really appreciate any help,

    Robert

    If you want to avoid using floating point math for this it can be done with integers in most cases.

    100 - (ch0 x 0.05) could be rewritten as (2000 - ch0) / 20. If you are outputting the result as a string the divide could be eliminated by shifting the result right one bit and inserting a decimal one digit to the left.
  • treborz17treborz17 Posts: 76
    edited 2011-04-03 22:45
    Thank you martinh and ElectricAye, this gives me a couple of alternatives. I believe you have solved my problem. It appears that if I continue with thie type of projects I'm working on I should do a little studying -- I don't believe I can expect others to continuely do my work for me. This time it really helped.

    Thanks again,

    Robert
  • treborz17treborz17 Posts: 76
    edited 2011-04-03 22:52
    Heater and kwinn,

    Thanks for your valuable response -- you must have responded while I was typing.

    Robert
  • treborz17treborz17 Posts: 76
    edited 2011-04-03 23:41
    If anyone is interested, my project is adding , joystick operation including xBee radio control to my autonomous Robot Base Full Kit (I built it before the kit) robot. I am using the excellent Obex object "Wheel_Controller.spin" designed by Don W. Ternyila . This object includes a method for adding a Joystick. However, the input requires +- 0 to 100% and 0 to -100% figures for both the X Turn and Y Throttle controls. Don's program is so perfect in it's total concept I thought it better to make changes to one of the Obex joystick objects instead. That's why I am changing the output of the ADC MCP3202 to 0-100% plus and minus figures. My example contained just aproximate numbers.

    BTW I knew that I could divide instead of multiply I just was using the wrong divisor instead of 20 a number that was so stupid that I am not going to mention what number I was using. I found it easier to think the program just couldn't handle the function I was trying to use.

    Thanks again everyone.

    Robert
  • JasonDorieJasonDorie Posts: 1,930
    edited 2011-04-04 00:28
    You shouldn't feel bad asking for help - everyone was a beginner once. :)

    No one here is likely to write something for you, but most are happy to point you in the right direction.
  • John AbshierJohn Abshier Posts: 1,116
    edited 2011-04-04 08:23
    You can use the ** operator. For i < 2^30
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000                                          ' use 5MHz crystal
    ' _xinfreq = 6_250_000                                          ' use 6.25MHz crystal
    
      CLK_FREQ = ((_clkmode - xtal1) >> 6) * _xinfreq
      MS_001   = CLK_FREQ / 1_000
      US_001   = CLK_FREQ / 1_000_000
    
    
    con
    
      RX1   = 31
      TX1   = 30
      SDA   = 29
      SCL   = 28
    
    con 
    
      #1, HOME, #8, BKSP, TAB, LF, CLREOL, CLRDN, CR, #16, CLS      ' PST formmatting control
    
    
    obj
      Sio  : "FullDuplexSerialPlus"        
    
      
    
    var
    
    Pub Main | i, j
      Sio.start(31,30,0,115200)  ' Rx,Tx, Mode, Baud
      j := 107375182    ' 2^31 * fraction.  For this case 2^31 * 0.05
      repeat
        i := Sio.GetDec
        Sio.dec((i << 1) ** j)
        Sio.tx(CR)
    

    Returned value is off by 1 for negative i with this fraction.
    John Abshier
  • JonnyMacJonnyMac Posts: 9,233
    edited 2011-04-04 10:28
    I have written and X/Y motor control application that uses an analog joystick and scaled the raw input from the MCP320x with this method:
    pub scale(raw, minOut, maxOut)
    
    '' Scales raw (0 to 4095) value to new range: minOut to maxOut
    
      raw := 0 #> raw <# 4095                                       ' constrain input
    
      if (minOut < maxOut)                                          ' if good range
        return ((raw * (maxOut - minOut)) / 4095) + minOut
      else
        return raw
    

    If your centered joystick reads about 2047 (midpoint of 12-bit value) then you can use this call:
    joystick := scale(joystick, -100, 100)
    

    ...to convert the raw joystick value to your desired range. As others have noted there will be small truncation errors, but I don't think that you can get around this outside of using a larger range for your motor speed input (mine used -500 to +500). Note,too, that joysticks don't ever auto-center exactly as we'd like so you may need to create a small deadband in the center of the pot range so that your motor(s) stop when the joystick is set free and allowed to return to neutral on its own.
  • JonnyMacJonnyMac Posts: 9,233
    edited 2011-04-04 10:45
    Here's my take on John Abshier's code -- probably faster (only one multiply operation) but the range is fixed by the fractional constant.
    pub scale2(raw)
    
    '' Scales 12-bit input to -100 to +100
    '' -- 209_776_413 = 0.04884 * 2^32
    
      return (raw ** 209_776_413) - 100                             ' x 0.04884 - 100
    

    I ran a test loop and there is virtually no difference in output of the two methods -- see attached.
  • martinhmartinh Posts: 58
    edited 2011-04-04 11:33
    JonnyMac wrote: »
    Here's my take on John Abshier's code -- probably faster (only one multiply operation) but the range is fixed by the fractional constant.
    pub scale2(raw)
    
    '' Scales 12-bit input to -100 to +100
    '' -- 209_776_413 = 0.04884 * 2^32
    
      return (raw ** 209_776_413) - 100                             ' x 0.04884 - 100
    

    I ran a test loop and there is virtually no difference in output of the two methods -- see attached.

    An minimal thing which I personally do not like about that is that there is a slight shift in the results due to truncation instead of rounding. Easy to see from the output if you look at the values mapped to -100 and 100 respectively. The result is not symmetric. It can be improved by adding 0.5.
    replace (raw ** 209_776_413) with (raw**419_532_825 + 1) >> 1
  • treborz17treborz17 Posts: 76
    edited 2011-04-04 13:35
    Thanks all. Since I have something coded that appears to be working well with "Parallax Serial Terminal", I plan to try it with "Wheel_Controller.spin" with the xBee radio addition before I work with these other suggestions, all of which seem to be better and shorter. My code excerpt that follows makes the left/right and forward/back, have a compensated 0 center for the joystick's lack of returning to exact zero center each time, and allows the 0 center to increase from 0, increase to positive 100, and to increase in the opposite direction from 0, increase to -100 which the motor controller spin requires:

    CH0 := MCP3202.in(0)

    CH0 := CH0 / 20
    If CH0 < 100
    CH0 := 100 - CH0
    CH0 := CH0 * ( -1 )
    If CH0 > 99
    CH0 := CH0 - 100
    If CH0 < 8 and CH0 > 0
    CH0 := 0
    CH0percent := CH0
Sign In or Register to comment.