Short-circuit Logic

E

Ethan Furman

If you iterate from 1000 to 173, you get nowhere. This is the expected
behaviour; this is what a C-style for loop would be written as, it's
what range() does, it's the normal thing. Going from a particular
starting point to a particular ending point that's earlier than the
start results in no iterations. The alternative would be an infinite
number of iterations, which is far far worse.

If the bug is the extra three zeros (maybe it should have been two),
then silently skipping the loop is the "far, far worse" scenario. With
the infinite loop you at least know something went wrong, and you know
it pretty darn quick (since you are testing, right? ;).
 
C

Chris Angelico

If the bug is the extra three zeros (maybe it should have been two), then
silently skipping the loop is the "far, far worse" scenario. With the
infinite loop you at least know something went wrong, and you know it pretty
darn quick (since you are testing, right? ;).

You're assuming you can casually hit Ctrl-C to stop an infinite loop,
meaning that it's trivial. It's not. Not everything lets you do that;
or possibly halting the process will halt far more than you intended.
What if you're editing live code in something that's had uninterrupted
uptime for over a year? Doing nothing is much safer than getting stuck
in an infinite loop. And yes, I have done exactly that, though not in
Python. Don't forget, your start/stop figures mightn't be constants,
so you might not see it in testing. I can't imagine ANY scenario where
you'd actually *want* the infinite loop behaviour, while there are
plenty where you want it to skip the loop, and would otherwise have to
guard it with an if.

ChrisA
 
E

Ethan Furman

You're assuming you can casually hit Ctrl-C to stop an infinite loop,
meaning that it's trivial. It's not. Not everything lets you do that;
or possibly halting the process will halt far more than you intended.
What if you're editing live code in something that's had uninterrupted
uptime for over a year? Doing nothing is much safer than getting stuck
in an infinite loop. And yes, I have done exactly that, though not in
Python. Don't forget, your start/stop figures mightn't be constants,
so you might not see it in testing. I can't imagine ANY scenario where
you'd actually *want* the infinite loop behaviour, while there are
plenty where you want it to skip the loop, and would otherwise have to
guard it with an if.

We're not talking about skipping the loop on purpose, but on accident.
Sure, taking a system down is no fun -- on the other hand, how much data
corruption can occur before somebody realises there's a problem, and
then how long to track it down to a silently, accidently, skipped loop?
 
S

Steven D'Aprano

You're assuming you can casually hit Ctrl-C to stop an infinite loop,
meaning that it's trivial. It's not. Not everything lets you do that; or
possibly halting the process will halt far more than you intended. What
if you're editing live code in something that's had uninterrupted uptime
for over a year?

Then more fool you for editing live code.

By the way, this is Python. Editing live code is not easy, if it's
possible at all.

But even when possible, it's certainly not sensible. You don't insist on
your car mechanic giving your car a grease and oil change while you're
driving at 100kmh down the freeway, and you shouldn't insist that your
developers modify your code while it runs.

In any case, your arguing about such abstract, hypothetical ideas that,
frankly, *anything at all* might be said about it. "What if Ctrl-C causes
some great disaster?" can be answered with an equally hypothetical "What
if Ctrl-C prevents some great disaster?"

Doing nothing is much safer than getting stuck in an
infinite loop.

I disagree. And I agree. It all depends on the circumstances. But, given
that we are talking about Python where infinite loops can be trivially
broken out of, *in my experience* they are less-worse than silently doing
nothing.

I've occasionally written faulty code that enters an infinite loop. When
that happens, it's normally pretty obvious: something which should
complete in a millisecond is still running after ten minutes. That's a
clear, obvious, *immediate* sign that I've screwed up, which leads to me
fixing the problem.

On the other hand, I've occasionally written faulty code that does
nothing at all. The specific incident I am thinking of, I wrote a bunch
of doctests which *weren't being run at all*. For nearly two weeks (not
full time, but elapsed time) I was developing this code, before I started
to get suspicious that *none* of the tests had failed, not even once. I
mean, I'm not that good a programmer. Eventually I put in some deliberate
errors, and they still didn't fail.

In actuality, nearly every test was failing, my entire code base was
rubbish, and I just didn't know it.

So, in this specific case, I would have *much* preferred an obvious
failure (such as an infinite loop) than code that silently does the wrong
thing.

We've drifted far from the original topic. There is a distinct difference
between guarding against inaccuracies in floating point calculations:

# Don't do this!
total = 0.0
while total != 1.0:
total += 0.1

and guarding against typos in source code:

total = 90 # Oops, I meant 0
while total != 10:
total += 1

The second case is avoidable by paying attention when you code. The first
case is not easily avoidable, because it reflects a fundamental
difficulty with floating point types.

As a general rule, "defensive coding" does not extend to the idea of
defending against mistakes in your code. The compiler, linter or unit
tests are supposed to do that. Occasionally, I will code defensively when
initialising tedious data sets:

prefixes = ['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm',
'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
assert len(prefixes) == 16


but that's about as far as I go.
 
R

rusi

The alternative would be an infinite number of iterations, which is far far worse.

There was one heavyweight among programming teachers -- E.W. Dijkstra
-- who had some rather extreme views on this.

He taught that when writing a loop of the form

i = 0
while i < n:
some code
i += 1

one should write the loop test as i != n rather than i < n, precisely
because if i got erroneously initialized to some value greater than n,
(and thereby broke the loop invariant), it would loop infinitely
rather than stop with a wrong result.
 
C

Chris Angelico

There was one heavyweight among programming teachers -- E.W. Dijkstra
-- who had some rather extreme views on this.

He taught that when writing a loop of the form

i = 0
while i < n:
some code
i += 1

one should write the loop test as i != n rather than i < n, precisely
because if i got erroneously initialized to some value greater than n,
(and thereby broke the loop invariant), it would loop infinitely
rather than stop with a wrong result.

And do you agree or disagree with him? :)

I disagree with Dijkstra on a number of points, and this might be one of them.

When you consider that the obvious Pythonic version of that code:

for i in range(n,m):
some code

loops over nothing and does not go into an infinite loop (or throw an
exception) when n >= m, you have to at least acknowledge that I'm in
agreement with Python core code here :) That doesn't mean it's right,
of course, but it's at least a viewpoint that someone has seen fit to
enshrine in important core functionality.

ChrisA
 
S

Steven D'Aprano

Then more fool you for editing live code.

Ouch! That came out much harsher than it sounded in my head :(

Sorry Chris, that wasn't intended as a personal attack against you, just
as a comment on the general inadvisability of modifying code on the fly
while it is being run.
 
N

Neil Cerutti

Actually, I wouldn't do that with integers either.

I propose borrowing the concept of significant digits from the
world of Physics.

The above has at least three significant digits. With that scheme
x would approximately equal 17.3 when 17.25 <= x < 17.35.

But I don't see immediately how to calculate 17.25 and 17.35 from
17.3, 00.1 and 3 significant digits.
 
I

Ian Kelly

I propose borrowing the concept of significant digits from the
world of Physics.

The above has at least three significant digits. With that scheme
x would approximately equal 17.3 when 17.25 <= x < 17.35.

But I don't see immediately how to calculate 17.25 and 17.35 from
17.3, 00.1 and 3 significant digits.

How about this:

while round(x, 1) != round(17.3, 1):
pass

The second round call may be unnecessary. I would expect the parser
to ensure that round(17.3, 1) == 17.3, but I'm not certain that is the
case.
 
C

Chris Angelico

Ouch! That came out much harsher than it sounded in my head :(

Sorry Chris, that wasn't intended as a personal attack against you, just
as a comment on the general inadvisability of modifying code on the fly
while it is being run.

Apology accepted :)

You're right that, in theory, a staging area is a Good Thing. But it's
not always feasible. At work, we have a lot of Pike code that really
does keep running indefinitely (okay, we have yet to get anywhere near
a year's uptime for administrative reasons, but it'll be plausible
once we go live; the >1year figure came from one of my personal
projects). While all's going well, code changes follow a sane
progression:

dev -> alpha -> beta -> live

with testing at every stage. What happens when we get a problem,
though? Maybe some process is leaking resources, maybe we come under
some kind of crazy DOS attack, whatever. We need a solution, and we
need to not break things for the currently-connected clients. That
means editing the live code. Of course, there are *some* protections;
the new code won't be switched in unless it passes the compilation
phase (think "except ImportError: keep_existing_code", kinda), and
hopefully I would at least spin it up on my dev box before pushing it
to live, but even so, there's every possibility that there'll be a
specific case that I didn't think of - remembering that we're not
talking about iteration from constant to constant, but from variable
to constant or constant to variable or variable to variable. That's
why I would prefer, in language design, for a 'failed loop' to result
in no iterations than an infinite number of them. The infinite loop
might be easily caught on my dev test - but only if I pass the code
through that exact code path.

But to go back to your point about editing live code: You backed down
from the implication that it's *foolish*, but I would maintain it at a
weaker level. Editing code in a running process is a *rare* thing to
do. MOST programming is not done that way. It's like the old joke
about the car mechanic and the heart surgeon (see eg
http://www.medindia.net/jokes/viewjokes.asp?hid=200 if you haven't
heard it, and I will be spoiling the punch line in the next line or
so); most programmers are mechanics, shutting down the system to do
any work on it, but very occasionally there are times when you need to
do it with the engine running. It's like C compilers. Most of us never
write them, but a few people (relatively) actually need to drop to the
uber-low-level coding and think about how it all works in assembly
language. For everyone else, thinking about machine code is an utter
waste of time/effort, but that doesn't mean that it's folly for a
compiler writer. Does that make sense?

ChrisA
 
N

Nobody

I suppose this depends on the complexity of the process and the amount
of data that produced the numbers of interest. Many individual
floating point operations are required to be within an ulp or two of
the mathematically correct result, I think, and the rounding error
when parsing a written representation of a number should be similar.

Elementary operations (+, -, *, /, %, sqrt) are supposed to be within
+/- 0.5 ULP (for round-to-nearest), i.e. the actual result should be the
closest representable value to the exact result.

Transcendental functions should ideally be within +/- 1 ULP, i.e. the
actual result should be one of the two closest representable values to the
exact result. Determining the closest value isn't always feasible due to
the "table-maker's dilemma", i.e. the fact that regardless of the number
of digits used for intermediate results, the upper and lower bounds
can remain on opposite sides of the dividing line.
 
D

Dennis Lee Bieber

Prior to most compiler and hardware manufacturers standardizing on IEEE
754, there was no real way to treat float's implementation in a machine
independent way. Every machine laid their floats out differently, or used
different number of bits. Some even used decimal, and in the case of a
couple of Russian machines, trinary. (Although that's going a fair way
back.)
Xerox Sigma used an exponent that moved in "16", so a "normalized"
binary mantissa could have up to three leading 0 bits, unlike so many
others that assume a normalized mantissa will have a 1 bit (and then go
one step further and don't store that leading 1 bit <G>).
 
D

Dennis Lee Bieber

Analysis of error is a complicated topic (and is much older than digital
computers). These sorts of things come up in the real world, too. For
example, let's say I have two stakes driven into the ground 1000 feet
apart. One of them is near me and is my measurement datum.

I want to drive a third stake which is 1001 feet away from the datum.
Do I measure 1 foot from the second stake, or do I take out my
super-long tape measure and measure 1001 feet from the datum?

On the same azimuth? Using the "super long tape" and ensuring it
traverses the 1000 foot stake is probably going to be most accurate --
you only have the uncertainty of the positioning of the tape on the
datum, and the small uncertainty of azimuth over the 1000 foot stake.
And even the azimuth error isn't contributing to the distance error.

Measuring 1 foot from the 1000 foot stake leaves you with any error
from datum to the 1000 foot, plus any error from the 1000 foot, PLUS any
azimuth error which would contribute to shortening the datum distance.
 
C

Carlos Nepomuceno

----------------------------------------
To: (e-mail address removed)
From: (e-mail address removed)
Subject: Re: Short-circuit Logic
Date: Thu, 30 May 2013 19:38:31 -0400



On the same azimuth? Using the "super long tape" and ensuring it
traverses the 1000 foot stake is probably going to be most accurate --
you only have the uncertainty of the positioning of the tape on the
datum, and the small uncertainty of azimuth over the 1000 foot stake.
And even the azimuth error isn't contributing to the distance error.

Measuring 1 foot from the 1000 foot stake leaves you with any error
from datum to the 1000 foot, plus any error from the 1000 foot, PLUS any
azimuth error which would contribute to shortening the datum distance.

Just because you have more causes of error doesn't mean you have lesser accurate measures.

If fact, errors may compensate each other. It all depends on the bias (accuracy) and variation (precision) involved in the measurements you are considering.
 
R

Rick Johnson

And do you agree or disagree with him? :) I disagree with
Dijkstra on a number of points, and this might be one of
them. When you consider that the obvious Pythonic version
of that code:

for i in range(n,m):
some code

Maybe from your limited view point. What if you need to perform operations on a sequence (more than once) in a non-linear fashion? What if you need to modify the sequence whilst looping? In many cases your simplistic "for loop" will fail miserably.

py> lst = range(5)
py> for n in lst:
.... print lst.pop()
4
3
2

Oops, can't do that with a for loop!

py> lst = range(5)
py> while len(lst):
.... print lst.pop()
4
3
2
1
0
 
N

Nobody

Measuring 1 foot from the 1000 foot stake leaves you with any error
from datum to the 1000 foot, plus any error from the 1000 foot, PLUS any
azimuth error which would contribute to shortening the datum distance.

First, let's ignore azimuthal error.

If you measure both distances from the same origin, and you have a
measurement error of 0.1% (i.e. 1/1000), then the 1000' measurement will
actually be between 999' and 1001', while the 1001' measurement will be
between 1000' and 1002' (to the nearest whole foot).

Meaning that the distance from the 1000' stake to the 1001' stake could be
anywhere between -1' and 3' (i.e. the 1001' stake could be measured as
being closer than the 1000' stake).

This is why technical drawings which include regularly-spaced features
will normally specify the positions of features relative to their
neighbours instead of (or as well as) relative to some origin.

When you're dealing with relative error, the obvious question is
"relative to what?".
 
R

Roy Smith

Nobody said:
First, let's ignore azimuthal error.

If you measure both distances from the same origin, and you have a
measurement error of 0.1% (i.e. 1/1000), then the 1000' measurement will
actually be between 999' and 1001', while the 1001' measurement will be
between 1000' and 1002' (to the nearest whole foot).

Meaning that the distance from the 1000' stake to the 1001' stake could be
anywhere between -1' and 3' (i.e. the 1001' stake could be measured as
being closer than the 1000' stake).

This is why technical drawings which include regularly-spaced features
will normally specify the positions of features relative to their
neighbours instead of (or as well as) relative to some origin.

Not to mention "Do not scale drawing" warnings. Do they still put that
on drawings? It was standard practice back when I was learning drafting.
When you're dealing with relative error, the obvious question is
"relative to what?".

Exactly. Most programmers are very poorly training in these sorts of
things (not to mention crypto, UX, etc). I put myself in that camp too.
I know just enough about floating point to understand that I don't
really know what I'm doing. I would never write a program where
numerical accuracy was critical (say, stress analysis of a new airframe
or a nuclear power plant control system) without having somebody who
really knew that stuff on the team.
 
D

Dennis Lee Bieber

If you measure both distances from the same origin, and you have a
measurement error of 0.1% (i.e. 1/1000), then the 1000' measurement will
actually be between 999' and 1001', while the 1001' measurement will be
between 1000' and 1002' (to the nearest whole foot).
I presumed the /same/ tape measure was used for the initial 1000 ft
measurement, and that this tape measure is fairly in-elastic... Such
that even your 0.1% error is constant in the first 1000 foot -- that is,
any error in the original 1000 feet is reproduced by the use of the same
tape. And that, in turn, would mean only the last foot difference would
be off, and the error would be in scale (if the 1000ft is 0.1% short,
the 1001ft is also 0.1% short of the total); but not that the first
distance could be short and the second could be long.
 
8

88888 Dihedral

Steven D'Apranoæ–¼ 2013å¹´5月30日星期四UTC+8上åˆ10時28分57秒寫é“:
EXACTLY!



The problem does not lie with the *equality operator*, it lies with the

calculations. And that is an intractable problem -- in general, floating

point is *hard*. So the problem occurs when we start with a perfectly

good statement of the facts:



"If you naively test the results of a calculation for equality without

understanding what you are doing, you will often get surprising results"



which then turns into a general heuristic that is often, but not always,

reasonable:



"In general, you should test for floating point *approximate* equality,

in some appropriate sense, rather than exact equality"



which then gets mangled to:



"Never test floating point numbers for equality"



and then implemented badly by people who have no clue what they are doing

and have misunderstood the nature of the problem, leading to either:



* de facto exact equality testing, only slower and with the *illusion* of

avoiding equality, e.g. "abs(x-y) < sys.float_info.epsilon" is just a

long and slow way of saying "x == y" when both numbers are sufficiently

large;



* incorrectly accepting non-equal numbers as "equal" just because they

happen to be "close".





The problem is that there is *no one right answer*, except "have everyone

become an expert in floating point, then judge every case on its merits",

which will never happen.



But if nothing else, I wish that we can get past the rank superstition

that you should "never" test floats for equality. That would be a step

forward.

The string used to represent a floating number
in a computer language is normally in the decimal base of very
some limited digits.

Anyway with the advances of A/D-converters in the past 10 years
which are reflected in the anttena- transmitter parts in phones,
the long integer part in Python can really beat the low cost
32- 64 bit floating computations in scientific calculations.
 
M

Michael Torrie

This is why technical drawings which include regularly-spaced features
will normally specify the positions of features relative to their
neighbours instead of (or as well as) relative to some origin.

If I am planting trees, putting in fence posts, or drilling lots of
little holes in steel, I am actually more likely to measure from the
origin (or one arbitrary position). I trust that the errors
accumulating as the tape measure marks were printed on the tape is less
than the error I'd accumulate by digging a hole, and measuring from
there to the next hole. And definitely when drilling a series of holes
I'll never measure hole to hole to mark. If I measure from the origin
than any error for the hole is limited to itself as much as possible
rather than passing on the error to subsequent hole positions. If I was
making a server rack, for example, having the holes consistently near
their desired position is necessary. Tolerances are such that my hole
can be off by as much as a 1/16" of inch of my desired position and it
would still be fine, but not if each hole was off by an additional
1/16". I guess what I've described is accuracy vs precision. In the
case of the server rack accuracy is important, and precision can be more
coarse depending on the screw size and the mount type (threaded hole vs
square hole with snap-in nut).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,470
Messages
2,571,809
Members
48,797
Latest member
PeterSimpson
Top