User: Password:
|
|
Subscribe / Log in / New account

A false midnight

A false midnight

Posted Mar 13, 2014 22:51 UTC (Thu) by m45t3r (subscriber, #92849)
In reply to: A false midnight by pboddie
Parent article: A false midnight

Well, various language decisions that maybe made sense when the only other major language was C, but nowadays don't make sense anymore.

One gotcha that I had with Python 2.x is the way it handles divisions. If you try the following code on a Python 2.x interpreter:

3/4

You'd get:

0

While with Python 3.x you get:

0.75

Of course, the first example is C-like, while the second is what you'd expect from a non-typed language. To get the same result on Python 2.x you'd have to write:

3.0/4

Even this doesn't work:

float(3/4) #since the 3/4 is evaluated before the conversion

This actually made me waste 20 minutes debugging a program because the error just doesn't made any sense to me. I think this is the same gotcha as the article shows.


(Log in to post comments)

A false midnight

Posted Mar 14, 2014 10:28 UTC (Fri) by khim (subscriber, #9252) [Link]

Yes, it's similar gotcha. But there are a difference: from __future__ import division handlily fixes problems with division, but you count not do anything like that to fix problems with DateTime.

A false midnight

Posted Mar 14, 2014 16:09 UTC (Fri) by hummassa (subscriber, #307) [Link]

Well, actually... nothing keeps you from doing a pragmatic "fixdatetime" module and "from fixdatetime import fixdatetime" so that DateTime.__notzero__() is redefined to a saner default for the scope of your programa, isn't it?

A false midnight

Posted Mar 14, 2014 18:38 UTC (Fri) by pboddie (guest, #50784) [Link]

Integer division has nothing to do with the datetime problem described, or at least it isn't the cause of it. The truth value of an object is determined by the result of the __nonzero__ method in Python 2, perhaps with some exceptions for the built-in types (I haven't looked at the CPython code for a while) that produce the same effect anyway.

As is pointed out already here, integer division was "fixed" with the "future" division feature - the origin of "from future" if I remember correctly - and was one of the original "Python warts" that was fixed in Python 2, although I did argue for keeping thing as they are/were and adding a new operator to produce the "expected" result. (It is only "expected" depending on context and on the user's background, of course, and I remember pointing out that the case for the change could have been made more concisely and persuasively.)

A false midnight

Posted Mar 16, 2014 15:31 UTC (Sun) by m45t3r (subscriber, #92849) [Link]

Well, OP asked for similar gotchas on Python 2.x language, not what is the reason of the problem (that is explained on the text anyway).

A false midnight

Posted Mar 18, 2014 20:19 UTC (Tue) by pboddie (guest, #50784) [Link]

I was explicitly responding to the assertion...

I think this is the same gotcha as the article shows.

...which, of course, it isn't.

I'm very familiar with general complaints about Python - and more specifically, Python 2 - but most of those are regarded as concerning widely known and accepted, if not widely liked, behaviour. The topic of the article concerns something that divides the opinions even of those developing Python 3 as to whether the behaviour should be fixed or is even excusable.

A false midnight

Posted Mar 17, 2014 13:07 UTC (Mon) by ikm (subscriber, #493) [Link]

> Of course, the first example is C-like, while the second is what you'd expect from a non-typed language

Python is strongly-typed. And I would actually expect 0 in the case of 3/4 since we're dividing two integers, not floats, and in all strongly-typed languages I know of the division operation doesn't change type.

A false midnight

Posted Mar 18, 2014 1:18 UTC (Tue) by hummassa (subscriber, #307) [Link]

3/4 has nothing to do with strong typing, but it is related to the type of
operator/(int, int)
... It's just like defining
complex operator+(real r, imaginary i)
and having
auto c = 3 + im(4);
yielding a complex "c".

A false midnight

Posted Mar 18, 2014 20:23 UTC (Tue) by pboddie (guest, #50784) [Link]

Yes, it's mostly something seen in C (and other languages) that became policy. Indeed, I imagine there's some group theory related elegance that appeals when defining such policy. Quite some time ago, of course, the decision to change this was made, but I can easily see people agitating for "int/int -> decimal" and "int/int -> fraction", and there are languages which have gone down those paths, too.

A false midnight

Posted Mar 19, 2014 0:29 UTC (Wed) by hummassa (subscriber, #307) [Link]

in C, "int/int -> int";

the whole "int/int -> float" thing came from Pascal/Modula via Algol IIRC... they had "int DIV int -> int" to compensate.

A false midnight

Posted Mar 20, 2014 15:35 UTC (Thu) by Wol (guest, #4433) [Link]

Given that, outside of programming, 3/4 DOES equal 0.75, then of course.

While typing is a good way of getting the compiler to spot your mistakes, it is wholly unnatural from a maths p-o-v. Looking at things as a mathematician and not as a computer scientist, a number is a number is a number, and this categorisation into things like ints, longs, reals, double-precisions etc is abnormal. If I wanted 3/4 to give me 0 I'd use mod, not divide.

Cheers,
Wol

A false midnight

Posted Mar 21, 2014 8:20 UTC (Fri) by mbunkus (subscriber, #87248) [Link]

Well, from the maths POV a lot of stuff in programming doesn't make sense. Underflows/overflows? All operations should operate on arbitrarily-sized numbers, of course. And 0.3 - 0.2 - 0.1 should always == 0.0, of course. And infinity handling could be better, too – you know, there are no exceptions in real life (or processor flags or…) ;)

So what's my point? Not sure, maybe something like: computer maths aren't like real world maths anyway, so arguing we should make an operation like division more like real-world maths doesn't make much sense to me.

However, int/int = int makes a lot of sense to me, coming from C and C++. Those are languages whose explicit goal is to make you pay for what you use, but not for anything more. Back when C was specified there were no FPUs in processors. Floating point number operations were exceedingly expensive. They're still more expensive than their integer counterparts. So yes, keeping division of integer operands an integer makes a lot of sense.

Note: I'm only arguing C/C++ here, not Python, as one of the OPs mentioned C/C++, and the reason for the integer division is simply speed/efficiency and giving the programmer control over the runtime cost.

A false midnight

Posted Mar 24, 2014 17:21 UTC (Mon) by quanstro (guest, #77996) [Link]

from a math pov, programming DOES (generally) make sense. by generally, i mean with the exception of floating point. operations in math are defined over a thing. that thing might be a group, a field or a ring. so there is such a thing as division on Z (integers), and there is such a thing as division on R (real numbers), but Z is not a division ring. programming typically operates on Zn, where n = 2^m, m is typically one of 8,16,32,64 for unsigned numbers. signed numbers are similar. note that the fact that Zn is not a division ring (i.e. there's a n satisfying n = a/b for all a, b in Zn) does *not* make this strange to mathematicians.

floating point is problematic. it's not a ring. not abelian. and has all sorts of other ... rather unexpected properties.

A false midnight

Posted Mar 20, 2014 8:58 UTC (Thu) by dakas (guest, #88146) [Link]

and in all strongly-typed languages I know of the division operation doesn't change type.
Algol and Pascal come to mind right away.

A false midnight

Posted Mar 20, 2014 12:34 UTC (Thu) by zuki (subscriber, #41808) [Link]

I've been using mixed Python 2/3 environments recently, and after getting accustomed to the new division behaviour, when I use Python 2, the old truncating behaviour is one of the bug "sources". I've had to add 'from __future__ import division' more than once in the last few weeks to fix actual bugs in code. Of course one can say that I should simply remember to do a cast to float here and there, but at least for me, it feels much easier and results in fewer bugs to do an explicit truncating division with // when necessary.

A false midnight

Posted Mar 20, 2014 12:51 UTC (Thu) by apoelstra (subscriber, #75205) [Link]

I think in general if you are doing a project of sufficient complexity that you need to be aware of the type of your objects (but are not), Python is the wrong language for you, and something where types are explicitly described and tracked would be better.

It seems very counterintuitive to me that mathematical operators would change numeric types, I'd expect that adding extra magic type-conversion rules to a language in which everything type-related is already done magically will increase your potential for bugs.

A false midnight

Posted Mar 20, 2014 13:31 UTC (Thu) by zuki (subscriber, #41808) [Link]

If I do a division, I *am* aware of the type... They are numbers, and specifically numbers.Real. In Python, both an int and and a float are subclasses of numbers.Real. For almost all intents and purposes, the difference between them is unimportant, but division is one them. Things might work when called with 5.0, but break if that argument changed to 6. I'd rather avoid that.

> It seems very counterintuitive to me that mathematical operators would change numeric types

I don't understand that... In "real" math, integers are also reals, and 5 is exactly the same as 5.0, and 4.999..., and nothing should change if one is substituted with the other. And operations *do* change type, e.g. with division, raising to the power or logarithm with might get a (non-integer) real from integers, and on the other hand, floor and ceiling and division or multiplication, we might get an integer from reals... I like it when Python keeps up this illusion, whenever feasible.

A false midnight

Posted Mar 20, 2014 15:43 UTC (Thu) by renox (subscriber, #23785) [Link]

I think that the main reason why other language doesn't do this is that reals have several possible representations: float32, float64, float intervals, rationals,etc which make choosing the 'real' type for the division's result quite arbitrary..

A false midnight

Posted Mar 20, 2014 16:16 UTC (Thu) by apoelstra (subscriber, #75205) [Link]

Well, even in mathematics there are different 'types' corresponding to the set in which an object is supposed to lie. For example, the integer 2 is prime while the rational 2 is not; the rational 2 is a unit while the integer 2 is not. The real 2 is a square while the rational or integer 2 is not, etc. etc.

Often there are implicit embedding maps from, as in your example, the integers into the reals. As long as they are actual embeddings (i.e. injective and a homomorphism of the appropriate type) they are left implicit, but otherwise you need explicit maps to move between types. And in fact this type information can cause confusion; exponentiation might map from a group of numbers under addition to a group under multiplication --- and if these groups have the same (or nearly the same) underlying set, it can cause confusion and bad logical implications, for exactly the same reason that type conversions are a source of bugs in programming.

So it is nothing new that 2 and 2.0 are different types in programming. (Though the truncating behaviour of '/' is pretty weird --- in mathematics the integers are not a division ring and division is not done on them in general.) And in programming there are dramatic differences: for one thing, there is no 'real' type since almost all reals cannot be described in finite information, while every integer can. With floating point types, all arithmetic operations are approximations (and their errors can compound in surprising ways) while integer operations are precise (everywhere that they are defined).

A false midnight

Posted Mar 20, 2014 20:28 UTC (Thu) by nybble41 (subscriber, #55106) [Link]

> Well, even in mathematics there are different 'types' corresponding to the set in which an object is supposed to lie. For example, the integer 2 is prime while the rational 2 is not; the rational 2 is a unit while the integer 2 is not. The real 2 is a square while the rational or integer 2 is not, etc. etc.

That's certainly an interesting way to look at it. Usually one doesn't consider the set an object was *selected from* to be an identifying characteristic of the object. The same object can belong to multiple sets, and, mathematically speaking, there is no difference between 2, 2/1, and 2.0. The number 2 is a member of the set of integers, and the set of rationals, and the set of reals, all at the same time. It has a square root in the set of reals, but not in the set of integers. It's a unit in the rings of rationals and reals but not in the ring of integers. The set qualifier goes with the property (has an integer square root, is a unit in the ring of rationals), not the number.

I'm not quite sure what you mean when you say that "rational 2" is not prime... "rational 2" is the same number as "integer 2", which is a prime number. If you're referring to a different kind of prime, like a prime field, then you'll have to be more specific before you can classify 2 as prime or not.

A false midnight

Posted Mar 21, 2014 15:10 UTC (Fri) by apoelstra (subscriber, #75205) [Link]

> That's certainly an interesting way to look at it. Usually one doesn't consider the set an object was *selected from* to be an identifying characteristic of the object. The same object can belong to multiple sets, and, mathematically speaking, there is no difference between 2, 2/1, and 2.0.

The reason I look at it this way is because I have encountered "type errors" when building mathematical proofs, for example when working with two groups whose group operations are both denoted as multiplication. The extra mental effort to tag every object with its originating set, in order to see which operations are available and meaningful, feels exactly like the extra mental effort to analyze code in an implicitly typed language.

Often hypotheses in mathematical proofs are actually type declarations, as in "Let M be a compact closed manifold". Early "proofs" before the late 19th century would lack this type information (in fact people were unaware that they needed it) and the result was an enormous body of confused and incorrect mathematical work. For example it was common practice for "proofs" to have corresponding lists of counterexamples. This is evidence that it's very easy and natural for people to lapse into defective reasoning when faced with implicit typing. Though of course programmers have a big advantage over 18th-century mathematicians in that we know not only that the types are there, but also a complete (and small) list of all available types.

Maybe this is a confused analogy. But I don't think it is, and it's helpful to me in both mathematics and programming.

> I'm not quite sure what you mean when you say that "rational 2" is not prime... "rational 2" is the same number as "integer 2", which is a prime number. If you're referring to a different kind of prime, like a prime field, then you'll have to be more specific before you can classify 2 as prime or not.

It's a bit of an aside, but I mean prime in the sense of commutative rings: an element p is prime if it is (a) nonzero and not a unit, and (b) if p divides ab, it divides one of a or b.

In the ring of integers 2 is prime; in the ring of rationals 2 is not (because it is a unit, which means it has a multiplicative inverse).

A false midnight

Posted Mar 22, 2014 0:03 UTC (Sat) by mathstuf (subscriber, #69389) [Link]

> Usually one doesn't consider the set an object was *selected from* to be an identifying characteristic of the object.

Well, in algebra "1 - 1 + 1 - 1 + …" is undefined since algebra cannot reach the end; in the realm of infinite series, ½ is a valid value for the series (and the only one). There's a similar thing with any of ζ(n) when n is a negative odd integers (ζ(-1) = 1 + 2 + 3 + 4 + … = -1/12, ζ(-3) = 1 + 8 + 27 + 64 + … = 1/120, etc.). Computations definitely rely on the context in which they reside; I see no reason why numeric values would be exempt.

A false midnight

Posted Mar 22, 2014 9:54 UTC (Sat) by Jandar (subscriber, #85683) [Link]

ζ(n) with negative odd n is infinite.

A false midnight

Posted Mar 22, 2014 10:46 UTC (Sat) by mathstuf (subscriber, #69389) [Link]

Depends on the context[1] :) . Each series resulting from the ζ(n) with n as a negative, odd integer has a single, useful value which can be used in its place when it occurs in equations (physics, chemistry, etc.).

[1]https://en.wikipedia.org/wiki/Particular_values_of_Rieman...

A false midnight

Posted Mar 22, 2014 15:23 UTC (Sat) by Jandar (subscriber, #85683) [Link]

It's embarrassing to see how much one can forget. My mathematical studies lie some 20 years in the past and almost anything about sheaf and germs is lost.

A false midnight

Posted Mar 21, 2014 10:42 UTC (Fri) by moltonel (subscriber, #45207) [Link]

> It seems very counterintuitive to me that mathematical operators would change numeric types, I'd expect that adding extra magic type-conversion rules to a language in which everything type-related is already done magically will increase your potential for bugs.

Do you also find it intuitive that adding 1 to a big enough value yields a very low negative value ? Some languages, python included, will convert that machine-level integer into an object that can store arbitrarily long numbers (BTW, it's transparent in python3, but python2 appends an 'L' suffix to that number), and that is a good thing in most cases.

Type-changing operators are actually common and expected, as long as they follow some common sense (how about the classic "date - date = duration" for example ?). And common sense says that numbers are numbers are numbers.

You only find "2/3=0" intuitive because you've been formated by years of using languages where this is the case for technical reasons. But tell that to any non-programmer or user of other languages and they'll be asking "WTF?". Python3 fixed that gotcha. This is a Good Thing.

A false midnight

Posted Mar 21, 2014 15:37 UTC (Fri) by apoelstra (subscriber, #75205) [Link]

Integer overflow is a property of the integer type in many languages (though as you say, not Python). It's intuitive that a fixed-width type is going to overflow at some point, by the pigeonhole principle. In languages like Python or Lisp which hold arbitrarily-sized integers, this overflow is not a property of the type. In either case there is no type transition caused by adding 1 repeatedly to an integer, so I can be sure of the types of my objects before and after the addition.

I really don't like that in C, signed integer overflow is undefined behaviour -- in this case, I can't be sure of anything after the addition. :(

I like your "date - date = duration" example. I think you're right that POLS says division should have a floating-point output (and the truncation behaviour should be relegated to some other construct, e.g. a truncate() function), since what '/' does to ints in C is not, in fact, division.

In this case I can still be sure of the types of objects before and after the operation --- of course there is no intrinsic reason that these should be the same.


Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds