Hi all. I was wondering if anyone else had encountered this, and if so, if anyone knows what's going on and why.
I've been writing a command that uses the polar function, but during debugging I'm noticing some strange behavior. Specifically, when I use the polar function with the 0,0 point of the drawing as input and any right angle other than 0, it gives a point just slightly off from where it should be. Here's some examples:
Command: (polar '(0 0 0) pi 1)
(-1.0 1.22465e-016 0.0)
Command: (polar '(0 0 0) (* pi 0.5) 1)
(6.12323e-017 1.0 0.0)
Command: (polar '(0 0 0) (* pi 1.5) 1)
(-1.83697e-016 -1.0 0.0)
It doesn't happen when the angle is 0, or if the angle is not perpendicular or parallel to 0 (such as a 45 degree angle, or a 167 degree angle). It looks to me like it might be some discrepency between the precisions used in angles and those used in other floating point numbers, but I'm not sure. Does anyone else have any insight into this?
Solved! Go to Solution.
Solved by dbroad. Go to Solution.
I know this perhaps isn't going to help your pain but its just part of learning the results of the program. Since PI is irrational and since the CPU has limits on its accuracy, you will see slight variations. For all intents, your odd looking numbers are extremely close to the target values (0). So you should use the equal function rather than the = function to test, and use the tolerance option.
If you need exactly 0, then you could apply a function to ensure it.
(setq test '(-1.0 1.22465e-016 0.0))
(mapcar '(lambda (x) (if (equal x 0 1e-15) 0.0 x)) test)
should return (-1.0 0.0 0.0)
Unfortunately that is what we must deal with. I edited my first post to demonstrate a workaround if interested.
@BigDumbWeirdo wrote:
....
I've been writing a command that uses the polar function, but during debugging I'm noticing some strange behavior. Specifically, when I use the polar function with the 0,0 point of the drawing as input and any right angle other than 0, it gives a point just slightly off from where it should be. Here's some examples:
Command: (polar '(0 0 0) pi 1)
(-1.0 1.22465e-016 0.0)....
Or, if in the case of such angles you can add or subtract a coordinate list instead....
The equivalent of the (polar) quoted above [for a point 1 unit to the left of 0,0,0] would be:
Command: (mapcar '- '(0 0 0) '(1 0 0))
(-1 0 0)
Is what I'm seeing right in that Acad uses double-precision? I thought it was single, since the Units dialog box maxes out at 8 digits after the decimal.
Yes double precision. To print more, you could use (princ (rtos myvalue 2 14))
or something else like it.
::grumble grumble:: Now I have to go re-write some older code...
But seriously, thanks to both you and Kent. Being the "AutoCAD Answer Guy" in my office, it's nice to know that there's still a place I can go to if I get stuck without revealing my ignorance to my co-workers.
These computers use binary math;
it is more efficient, especially for integer math.
If the numbers are 'converted'
to our more familiar decimal format,
more errors can accumulate in subsequent math,
because it averages out.
Or something like that.
If the numbers must be shown to the closest decimal:
(setq p (polar '(0.0 0.0 0.0) pi 1.0) x (car p) y (cadr p) z (caddr p))
(defun N_E12 (N) (atof (rtos N 2 12)))
;(setq Xn (N_E12 X))
;(princ "\n X...16: ")(princ (rtos X 2 16))
;(princ "\n Xn..16: ")(princ (rtos Xn 2 16) )
(setq Yn (N_E12 Y))
(princ "\n Y....16: ")(princ (rtos y 2 16))
(princ "\n Yn...16: ")(princ (rtos yn 2 16) )
(princ "\n Prin1.Y: ") (prin1 y )
(princ "\n Prin1.P: ") (prin1 p )
(setq Pn (list (N_E12 X) (N_E12 Y) (N_E12 Z) ))
(princ "\n Prin1.Pn: ") (prin1 pn )
(princ)
In short, computer memory is finite and hence recurring & irrational values must be stored to a finite precision (approximately 15 decimal places for double precision floating point values). This memory limitation introduces minute rounding errors at the extent of the stored precision, and these minute errors accumulate over repeated calculation.
As noted by the other contributors to this thread, this behaviour is simply something that you need to become familiar with when programming: for example, when comparing doubles for equality, ensure that you use the AutoLISP equal function with some tolerance (I personally use 1e-8 (i.e. 0.00000001) since this is the limit of unit precision in AutoCAD and so values differing by a tolerance less than this would not have been introduced intentionally by the user when drafting).
For more information on the subject, this Wikipedia article may be interesting to read.
Lee
>> (approximately 15 decimal places for double precision floating point values)
Not 15 decimal places, but 15 significant digits, as shown the following examples
12345678901234500000 (0 decimal places)
1234567890.12345 (5 decimal places)
1.23456789012345 (14 decimal places)
0.000000000123456789012345 (25 decimal places)
Good catch gile - thanks.
I tried something to automatically compare numbers and points with a tolerance which takes in account the significant digits (not deeply tested).
(defun log10 (x) (if (zerop x) 0.0 (/ (log (abs x)) (log 10)) ) ) (defun fuzz (x) (expt 10.0 (- (fix (log10 x)) 14)) ) (defun equalNumbers (x y)
(or (= x y) (equal x y (max (fuzz x) (fuzz y)))) ) (defun equalPoints (p1 p2) (vl-every 'equalNumbers p1 p2) )
(fuzz 0.0000123456789012345) =>1.0e-018
(fuzz 0.123456789012345) =>1.0e-014
(fuzz 12345.6789012345) =>1.0e-010
(fuzz 1234567890.12345) =>1.0e-005
(fuzz 123456789012345) =>1.0
Here's a more mathematically rigorous implementation:
(defun gc:Log10 (x) (/ (log x) (log 10)) ) (defun gc:Fuzz (x) (if (zerop x) 1e-15 (expt 10.0 (fix (- (gc:Log10 (abs x)) 15))) ) ) (defun gc:EqualNumbers (x y) (or (= x y) (equal x y (max (gc:Fuzz x) (gc:Fuzz y)))) ) (defun gc:EqualPoints (p1 p2) (vl-every 'gc:EqualNumbers p1 p2) )