News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Fahrenheit To Celcius

Started by oex, October 03, 2015, 01:10:08 AM

Previous topic - Next topic

oex

Hey guys,

I have an optimization problem I need to work out for a project I'm working on....

Can anyone suggest or has code to convert Fahrenheit to Celcius optimized for size?

It's actually not for x86 but finding the best method would be a good start.

Thanks :biggrin:

qWord

fld/fild,fadd,fmul,fstp/fistp with REAL4 constants?
MREAL macros - when you need floating point arithmetic while assembling!

oex

It's actually RISC PIC16F1784
http://ww1.microchip.com/downloads/en/DeviceDoc/40001637C.pdf

I'm guessing I can only get help here finding a best higher level method or some doc reference

qWord

sorry, I didn't notice "not for x86".

hard to solve with these device, even no multiplication available.
MREAL macros - when you need floating point arithmetic while assembling!

oex

It's been a while since I've done this, not at all with RISC. It is for a small range 150-430, I have read that you can get a rough estimate
Subtract 30 then divide by 2....

Not sure how that would work with a small table (assuming that is possible for RISC lol).

only needs to be integer accuracy, I'm guessing give or take 1 degree  for a few readings wouldn't matter too much

Actual formula is (F-32)*5/9=C

dedndave

hi Peter
haven't written any code for that processor, yet
but, it doesn't appear to have multiply or divide
i can write x86 code, and you can convert it to pic16 equiv   :biggrin:

first thing, of course, is to subtract out 32 degrees

    mov     ax,TempF
    sub     ax,32


what's left is, you have to multiply by 5 and divide by 9
or, multiply by 0.5555555555555
multiplying by 5 is simple with shift and add

    mov     dx,ax
    shl     dx,1
    shl     dx,1
    add     ax,dx


i can think of 3 ways to get it done (i'm sure there are more)
1) multiply by 5 with shift/add, then divide with shift and add (multiply by 2^n/9)
2) write a general purpose long-division routine - this might be a good solution if it were also to be used elsewhere
3) multiply by 0.555555555555 x 2^n (.5555555555 = 5/9 = 1/1.8 )

the last option is probably going to make the smallest code
16-bit registers, so use 2 of them for a 32-bit result, the high word will be the answer
the low word will be the fraction, which you may want to throw away

so - the constant will be 36409
if you multiply by that value, 32-bit result will be the tempurature in C, x 65536

qWord

Quote from: oex on October 03, 2015, 01:54:38 AMIt is for a small range 150-430, I have read that you can get a rough estimate
Subtract 30 then divide by 2....
gives a max. relative error of ca. 10% for that input range
MREAL macros - when you need floating point arithmetic while assembling!

dedndave

multiply by 36409 - 1000 1110 0011 1001

    mov     dx,ax     ;CX:DX = bit 0
    mov     cx,0
    shl     ax,1      ;bit 1
    shl     ax,1      ;bit 2
    shl     ax,1      ;bit 3
    add     dx,ax     ;CX:DX = bit 3 + bit 1
    shl     ax,1      ;bit 4
    add     dx,ax     ;CX:DX = bit 4 + bit 3 + bit 1

and so on
at some point, you'll want a 4th register for AX to overflow into
and, you'll want to
    add     dx,ax
    adc     cx,bx


notice, that you can put the number 36409 in a constant and use a loop to multiply   :t
you put a single bit in a register
test against the constant - if the constant has that bit set, you add with carry
shift the single bit left and repeat the test and conditional add 16 times

oex

Quote from: qWord on October 03, 2015, 02:09:22 AM
Quote from: oex on October 03, 2015, 01:54:38 AMIt is for a small range 150-430, I have read that you can get a rough estimate
Subtract 30 then divide by 2....
gives a max. relative error of ca. 10% for that input range

Haha sorry QWord, as I say it's been a while, that is a bit too rough, a degree at most either way would be acceptable

Thanks for your solutions Dave, fortunately I don't have to translate this, just trying to help a friend with some optimization, may have a followup or two

dedndave

here is some x86 code to get you started
it works ok with positive result values
code be modified to handle lower temps (negative), but you don't care
i tested it with a couple input values:

32 -> 0
212 -> 100

;###############################################################################################

        INCLUDE    \Masm32\Include\Masm32rt.inc

;###############################################################################################

        .DATA

wConstant dw 36409

;###############################################################################################

        .CODE

;***********************************************************************************************

main    PROC

        mov     ax,32
        call    F2C

        print   ustr$(eax),13,10


        print   chr$(13,10)
        inkey
        INVOKE  ExitProcess,0

main    ENDP

;***********************************************************************************************

F2C     PROC

        mov     dx,0           ;DX:AX = TempF

        sub     ax,32
        sbb     dx,0           ;DX:AX = TempF - 32

        mov     cx,0
        mov     esi,0          ;ESI:CX = result accumulator (only lower 16 bits of ESI used)

        mov     bx,1           ;shifted test bit
        mov     edi,16         ;EDI = loop count
        jmp short Loop01

Loop00: shl     bx,1
        shl     ax,1
        rcl     dx,1

Loop01: test    bx,wConstant
        jz      Loop02

        add     cx,ax
        adc     esi,edx

Loop02: dec     edi
        jnz     Loop00

        xchg    eax,esi
        and     eax,0FFFFh    ;result in AX
        ret

F2C     ENDP

;###############################################################################################

        END     main



EDIT: oh....
at the end, you could test the high bit of the result fraction to round to the nearest   :P
        shl     cx,1
        adc     esi,0
        xchg    eax,esi
        and     eax,0FFFFh
        ret

rrr314159

#10
Here's a hokey way to do it, has these advantages,

- shortest I can think of (as requested by OP)
- uses no mult, div, or other instructions that might not be on RISC like sbb etc
- gives closest integer answer
- easy to understand (mindless in fact)

and these disadvantages,

- could take very long time (up to dozens of nanoseconds)
- Fahrenheit must be >= 32

; Fahrenreit to Centigrade, gives closest integer answer, no mult used, hokey
INCLUDE    \Masm32\Include\Masm32rt.inc

FAHR = 37           ; must be >= 32

.code
start:

mov eax, FAHR
sub eax, 32
mov edx, eax       
shl eax, 2          ; multiply eax by 4
mov ecx, eax        ; start looping at ecx = eax * 4 a little quicker
add eax, edx        ; now eax is times 5

@@: mov edx, ecx    ; hokey way to divide by 9
    shl edx, 3      ; mult edx by 8
    add edx, ecx    ; now edx is times 9
    cmp edx, eax    ; see if it's down to the target yet
    jle @F
    loop @B
   
@@: sub eax, edx    ; round up if target was overshot by 5,6,7, or 8
    sub eax, 4
    jle @F
        inc ecx
    @@:

print ustr$(ecx),13,10    ; result in ecx
inkey
ret
end start


[edit] this could be faster, on average, by starting the loop at 223, given the max Fahrenheit of 430; but if speed matters this approach no good anyway
I am NaN ;)

dedndave

it's a 16-bit processor - lol

rrr314159

Quote from: dedndaveit's a 16-bit processor - lol

Sure, but that doesn't mean the algo should be expressed with 16-bit X86 code .. ! Could just as well give it in psuedocode, or basic. It needs to be translated regardless. Note that the largest number my algo needs is 15480
I am NaN ;)

hutch--

Peter,

See if this can be translated to the processor you are working with. Its basically one of the old timer techniques for multiply on a processor that does not have a MUL instruction. You need to be able to do bit shifts with this one.


; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

peasant_multiply proc arg1:DWORD,arg2:DWORD

    mov ecx, [esp+4]        ; arg1           ; load 1st arg into ECX
    mov edx, [esp+8]        ; arg2           ; load 2nd arg into EDX
    xor eax, eax            ; zero EAX
    jmp testit              ; jump to test if arg2 is ODD or EVEN

  lbl0:
    add ecx, ecx            ; double arg1
    shr edx, 1              ; div arg2 by 2
    cmp edx, 1              ; compare it to 1
    jbe lbl1                ; exit loop if below or equal

  testit:
    test edx, 00000000000000000000000000000001b
    je lbl0                 ; jump back if its even

    add eax, ecx            ; accumulate ECX in EAX if EDX is odd
    jmp lbl0

  lbl1:
    add eax, ecx            ; add last ECX to EAX and exit

    ret 8

peasant_multiply endp

OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

rrr314159

Quote from: rrr314159uses no mult, div, or other instructions that might not be on RISC like sbb etc

Quote from: hutchYou need to be able to do bit shifts with this one.

The reference given above for RISC PIC16F1784 shows it does have a full complement of bit operations: bit-test-and-skip (in one operation), bit shifts, subtract with borrow, rotate thru carry etc
I am NaN ;)