(show&tell) The Floating Point Gotcha

Started by m i c h a e l, April 25, 2006, 08:49:21 PM

Previous topic - Next topic

m i c h a e l

Here is a little reminder to remember to keep those integer and floating point functions straight.



I was looking at Steve Dekorte's (author of the Io language) blog the other day when i noticed some code he had written to calculate savings over a worktime. Oh goodie: more code to translate into newLISP! I had already finished translating most of my other sundry code into newLISP days ago. I was ready for more lisping.



But this almost ended up a sad story, you see. Besides being a terrible programmer (I'm a bricoleur), I'm also impatient. This led me to assume the problems I'd encountered were something personal between newLISP and me, so we decided to begin seeing other languages.



So, without further ado, here is the evil code that almost came between us:



(context 'Person)

(setq
yearly-income 35000
saving-ratio .3
savings 0)

(define (work tax-rate inflation interest-rate)
(setq
$-value 1
tax-rate (or tax-rate .33)
inflation (or inflation .03)
interest-rate (or interest-rate .10))
(for (age 18 65)
(setq
income (- yearly-income (* yearly-income tax-rate))
savings (+ savings (* savings interest-rate))
savings (- savings (* savings inflation))
savings (+ savings (* income saving-ratio))
yearly-income (+ yearly-income (* yearly-income inflation))
$-value (- $-value (* $-value inflation))))
(silent
(println
(context) {: }
(floor savings) { * }
(format "%.2f" $-value) { = }
(floor (* savings $-value)))))

(context MAIN)

(new Person 'anna)
(anna:work)


Seasoned newLISPers will spot my mistake immediately, while others may have no clue what could be wrong with it. I tried and tried to figure out why, why, why this (obviously) correct script was not working!



newLISP kept telling me the answer was: "0 * 1 = 0". Clearly, my ignorance of contexts was at the bottom of this, I thought.



So on with this fool's quest I went till, coming to the end of my string, I decided to give up this "not OOP enough" newLISP :-(



Yes, still here friends, for as you already know, newLISP is a seductive mistress, indeed.



Getting back to the point of this (is there really a point to this?) communiqué, the integer<->float divide really bit me on this one. Just wanted to send up a little signal flare for any others who find themselves down this wrongway alley.





m   i   c   h   a   e   l





For the curious, the final code (using some ocaml-inspired constants defined in init.lsp) ended up looking like this:



(context 'Person)

(setq
yearly-income 35000
saving-ratio .3
savings 0)

(define (work tax-rate inflation interest-rate)
(setq
$-value 1
tax-rate (or tax-rate .33)
inflation (or inflation .03)
interest-rate (or interest-rate .10))
(for (age 18 65)
(setq
income (-. yearly-income (*. yearly-income tax-rate))
savings (+. savings (*. savings interest-rate))
savings (-. savings (*. savings inflation))
savings (+. savings (*. income saving-ratio))
yearly-income (+. yearly-income (*. yearly-income inflation))
$-value (-. $-value (*. $-value inflation))))
(silent
(println
(context) {: }
(floor savings) { * }
(format "%.2f" $-value) { = }
(floor (*. savings $-value)))))

(context MAIN)

(new Person 'anna)
(anna:work)

(new Person 'betty)
(betty:work .25 0 .07)


Man, that's pretty.



P.S. Here is the original http://www.dekorte.com/blog/blog.cgi?do=item&id=1818">link.

HPW

#1
Since you do not show your user-defined operators maybe the code with native newLISP is more readable to forum readers:



(context 'Person)

(setq
   yearly-income 35000
   saving-ratio 0.3
   savings 0)

(define (work tax-rate inflation interest-rate)
   (setq
      $-value 1
      tax-rate (or tax-rate 0.33)
      inflation (or inflation 0.03)
      interest-rate (or interest-rate 0.10))
   (for (age 18 65)
      (setq
         income (sub yearly-income (mul yearly-income tax-rate))
         savings (add savings (mul savings interest-rate))
         savings (sub savings (mul savings inflation))
         savings (add savings (mul income saving-ratio))
         yearly-income (add yearly-income (mul yearly-income inflation))
         $-value (sub $-value (mul $-value inflation))))
   (silent
      (println
         (context) {: }
         (floor savings) { * }
         (format "%.2f" $-value) { = }
         (floor (mul savings $-value)))))

(context MAIN)

(new Person 'anna)
(anna:work)

(new Person 'betty)
(betty:work 0.25 0 0.07)


Also subsequent call to *:work sum up the variables.

Wanted behaviour?
Hans-Peter

m i c h a e l

#2
Quote from: "HPW"Since you do not show your user-defined operators maybe the code with native newLISP is more readable to forum readers:


Yes, thank you HPW. I don't want to stir up more confusion in the process of  trying to clear some up :-) In the interest of full disclosure, here is the code in question:
(constant '+. add)
(constant '-. sub)
(constant '*. mul)
(constant '/. div)


Quote from: "HPW"Also subsequent call to *:work sum up the variables.

Wanted behaviour?


Yes, this is a one-shot deal. Steve's original method was called run, which gives a better sense of its one-time nature. I named it work to reflect that we were simulating the savings of a person over what I called their "worktime" (as related to their lifetime). But if you wanted to track a person through reincarnated lifetimes, then my function would be all set ;-)

cormullion

#3
Nice post. And thanks for teaching me the word bricoleur, which I had to look up but I like very much! I'm intrigued by the idea of newLISP being a seductive mistress. Plainly I haven't been spending long enough with it/her.



Not being a seassoned newLISPer, it took me some time to spot a mistake - presumably it's just that you were trying to do this:


(* 3500 0.3)

and getting 0 instead of 1050? Or is there something more adrift?



I remember thinking when I first started with newLISP that I was half expecting + - * and / to do the floating point, and that the add/sub//mul/div functions were for special speedy integer arithmetic. (Ie you work harder if you really want less user-friendly arithmetic.) But it wasn't a strong feeling a the time, and I haven't thought much about it recently.



Now that you mention it, I wrote this the other day, and it does look a bit harder with the add... functions:


(constant 'pi (mul 2 (acos 0)))

(define (equation-of-time d)
(set 'day-number d)
(set 'B (div (mul (mul pi 2) (sub day-number 81)) 364))
(set 'E (sub (mul 9.87 (sin (mul 2 B))) (mul 7.53 (cos B)) (mul 1.5 (sin B)))))

It's also strange in a way that (for) and (dotimes) use floating points by default...

m i c h a e l

#4
Quote from: "cormullion"Nice post. And thanks for teaching me the word bricoleur . . .


Thank you, and you're welcome!


Quote from: "cormullion"I'm intrigued by the idea of newLISP being a seductive mistress.


This idea came from the observation that the way I would move from studying  one language to another -- being infatuated with a new discovery, only to grow bored with it as I got to know it better and beginning to seek out new new languages on the side -- was similar to the way some go from one affair to another. Funny though, I've been feeling kinda content lately. Could newLISP be the one?!


Quote from: "cormullion"presumably it's just that you were trying to do this:



Code:

(* 3500 0.3)



and getting 0 instead of 1050? Or is there something more adrift?


No, that is exactly right. As HPW pointed out, I should have used add, sub, mul, and div, instead of my ocaml-like constants to avoid any confusion.


Quote from: "cormullion"I remember thinking when I first started with newLISP that I was half expecting + - * and / to do the floating point, and that the add/sub//mul/div functions were for special speedy integer arithmetic. (Ie you work harder if you really want less user-friendly arithmetic.)


I'm afraid this may be the impression many will have when first coming to newLISP. A shame if they give up (as I almost did) before discovering the separate functions for working with floating point numbers. That there are separate functions is not a problem. No, it's the way newLISP silently throws away the fractional part, smiles, and pretends like everything is fine. Some languages, on the other hand, complain about floats and ints not getting along (or something to that effect :-) BTW, I like the idea of using user-friendliness (or lack thereof) as a natural deterrent to potentially more dangerous (i.e., powerful) functions.


Quote from: "cormullion"(constant 'pi (mul 2 (acos 0)))



(define (equation-of-time d)

   (set 'day-number d)

   (set 'B (div (mul (mul pi 2) (sub day-number 81)) 364))

   (set 'E (sub (mul 9.87 (sin (mul 2 B))) (mul 7.53 (cos B)) (mul 1.5 (sin B)))))


This may be just me, but I find the words (add, sub, mul, & div) in the above code make it harder to see the maths going on in there, which is the main reason I made the aliases +. -. *. /. in the first place. Being an artist rather than a programmer, I may be guilty of focusing on the aesthetics of a language more than I should. But it's funny how beauty and function are often fused in the best of designs.


Quote from: "cormullion"It's also strange in a way that (for) and (dotimes) use floating points by default...


Thanks, cormullion. Now in return, I've learned something from you! Ah, balance.





m i c h a e l



P.S. Remember folks, flirting around with newLISP may become serious :-)

cormullion

#5
Is floating-point computation faster than integer? On my machine I'm getting confusing results, with FP being generally faster in newLISP than integer. (But I'm no expert in benchmarking.)

Lutz

#6
Quote
Being an artist rather than a programmer, I may be guilty of focusing on the aesthetics of a language more than I should. But it's funny how beauty and function are often fused in the best of designs.


Not guilty at all. The aesthetics of a programming language are as important as the functional aspects of it. Programmers with a sense for aesthetics make better functioning programs ;)



If you are not heavy into bit fiddling or controlling hardware with newLISP, just do the reassignment of the integer operators as shown in the manual, or use the shorter:

(constant '+ add '- sun '* mul '/ div)


Whenever you really need an integer just cast with 'int' as in: (int x). But most built-in functions in to newLISP will automatically convert to what they need, no matter what you feed them. When importing libraries, as is done in some of the modules shipped in newLISP you might get into problems with some, because of 'get-int' 'get-char' and 'get-string' which all expect an integer address (and do not convert).



The speed of floating point versus integer operations depends on the CPU and OS running on and the compiler used to compile newLISP.



Lutz



ps: welcome to newLISP Michael

m i c h a e l

#7
Quote from: "Lutz"Programmers with a sense for aesthetics make better functioning programs ;)


Uh-oh. Most all of my programs, if you could see their scars, would end up looking like bruised and bandaged war veterans. I wonder what that says about my aesthetic sensibility ;-)


Quote from: "Lutz"(constant '+ add '- sun '* mul '/ div)


You would think I would've noticed constant being used with multiple arguments after having read the  http://newlisp.org/downloads/newlisp_manual.html">manual (BTW, kudos Lutz on the excellent manual -- a wealth of examples!), cormullion's great  http://newlisp.org/introduction-to-newlisp.pdf">Introduction to newLISP, newlisper's invaluable  http://newlisper.blogspot.com/">blog (also BTW, I followed newlisper's instructions for using Big Cat along with his newLISP script for making thumbnails, and it worked, just like that. Bravo!), John Small's  http://newlisp.org/newlisp-in-21-minutes.html">An Interactive Tutorial (I learn best by example, so this one was especially appreciated), as well as all the information under  http://newlisp.org/index.cgi?Tips_and_Tricks">Tips&Tricks, the  http://www.alh.net/newlisp/phpbb">discussion boards, or http://newlisp.org/index.cgi?Code_Contributions">code (apologies to those whom I have missed in this hasty list). But I never did! Time to condense all those definitions :-)


Quote from: "Lutz"ps: welcome to newLISP Michael


Thank you. Thank you, I'm glad to be here. :-) [1]



m i c h a e l



[1] Twilight Zone, Episode 30. "A Stop at Willoughby."