Shop OBEX P1 Docs P2 Docs Learn Events
decimal math and scratch ram storage problems — Parallax Forums

decimal math and scratch ram storage problems

Lone_huskyLone_husky Posts: 13
edited 2009-02-14 22:20 in BASIC Stamp
First I want to say how great this forum is...very open and very responsive and helpful people.

Anyways I have another issue.

I have a detector that makes two different measurements in rapid succession and then calculates the ratio. I have used a bit wise approach to the division within in the confines of BCD math (see below) and had been storing the value as two words in scratch ram to use later. The program must acquire multiple readings and then calculate an average which it stores and then uses later to compare to the run-time (real-time) ratio. I have been having a number of issues, with changes in the number of significant digits that the division code produces sometimes 3 digits, sometimes 4 despite the number of inputs significant digits remaining the same. The real issue is then because I store the decimal part and fractional part in two separate words, and multiply the decimal part by a 1000 and add the fractional part (assuming 3 significant digits) when comparing the average to the current measured ratio. Obvisously if the number of significant digits changes, my multiply by 1000 and adding the fractional portion does not produce the correct result.

so is there a better way to do the division part? and is there a better to store and re-consistent the floating point number?

code is below
'light1 and light2 are measurements

'now proceed to calculate ratio
'---------------Calculation of Ratio-----------------
I=1-(light1.BIT15 ^ light2.BIT15*2)   ' sign of result
light1=ABS light1                     ' divide + numbers
light2=ABS light2
I=light1/light2*I                     ' integer part
   F=0                         ' initialize
'---------binary division loop-----------
FOR J=15 TO 0               ' 16 bits
light1=light1//light2<<1                   ' remainder*2
F.BIT0(J)=light1/light2              ' next bit
NEXT
   '----------------------------------------
F=F**10000                  ' normalize F*10000/65536


  
      ratio = (I*1000)+(F)
      PUT mem_idx, WORD ratio ' put ratio in scratch ram to be retrieved later for calculation of average
      mem_idx=mem_idx+2


'to retrieve stored value and calculate average
GET avg_mem_start, WORD I
  DEBUG CR, "location =", DEC avg_mem_start, " ",DEC I,CR
  FOR mem_idx = avg_mem_start+2 TO avg_mem_stop STEP 2 ' each loop add 2
    GET mem_idx, WORD F
   DEBUG "location =", DEC mem_idx, " ",DEC F,CR
  I=I+F

  NEXT

  avg=I/6
  DEBUG "average->",DEC avg






thanks in advance

-V

Comments

  • Mike GreenMike Green Posts: 23,101
    edited 2009-02-12 16:18
    The Stamps do 16-bit integer arithmetic including division. It's very easy to do multiple precision division a byte at a time. Usually, when dealing with fractional values, the numbers are represented as fixed point. For example, 123.45 would be represented internally as 12345 in a 16-bit word. You'd have to scale multiplication and division by appropriate constants, but it's pretty easy to do.

    What's the range and precision of your values? It's hard to give you specific advice without more information.
  • Lone_huskyLone_husky Posts: 13
    edited 2009-02-12 16:52
    Well, all I really need is probably 3 significant digits, i.e. 1.314 as a ratio. The values that I routinely get range from 1500 to 4000 and then I calculate the ratio. For example light1 will be 3145 and light2 will be 3367 and so the ratio of light1 to light2 would be 0.934. In fact it appears that I run into the biggest trouble when the ratio switches from less to greater than 1 or the reverse. So as I said to do this I was using a 16 bit binary division loop which would then produce an integer part and fractional part with 3 significant digits if the ratio was greater than 1. However if the ratio was less than one it would have 4 significant digits (or at least 4 digits in the fractional part). Then I store the ratio as two 16 bit words, I for integer and F for the fractional part. Then to retrieve the data I GET the two words from memory and multiply the integer part by 1000 and adding it to the fractional part, thus creating a single 16 bit word that I then use for comparsions.

    I realize this is a little clumsy but I wasn't sure if the really divison statement would correctly perform the division as I need it to and then I don't really understand the scaling.

    Any simplification would be greatly appreciated. Also I suspect my code would run faster.

    -V
  • Tracy AllenTracy Allen Posts: 6,662
    edited 2009-02-12 17:03
    The algorithm gives the fractional part always to 4 significant digits, 0.0000 to 0.9999 so to be correct, your ratio formula would have to be
    ratio = (I*10000)+(F)    ' changed to * 10000
    


    That allows an integer part only up to 5 or 6, the representable numbers being ratios from
    0.0000 to 6.5535
    Do the ratios you expect fit in that range?

    Stepping back to look at the problem, I don't think the light level will ever be negative. (?!) So you don't need to deal with the sign in the first integer division. Will one light level always be larger than the other? If that is the case, that could be always the dividend, so there would never be an integer part and never a problem with overflow when it goes to add up the 6 entries. But maybe either light level can be greater. How much greater is significant? Maybe you need a log ratio to cover a huge range of ratios.

    This is not BCD math. It is strictly binary. It is possible to go to double precision math if you need to add up a longer list of numbers to go into the average and to maintain precision, but that is more work.

    If you can work with 3 digits of precision in the fractional part, use
    ratio = (I*1000) + (F**1000)   ' instead of ratio=(I*10000)+(F**10000)
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
  • Lone_huskyLone_husky Posts: 13
    edited 2009-02-13 18:56
    So I guess the problem occurs when the ratio changes from less than 1, i.e 0.987 for example to greater than 1. I guess I could preferentially filter one of the wavelengths, but I would prefer not to. What would be the best way to calculate the ratio and then store that result into memory.

    A broader question is whether or not I need to use the bit wise binary division loop that I'm using...is there a simpler way?

    -Vassilios
  • Tracy AllenTracy Allen Posts: 6,662
    edited 2009-02-13 20:33
    It should not be a problem. If you use the binary division followed by,
    ratio = (I*1000) + (F**1000)
    


    the number will go smoothly from 0987 to 1185, say, representing 0.987 to 1.185. Store the numbers like 987 and 1185 in memory. The integer is a fraction in thousandths, and you can add them up, average them and compare them without trouble. No need to filter the wavelengths.

    I can almost guarantee you that the binary division loop is the most accurate and fastest why to get there.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
  • Lone_huskyLone_husky Posts: 13
    edited 2009-02-13 21:59
    dumb question....but why use the
    F**1000
    
    


    rather than

    F*1000
    
    
  • Tracy AllenTracy Allen Posts: 6,662
    edited 2009-02-13 22:18
    The value F that is calculated by the binary division is the best approximation to your original ratio, like this
    light2/light1 = F/65536.
    On the BASIC Stamp with the ** operator, the division by 65536 is implicit. You can see that in Mike's explanation. If you take the most significant 16 bits that result from a 16x16 multiply, that is really the same as multiplying two numbers and shifting the result 16 to the right, the same as division by 65536.

    When you do N = 1000**F, you are renormalizing, to make the implied denominator 1000 instead of 65536...
    light1/light2 = N / 1000
    For example, if light1/light2 = 5/7, then F = 46811, and N = 714.
    5/7 = 46811/65536 = 714/1000 = 0.714
    There are round-off errors, but not too bad within three digits of precision.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
  • Lone_huskyLone_husky Posts: 13
    edited 2009-02-14 19:51
    Tracy

    I'm sorry to bother you with this and I hope I don't appear as hopelessly stupid, but I can't seem to get only 3 digits of precision using what you recommend.

    if I use the following
    I=1-(light1.BIT15 ^ light2.BIT15*2)   ' sign of result
    light1=ABS light1                     ' divide + numbers
    light2=ABS light2
    I=light1/light2*I                     ' integer part
       F=0                         ' initialize
    '---------binary division loop-----------
    FOR J=15 TO 0               ' 16 bits
    light1=light1//light2<<1                   ' remainder*2
    F.BIT0(J)=light1/light2              ' next bit
    NEXT
       '----------------------------------------
    
    F=F**10000                  ' normalize F*10000/65536
    
         ratio= (I*10000)+(F)
    
         DEBUG "ratio = ", DEC I, ".", DEC4 F, "  stored as ", DEC ratio,CR
    
    
    



    everything works fine, but because I want to do some smoothing, it rapidly overfills the 16 bit word. see below
    Output

    ratio = 0.9453 stored as 9453
    ratio = 0.9958 stored as 9958
    ratio = 1.0737 stored as 10737
    ratio = 1.1221 stored as 11221

    but how do I get only 3 digits of precision....
    trying

    I=1-(light1.BIT15 ^ light2.BIT15*2)   ' sign of result
    light1=ABS light1                     ' divide + numbers
    light2=ABS light2
    I=light1/light2*I                     ' integer part
       F=0                         ' initialize
    '---------binary division loop-----------
    FOR J=15 TO 0               ' 16 bits
    light1=light1//light2<<1                   ' remainder*2
    F.BIT0(J)=light1/light2              ' next bit
    NEXT
       '----------------------------------------
    
    F=F**10000                  ' normalize F*10000/65536
    
         ratio= (I*1000)+(F**1000)
    
         DEBUG "ratio = ", DEC I, ".", DEC4 F, "  stored as ", DEC ratio,CR
    
    
    




    Doesn't work and I get the following....with

    output
    ratio = 0.9401 stored as 143
    ratio = 0.9396 stored as 143
    ratio = 0.9481 stored as 144
    ratio = 0.9897 stored as 151
    ratio = 1.0607 stored as 1009
    ratio = 1.0940 stored as 1014
    ratio = 1.0973 stored as 1014
    ratio = 1.1150 stored as 1017

    So the ratio is still calculated with 4 digits of precision and then overflows the 16bit register when stored...

    What am I doing wrong?

    -V
  • Lone_huskyLone_husky Posts: 13
    edited 2009-02-14 19:58
    Tracy,

    never mind my last post....I figured it out... I had been screwing up the normalization.

    BTW

    do you have a good procedure for calculating a running differential or derivative?


    thanks for all of your help

    - V
  • Tracy AllenTracy Allen Posts: 6,662
    edited 2009-02-14 22:20
    Right, you should have left out the line
    [s]F=F**10000[/s]                  ' normalize F*10000/65536
    


    and included only the line
    ratio= (I*1000)+(F**1000)
    


    which includes the F**1000

    A running differential or derivative. It is easy enough to take differences from one measurement to the next. The fun part is determining what to make of them.

    By the way, since the light levels are always going to be positive numbers you can replace
    [s]I=1-(light1.BIT15 ^ light2.BIT15*2)   ' sign of result
    light1=ABS light1                     ' divide + numbers
    light2=ABS light2
    I=light1/light2*I                     ' integer part
       F=0                         ' initialize[/s]
    


    with simply
    I = light1 / light2
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
Sign In or Register to comment.