Update: There is now a TIP (#237, [1]) which goes ahead with a more ambitious plan than what is sketched below, and since that TIP was put forth by a TCT member, there's a real good chance that it will be ready for 8.5. Yummy!This also means that the following may becoming more of historical interest.
First, an explanation of the title might be in order. It is a sad fact that what passes for "integers" in current Tcl documentation isn't really integers, but rather a (sometimes crude) imitation of the true thing. They quite often fail to satisfy important properties of proper integers. Take for example the expressions
$a * $a >= 0 $a * $b % $b == 0 ($a > $b) == ($a+$c > $b+$c)All of these should return 1 (or an error) when a, b, and c are proper integers, but none of them do in general in current Tcl. Behold:
% info patchlevel 8.4.5 % set tcl_platform(wordSize) 4 % set a 54321; expr {$a * $a} -1344196255 % set a 54321; set b 45678; expr {$a * $b % $b} 43688 % set a 1234567890; set b 0; set c $a; expr {($a > $b) == ($a+$c > $b+$c)} 0Problems caused by "integers" not being proper integers are generally difficult to spot in testing, because test cases tend to be small, whereas the problems show up mostly for large numbers. It is also something that is expensive to avoid at the development stage, because analyzing an algorithm to see if "N-bit integers" are sufficient is hard work. In a language such as C (with its cost ratio between development time and running time) this may be time well spent, but in Tcl (that has quite a different ratio between the two) it should be better to simply have the internal representation grow to whatever size is necessary. In effect this means that the integers will have to be proper integers.
Proper integers in Tcl: Comparatively easy edit
Implementing proper integers is much easier in Tcl than it is in many other languages. It is not necessary to devise any new data type and an input/output library for that data type, because in Tcl everything is a string. The strings that are canonical forms of integers are those matched by the regular expression0|-?[1-9][0-9]*and that is all we need. There are other strings that should perhaps also be valid integers (hexadecimal and octal numbers, perhaps non-canonical forms with extra leading zeros, signs, or surrounding whitespace, and maybe even non-ASCII numerals), but restricting attention to these for the moment will simplify the discussion.The specification of operations is no problem either, because the arithmetic operations are quite well-defined and well-known (most of them are part of any all-round education). The bit-wise operations are usually not taught in school, but these too can be defined for integers of any size. The relevant defining identities are, apart from commutativity, that
0 & 0 == 0 -1 & 0 == 0 -1 & -1 == -1 0 | 0 == 0 -1 | 0 == -1 -1 | -1 == -1 0 ^ 0 == 0 -1 ^ 0 == -1 -1 ^ -1 == 0 (2*$a) & (2*$b) == 2*($a & $b) (2*$a + 1) & (2*$b + 1) == 2*($a & $b) + 1 (2*$a + 1) & (2*$b) == 2*($a & $b) (2*$a) | (2*$b) == 2*($a | $b) (2*$a + 1) | (2*$b + 1) == 2*($a | $b) + 1 (2*$a + 1) | (2*$b) == 2*($a | $b) + 1 (2*$a) ^ (2*$b) == 2*($a ^ $b) (2*$a + 1) ^ (2*$b + 1) == 2*($a ^ $b) (2*$a + 1) ^ (2*$b) == 2*($a ^ $b) + 1 ~$a == -$a - 1for all integers a and b. These definitions are equivalent to the usual definitions in terms of bits in memory words if the word length is large enough that all operands can be stored as signed quantities in two's complement representation.In other words: the specs are strikingly easy. The implementation is a bit more work, but still fairly straightforward. In particular, it is possible to defer tricky optimization issues until such a time that one really needs them.
Why do we need integers? edit
One way of discovering where integers are used in Tcl is to imagine what Tcl would be like if all core (sub)commands that use integers were removed. It is however necessary to refine this idea slightly, because Tcl uses 0 and 1 for boolean true and false virtually everywhere. Thus we should first imagine that the string representations of boolean values were changed (or that we're using something like Success-fail control structures to cope without boolean values), before looking for places where integers are used. What is left roughly falls in three categories:- Internal counting
- Integers used to enumerate things that exist inside the Tcl system. Examples include lindex, string length, and regsub (return value). In all cases, the integers that can be stored in single machine words, because there cannot be more things around than what is possible to address.
- External counting
- Integers used to enumerate things that exist outside the Tcl system, but for the manipulation of which there exists Tcl core commands. Examples include seek, clock, and various file subcommands.
- User-defined data
- Integers in some sort of data that are neither defined in nor directly supported by the Tcl core. Here the main workhorse is expr, with occational help from binary, format, scan, and lsort, but that is pretty much it.
Implementing proper integers edit
There are strong reasons for making Tcl integers semantically uniform (e.g. supporting expr operating on arbitrarily large integers, rather than inventing some new command that one would have to use for all operations on large integers), such as the aforementioned cost ratio. There are equally strong reasons for seeking an implementation that is as fast as the current for "small" (that can be accurately represented in machine words) integers. Finally, it would probably be nice to avoid burdening the core with a full-fledged multiple precision library (it could require special expertise to maintain and might present some porting problems). Are these goals possible to combine? I certainly believe they are.The first thing to observe is that there doesn't have to be a uniform implementation just because there are uniform semantics; even for highly optimized arithmetics libraries these are often several implementations, each of which outperforms the others for some range of input data sizes. For integers that fit into machine words (i.e., most of the integers occuring in Tcl programs) the raw machine operations are indeed the most efficient, so it makes sense to handle e.g. addition of small integers (internal representation of type tclIntType) through a single C addition (in the INST_ADD branch of TclExecuteByteCode). Since this takes good (and fast) care of almost all integers that occur, we can then afford to do something more complicated for the few cases that remain.One approach that I find nicely flexible is to handle everything else via a callback to the script level, say to some overflow procedure. (This would then become a sort of relative of the unknown procedure.) The syntax could be e.g.- overflow op arg ?arg ...?
overflow + 1234567890 9876543210could return 11111111100. Actually implementing such an overflow procedure is of course quite some work, but a lot of the necessary code already exists on this Wiki. For those operations that are missing, I think that it in principle should be OK to have an overflow procedure that simply works as
proc overflow {op args} { if {[llength $args]>1} then { set expr [join [linsert $args 1 $op]] } else { set expr "$op [join $args]" } error "Arithmetic overflow in $expr" "" [list ARITH IOVERFLOW $expr] }An error saying "I can't do this right." is generally preferable to silently returning an incorrect result.Alternatively, it would be possible to install an overflow procedure that emulates the current improper behaviour, through something like
proc overflow {op args} { set E [list] foreach arg $args {lappend E "wide($arg)"} # Assuming all operations are unary prefix or binary infix. # Assuming there won't be overflows with wides. return [expr "int([join [linsert $E end-1 $op]])"] }This would probably be the way to go for an init.tcl definition of overflow in Tcl 8.5, as it provides some backwards compatibility. DKF has been observed expressing concerns about that matter.One issue here is however that overflows have to be detected not only when interpreting strings as integers, but also when an operation on two integers that fit in machine words produces a result that is too big to fit. It seems processor manufacturers are well aware of the need to detect this -- in all processors I know the details of, there is some carry or overflow flag that can be inspected after each operation -- but the C language does not provide anything in particular for this need. Still, there are ways of detecting overflow. For addition and subtraction one can do
overflow = (sum = a+b) >> 1 != ((a>>1) + (b>>1) + (a&b&1)); overflow = (diff = a-b) >> 1 != ((a>>1) - (b>>1) - (~a&b&1));whereas for products it is probably necessary to make use of a result twice as wide as the operands; assuming long is 32 bits and long long 64 bits, one can do:
long a,b; long long prod; overflow = 0 != ((prod = a*b) + 0x80000000 >> 32);(but I'm no C programmer, so I don't know whether compilers will in general make from the above what I think they will). The only other Tcl 8.5 operations that may cause an overflow are << and ** (unless that produces floats), both of which probably need some special attention anyway.
Implementation through indirection edit
Here are some ideas, in more practical detail, for how the above could be implemented. The overall idea is however to as much as possible avoid making choices, so that the core will not get stuck with a poor design. Hence this "implementation" consists mostly of a sketch of an interface, to which an extension might attach a working implementation...Suppose a new object type tclBigIntType was created for those integers that are too big to have a tclIntType representation. Then it would make sense for library procedures (such as Tcl_GetIntFromObj) that interpret TclObjs as integers to, in the case that the given string is a syntactically correct integer but too big to have a tclIntType representation, instead set the type of the TclObj to tclBigIntType (discarding any old internal representation and not creating a new one). This would have the effect that the core only has to try this conversion once (for each value); as soon as something is a tclBigIntType TclObj, it is known to be big enough to not correspond to anything in the core. Thus in particular, a command like lindex can safely return an empty string whenever it is given a tclBigIntType index, since that has to be out of bounds of the list. Quite a lot of the things the core does with integers is like this, although the arithmetic is not.Suppose further that the internal representation of a tclBigIntType TclObj is a twoPtrValue, where the first pointer points to a "subtype struct" and the second points to the actual data. This can abstract away all details of a native representation for big integers from the core while at the same time allow the core to take advantage of the existence of such a representation. If the subtype pointer is NULL then there is no internal representation (the value is only stored in the string representation); this can be considered "the null subtype" of tclBigIntType and it is the only one that the core needs to handle by itself.Otherwise the subtype struct would be a collection of function pointers that the core could rely on for carrying out any operations on the internal representation. In particular the core wouldn't know how to free, duplicate, or generate a string from the internal representation, so the freeIntRepProc, dupIntRepProc, and updateStringProc in the tclBigIntType Tcl_ObjType would simply (do nothing if the subtype pointer is NULL, but otherwise) pass the call on to some counterpart function in the subtype struct. Besides these (and this is the nice thing), the subtype struct could also contain pointers to functions performing each of the standard arithmetic operations. Rather than relying on the overflow callback when adding two big integers of the same subtype, the core could call on some addUsingIntRepProc of the common subtype struct for this. Then the overflow callback would only be used for arithmetic operations on big integers of null or different subtypes (typically one of which would be null). Presumably any extension that defines a tclBigIntType subtype would also provide a compiled overflow procedure that returns big integers with that subtype, so the overflow callback would never be used more than once for each value given as a string.There is however nothing in this that prevents having more than one non-null tclBigIntType subtype. Typically TclObjs would tend to have the subtype returned by the overflow procedure and having something that produces TclObjs of another subtype would lead to shimmering whenever the two meet, but there is no principal problem.What about wides? edit
What place does wides have in the above scheme, then? They could of course be some intermediate between tclIntType and tclBigIntType, but there is a more interesting approach. There is a certain (user-defined data) need for an integer-modulo-some-power-of-2 data type, as checksum algorithms and the like are often designed with such a thing in mind. Since the characteristic property of a wide currently is that "it is at least 64-bits wide", the step to "it is an integer modulo 2**N for some N>=64" is rather small. This would however necessitate a change of string representation for wides, as they currently cannot be distinguished from integers (despite behaving differently). One string representation that is fairly backwards compatible is as wide(int), where "int" is anything that is a valid integer.escargo 19 Feb 2004 - I have heard (and can only claim that this might be rumor, rather than fact) that today's highly pipelined processors can encounter mathematical overflow/underflow without being able to pinpoint where in the multistage arithmetic pipeline that the error occurred. This prevents the underlying implementation from being able to specifically indicate where the error happened. Whatever you are specifying here should not assume that detection of an error can also inform you where the error occurred. (Perhaps you have no choice; if that is correct, then you might have introduced some platform dependence, on the processor, compiler, or math libraries, that you might not have expected.)Lars H: Even if that was true it would be irrelevant, simply because C doesn't give us access to the overflow detection in the processor and we therefore have to explicitly compute it, as shown above. The rumor furthermore doesn't ring true. It might be the case that checking for overflow at a particular instruction will prevent pipelining of that instruction, but in view of how seldom there would be a need to do such a check, that can hardly be a problem.Seriously though, any on-topic comments, anyone?GPS: Great ideas! I say we start with a few small apps that do addition, multiplication and so forth with large numbers. I've wanted to play with the carry flag in assembly language anyway, so this interests me. http://www.snippets.org has some BigNum code in the C section that might be useful. Perhaps we could have some inline assembly for systems that support it, and the slower BigNum code for portability. We already have Win32 assembly code in Tcl for some DLL crap, although it's fairly simple. I'll post to the Wiki when I have something that works with addition.Lars H: What do you mean by "start with a few small apps"? Separate applications for addition and so on? I don't quite see the point of that, because the functionality should be made available inside Tcl. But if you by "apps" mean extensions or some such then I agree.A possible step-by-step implementation could be something like this:
LV The issue really being addressed here is not proper integers but large integers.Change the above values of a to something like 107 or 1376 and you will see the results that you indicate that you expect.Then, try the same thing in a language like C (or many other scripting languages) and see quite similar results.What is really being asked for is not even 64 bit integers, but infinite sized integers.I'm not saying that asking for that is somehow unworthy - just wanting to be certain that someone reading this understands that over the years, Tcl maintainers have always indicated that Tcl's integers are limited by C's representation of integers by variables of size int.Lars H: The page is mainly aimed at people interested in the internals of Tcl, and its title was chosen with this in mind, to forestall precisely that kind of reply.Proper integers are sometimes very large. They have to be, because for any given integer there must be a successor which is larger. "Integers" that are limited in size will therefore be improper, at least in some respect. Avoiding some of the usual lingo makes the problems easier to spot, because the effect of that lingo is partly to turn bugs into features by documenting them: it doesn't really make anything better, but it does make the whole thing Somebody Else's Problem (cf. SEP Field).A large exposure to C, which is hard to avoid for people interested in the internals of Tcl, does tend to distort one's perceptions on these matters. (Not a sarcasm, but a serious observation.) Every schoolchild knows that if you've got 1500 millions and add another 1000 millions to that, then you've got 2500 millions. Tcl currently asks C straight off however, and C says that the sum of 1500 millions and 1000 millions is -1794967296, which from most points of view is a proposterous answer. We're only so used to it that we don't react anymore.DKF: For a long time, Tcl's been defined to use finite field arithmetic with a field size of X bits where X is determined by the size of the C type long (not int; the difference matters on some platforms.) Changing to use arbitrarily long integers has two consequences:
davidw - I wouldn't put 4) in quite those terms, but if everyone has managed to implement something, it's worth a serious, in-depth look, and it's probably worthwhile to write down why it's a bad idea if that is indeed the conclusion reached.DKF: Some comments on the comments.1) Speed. I'd love to see some performance studies on the effect of bignums, speaking as one of the people who has to field complaints when things aren't fast enough. It's too easy to say "I think things will be OK". Let's see the evidence.2) I know those programs are non-portable. But non-portability doesn't stop people from doing it. Especially people with an EE background, and Tcl's (apparently) heavily used in the CAD field where those people are common.3) The speed is needed when working in the 32- or 64-bit range. Slowing down when you go out of that is not such a big problem for me. OTOH, we really don't want to get tangled up in license issues. If we can do this for ourselves in a way that satisfies point 1, this is a non-issue.4) There's "mainstream" and there's mainstream. Who do we compare ourselves with? :^) More seriously, my objections are all really to do with how to implement the scheme in a way that doesn't hurt people who wouldn't take advantage of it. If we can manage that trick, I'd love to see bigints.
Someone anonymous: Okay, correct me if I am wrong!As far as I know Tcl has many Object Oriented extensions. How hard would it be to introduce infinite sized integers as new "Objects", "Classes" or "Types" and leave the literal integers (integers that don't require special initialization) alone to ensure backward compatibility.Lars H: One of the beautiful things about the everything is a string principle is that no "special initialization" for large integers would ever be required. If it has been set as a string, then it has been as properly set as the script level requires. What the above is mainly about is changing the arithmetic operations so that they can do the right thing. There are also some parts about internally caching binary representations of numbers to speed up the processing, but that is just the way things are done. (Tcl internally caches binary representations of all sorts of things: procedure bodies (bytecode), command meanings, subcommand meanings, lists, etc.)There is no need to drag OOP into the picture. Unlike many other languages, Tcl can handle the required amount of polymorphism without resorting to decidedly OO extensions.Someone anonymous continues: Is it that awkward to introduce/use new types in tcl? Is this not the obvious or satisfying solution?Lars H: On the script level, Tcl has no types; all values are strings. On the C library level, there are types, I don't think they're hard to introduce, and the tclBigIntType suggested above is precisely the new type for integers too large to store as tclIntType Tcl_Objs.DKF: Speaking as the only person to have done this recently, fitting a new numeric type to Tcl is a lot of work, most of which is just boring coding.
Someone else anonymous: I think your suggestion to support large integer math sound great - will you be trying to implement one or more alternatives for people to try out and give feedback? Or at least writing a TIP with specific implementation details?Lars H: I'm not enough of a C programmer to implement all of it, but I have coded some bits and pieces. Since there is some interest, I've put what I have on Proper integers implementation.CMCc: you know, there's an earlier libgmp version which is LGPL. It's no longer actively developed, but it'd easily suffice for bignums of some kind. If there's a need, I'll happily redo the bignum package to use it.The reason I want bignums is to do some crypto work.DKF: KBK is working on this using a library called libtommath.
LV See Big Floating-Point Numbers for a Tcl package to provide larger floating point - sort of a parallel situation.Lars H: No, Big Floating-Point Numbers just provides for large exponents, but keeps the mantissa size fixed. That is of no help for handling large integers.
aspenlogic: Just for the sake of a data point I'll offer that I need 64-bit address (unsigned) arithmetic today (for hardware testing using TCL). 128-bit address calculations tomorrow (or the next day!). I was brought here searching for a solution to this problem that didn't require me to write the solution! Hope this data point is useful. Thanks for the discussion.
- Create an extension that defines the tclBigIntType (only the null variety) and some basic operations on it (in particular: convert string to canonical proper integer form). No arithmetic at this stage.
- Start writing separate commands that do arithmetic on ints and bigInts, calling overflow when necessary.
- Write an expr-clone (making use of the existing parser) that does int and bigInt arithmetic.
- Write a prototype program that deal with +, -, *, and / of large numbers. It should support an accelerated version in asm, and a portable C version.
- Add support for large floating point numbers to the previous program.
- Verify that everything works w/ some tests.
- Test the program on another architecture.
- Write extension commands for +, -, *, and / which use the code from our working prototype. At this point we can write our new Tcl_Obj type.
- Integrate the code into expr, and then into the scheme of TclExecuteByteCode for faster operation.
- mpexpr (seems to be the Tcl grand old man in this area)
- bignum
- tclsbignum [2]
- BigFloat for Tcl (MPA is obsolete at this time)
- Arbitrary Precision Math Procedures
- arbint
LV The issue really being addressed here is not proper integers but large integers.Change the above values of a to something like 107 or 1376 and you will see the results that you indicate that you expect.Then, try the same thing in a language like C (or many other scripting languages) and see quite similar results.What is really being asked for is not even 64 bit integers, but infinite sized integers.I'm not saying that asking for that is somehow unworthy - just wanting to be certain that someone reading this understands that over the years, Tcl maintainers have always indicated that Tcl's integers are limited by C's representation of integers by variables of size int.Lars H: The page is mainly aimed at people interested in the internals of Tcl, and its title was chosen with this in mind, to forestall precisely that kind of reply.Proper integers are sometimes very large. They have to be, because for any given integer there must be a successor which is larger. "Integers" that are limited in size will therefore be improper, at least in some respect. Avoiding some of the usual lingo makes the problems easier to spot, because the effect of that lingo is partly to turn bugs into features by documenting them: it doesn't really make anything better, but it does make the whole thing Somebody Else's Problem (cf. SEP Field).A large exposure to C, which is hard to avoid for people interested in the internals of Tcl, does tend to distort one's perceptions on these matters. (Not a sarcasm, but a serious observation.) Every schoolchild knows that if you've got 1500 millions and add another 1000 millions to that, then you've got 2500 millions. Tcl currently asks C straight off however, and C says that the sum of 1500 millions and 1000 millions is -1794967296, which from most points of view is a proposterous answer. We're only so used to it that we don't react anymore.DKF: For a long time, Tcl's been defined to use finite field arithmetic with a field size of X bits where X is determined by the size of the C type long (not int; the difference matters on some platforms.) Changing to use arbitrarily long integers has two consequences:
- Reduced speed for everyone. Not everyone cares about being able to add pairs of numbers of 200 digits (or compute the cube of 2**30+7, to give something that is more representable) and there are lots of people who want more speed. Is the performance cost of detecting the overflow cases worth it? I do not know the answer to that, and it isn't something that is easy to answer.
- An end to finite-field arithmetic. Some people (often hardware engineers IME) write programs that depend on the fact that the field is finite. Nobody's really sure just how much code there is out there that would be semantically affected like this.
davidw - I wouldn't put 4) in quite those terms, but if everyone has managed to implement something, it's worth a serious, in-depth look, and it's probably worthwhile to write down why it's a bad idea if that is indeed the conclusion reached.DKF: Some comments on the comments.1) Speed. I'd love to see some performance studies on the effect of bignums, speaking as one of the people who has to field complaints when things aren't fast enough. It's too easy to say "I think things will be OK". Let's see the evidence.2) I know those programs are non-portable. But non-portability doesn't stop people from doing it. Especially people with an EE background, and Tcl's (apparently) heavily used in the CAD field where those people are common.3) The speed is needed when working in the 32- or 64-bit range. Slowing down when you go out of that is not such a big problem for me. OTOH, we really don't want to get tangled up in license issues. If we can do this for ourselves in a way that satisfies point 1, this is a non-issue.4) There's "mainstream" and there's mainstream. Who do we compare ourselves with? :^) More seriously, my objections are all really to do with how to implement the scheme in a way that doesn't hurt people who wouldn't take advantage of it. If we can manage that trick, I'd love to see bigints.
Someone anonymous: Okay, correct me if I am wrong!As far as I know Tcl has many Object Oriented extensions. How hard would it be to introduce infinite sized integers as new "Objects", "Classes" or "Types" and leave the literal integers (integers that don't require special initialization) alone to ensure backward compatibility.Lars H: One of the beautiful things about the everything is a string principle is that no "special initialization" for large integers would ever be required. If it has been set as a string, then it has been as properly set as the script level requires. What the above is mainly about is changing the arithmetic operations so that they can do the right thing. There are also some parts about internally caching binary representations of numbers to speed up the processing, but that is just the way things are done. (Tcl internally caches binary representations of all sorts of things: procedure bodies (bytecode), command meanings, subcommand meanings, lists, etc.)There is no need to drag OOP into the picture. Unlike many other languages, Tcl can handle the required amount of polymorphism without resorting to decidedly OO extensions.Someone anonymous continues: Is it that awkward to introduce/use new types in tcl? Is this not the obvious or satisfying solution?Lars H: On the script level, Tcl has no types; all values are strings. On the C library level, there are types, I don't think they're hard to introduce, and the tclBigIntType suggested above is precisely the new type for integers too large to store as tclIntType Tcl_Objs.DKF: Speaking as the only person to have done this recently, fitting a new numeric type to Tcl is a lot of work, most of which is just boring coding.
Someone else anonymous: I think your suggestion to support large integer math sound great - will you be trying to implement one or more alternatives for people to try out and give feedback? Or at least writing a TIP with specific implementation details?Lars H: I'm not enough of a C programmer to implement all of it, but I have coded some bits and pieces. Since there is some interest, I've put what I have on Proper integers implementation.CMCc: you know, there's an earlier libgmp version which is LGPL. It's no longer actively developed, but it'd easily suffice for bignums of some kind. If there's a need, I'll happily redo the bignum package to use it.The reason I want bignums is to do some crypto work.DKF: KBK is working on this using a library called libtommath.
LV See Big Floating-Point Numbers for a Tcl package to provide larger floating point - sort of a parallel situation.Lars H: No, Big Floating-Point Numbers just provides for large exponents, but keeps the mantissa size fixed. That is of no help for handling large integers.
aspenlogic: Just for the sake of a data point I'll offer that I need 64-bit address (unsigned) arithmetic today (for hardware testing using TCL). 128-bit address calculations tomorrow (or the next day!). I was brought here searching for a solution to this problem that didn't require me to write the solution! Hope this data point is useful. Thanks for the discussion.