Friday, January 28, 2011

Tetrational-point

As you might have guessed, most of my posts are a synthesis of two or more ideas. This time, they are IEEE-754 floating point formats and composite arithmetic formats. To provide some overview, IEEE-754 has been the official standard for representing numbers in computers for over 25 years, and is widely deployed in practically every computer architecture. Composite arithmetic on the other hand is a new approach which uses a combination of integer, rational, scientific, and tetrational representation in unison to achieve greater accuracy, precision, and dynamic range.

References for composite arithmetic include: "Beyond Floating Point" (by C.W.Clenshaw and F.W.J.Olver) which focuses on extending floating point with tetration alone, "Composite Arithmetic, a Proposal for a New Standard" and "Composite Arithmetic, A Storage Form" (both by W. Neville Holmes) which both focus on combining all four approaches, "Design of a Composite Arithmetic Unit for Rational Numbers" (by Tomasz Pinkiewicz, W. Neville Holmes, and Tariq Jamil) which focuses on the implementation of these in hardware, "Design of a 32-bit Arithmetic Unit based on Composite Arithmetic and its Implementation on a Field Programmable Gate Array." (by Tomasz Hubert Pinkiewicz) which focuses on the same, and "Lecture notes on: Computer Arithmetic: Principles, Architectures, and VLSI Design" (by Reto Zimmermann) which seems to be an overview of the subject targeted at students.

The most recent edition of IEEE-754 introduced a 16-bit floating point format, called 'binary16' for short. Since this is the smallest standard floating point format, we will use it as an example of how IEEE-754 represents numbers. To begin we will see how it represents 1.5:

s exponent significand
bin 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0
hex 3 E 0 0
value = 1.5

and this is how the standard binary16 format represents infinity:

s exponent significand
bin 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
hex 7 C 0 0
value = Infinity

As you can see, binary16 uses a 5-bit exponent and a 10-bit significand. One important class of representations which do not represent numbers is known collectively as Not-A-Number (NaN) representations. These are when the exponent is all 1's, regardless of the sign. Infinity is a particular example of this, since the exponent is all 1's and the significand is all 0's. If the significand is nonzero, then the remaining representations fall into two categories: signaling NaNs (sNaN) and quiet NaNs (qNaN). A signaling NaN is traditionally represented with a 0 in the MSB of the significand, and some other bit nonzero. A quiet NaN is traditionally represented with a 1 in the MSB of the significand, with anything in the other bits. Since sNaNs can cause errors and trap handlers to invoke, it is a bad idea to use them to represent numbers, since many architectures will automatically convert sNaNs to qNaNs. So, we will leave sNaNs alone, and replace qNaNs with a tetrational number representation format.

This tetrational format will use the two MSBs to represent quietness (q) and height (h) respectively. The tetrand will become the exponent at the top of the exponential tower. So a height of 0 will indicate a value of 2^2^2^2^2^(0.tetrand), and a height of 1 will indicate a value of 2^2^2^2^2^2^(0.tetrand). In general, the value associated with the tetrational format is:

(-1)sexp2h+5(0.tetrand) -- HTML
-1 s exp 2 h + 5 0.tetrand -- MathML

We will start at 65536, which is just greater than the largest value represented by binary16 (65504). Extending this to larger formats (like binary32) should probably start here as well, because the next height is too much larger than the largest number represented by binary32. Starting from 65536, we can represent it as follows:

binary16: s exponent significand
tetra16: s exponent q h tetrand
bin 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
hex 7 E 0 0
value ≈ 65536

We get this value because 2^2^2^2^2^(0.0) = 2^2^2^2^1 = 2^2^2^2 = 2^2^4 = 2^16 = 65536. The next greater representation is

binary16: s exponent significand
tetra16: s exponent q h tetrand
bin 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1
hex 7 E 0 1
value ≈ 71036

and perhaps another interesting representation is that of googolplex:

binary16: s exponent significand
tetra16: s exponent q h tetrand
bin 0 1 1 1 1 1 1 1 1 0 1 1 0 0 1 0
hex 7 F B 2
value ≈ googolplex = 10^(10^100)

Note that we only used a single bit for the height of the exponential tower, but we could have used more. I think that 2 bits should be used for height in binary32, and 3 bits should be used for height in binary64 and binary128. All floating point formats would encode the height and tetrand in the extra bits of a qNaN so as to avoid conflicts with hardware arithmetic.

Here is an overview of the extensions to the binary16 format:

#x0000 = +0
#x0001 = 5.960464477539063e-8
... (standard binary16 floating-point) ...
#x7BFF = 65504.
#x7C00 = +Infinity
#x7C01 = +sNaN
... (standard binary16 signaling NaN) ...
#x7DFF = +sNaN
#x7E00 = 65536 = 2^2^2^2^2^(0)
#x7E01 = 71036.
... (new tetrational-point) ...
#x7E?? = 2^2^2^2^2^(0.??_16)
...
#x7EFE = 8.9423389054e+15721
#x7EFF = 6.2622606021e+17594
#x7F00 = 2^65536 = 2^2^2^2^2^2^(0) = 2^2^2^2^2
#x7F01 = 6.8504792165e+21383
... (new tetrational-point) ...
#x7F?? = 2^2^2^2^2^2^(0.??_16)
...
#x7FFE = 10^(2.69191e+15721) which is < 2^(2^65536)
#x7FFF = +qNaN (unique)
#x8000 = -0
#x8001 = -5.960464477539063e-8
... (standard binary16 floating-point) ...
#xFBFF = -65504.
#xFC00 = -Infinity
#xFC01 = -sNaN
... (standard binary16 signaling NaN) ...
#xFDFF = -sNaN
#xFE00 = -65536
#xFE01 = -71036.
... (new tetrational-point) ...
#xFE?? = -2^2^2^2^2^(0.??_16)
...
#xFEFE = -8.9423389054e+15721
#xFEFF = -6.2622606021e+17594
#xFF00 = -2^65536
#xFF01 = -6.8504792165e+21383
... (new tetrational-point) ...
#xFF?? = -2^2^2^2^2^2^(0.??_16)
...
#xFFFE = -10^(2.69191e+15721) which is > -2^(2^65536)
#xFFFF = -qNaN (unique)

In conclusion, we have shown exactly how to increase the dynamic range (quite dramatically) of standard IEEE-754 floating point formats by using a tetrational representation encoded in the unused bits of quiet NaNs. This provides a backwards-compatible hardware-accelerated tetrational floating point format which will encode approximate overflows as precisely as possible. So instead of overflows, you get a number! This makes it possible to be more informative when it comes to mathematical functions such as exp, log, or 1/x. With a bigger dynamic range, many of the outputs of these functions will no longer be indeterminate, but instead, finite.

Monday, January 3, 2011

GoLang Proposals

Go is a very new programming language, barely a year old. Since its initial release, it has become an instant success, almost overnight. Google designed Go primarily for high-level systems programming. Since then, Google has replaced a few of its internal servers with custom Go servers. I believe this success is driven by the simplicity of Go, and its special features which focus on concurrency.

Compared to C, it has many more features so I consider its features a strict superset of C's features. Compared to C++ however, Go is very different, possibly described by a classic Venn-diagram in which Go's features and C++'s features intersect, but both have features the other doesn't. For example, method overloading can be accomplished in both languages, but in different ways.One feature that C++ has enjoyed is that of templates, which do not exist in Go. This requires that algorithms that can work on multiple datatypes be written out each time, which negatively affects readability and maintainability. While adding full-fledged C++ templates to Go is certainly possible, I believe we need to look at other languages for guidance.

Java started out as a simple language. Java's success is due in part to this simplicity, however, language designers at Sun (now Oracle) decided very late in the process (2004, a full 9 years after its introduction in 1995) to add generics to the language. In addition, they chose to use C++ syntax, which has conflicts with the shift operator (>>). In my opinion, avoiding the introduction of generics has divided the community, effected incompatibilities, and complicated the standard library with multiple versions of the same function.

Depending on your definition of generics, they may have different kinds of parameters. The most intellectually challenging are Agda generics, which imply that generics are just functions which return types, and any function may take types as a parameter. C++ templates are similar to this kind of generics, but make a distinction between usual functions and functions returning types. Since this is a minority when it comes to generics implementations, we will not consider this kind of generics in the remainder of this article.

The most prevalent form of generics are found in Java and Haskell, which only allow types as parameters. From this point of view, the only generic type found in Go today is the map type, so it will be the primary example used.


Proposal #1: Add first-class types

This is doomed for failure, because it requires Agda-like (or C++/RTTI) semantics, but it is worth discussing. It is the proposal which requires the least syntactic changes, even if it might be the most intellectually demanding on semantics and runtime. If Go did have first-class types, then we could define the Map type as follows:

func Map(KeyT .(type), T .(type)) .(type) {
type mapT []struct{
hash int
key KeyT
value T
}
return mapT
}

Using this proposal, we can simulate map[KeyT]T with Map(KeyT, T). As you can see, type parameters are indicated with the ".(type)" token as is found in Go type-switch statements. One advantage of this system would be that it could be used to define Array(n, T) and Matrix(m, n, T) which would be impossible to define using more restrictive generics. Other functions that could defined using this proposal are new and make, which take a type as their first parameter. Here is a summary of the related changes to the Go specification:

Result        |= ".(type)"
ParameterDecl |= TypeName ".(type)"
Expression |= TypeName

In addition to these syntactic changes, a few paragraphs of rules and discussion maybe required.


Proposal #2: Add 'generic' (or '_Generic') keyword

This is the crux of this blog article. Since first-class types require that types and objects be intermixed, it requires that every parameter be suffixed with .(type), but with the generic keyword, we can enforce that every parameter be a type parameter (Java/Haskell-style generics). This makes declarations much easier to read, and has provides a much more structured style.

generic Map[KeyT]T []struct{
hash int
key KeyT
value T
}

Using this proposal, we can simulate map[KeyT]T with Map[KeyT]T. As you can see, this is very close to the standard built-in map type, with the obvious case difference. Here is an overview of the associated changes to the Go specification:

TypeLit       |= GenericType
TopLevelDecl |= GenericDecl
GenericType = "generic" GSignature Type
GenericDecl = "generic" identifier GSignature Type
GSignature = GParameters identifier
GParameters = "[" [ GParameterList [ "," ] ] "]"
GParameterList = GParameterDecl { "," GParameterDecl }
GParameterDecl = identifier

Having considered adding generic syntax to LiteralType, I don't think it would work well with literal syntax or semantics. What would such a literal mean? Would it simply be syntactic sugar for the base-type of the generic type with all the free variables filled in? If all that would be gained is syntax sugar, then I don't see the value of adding it to literal syntax.

This generics model also allows more than one parameter inside the brackets, but it also requires at least one parameter after the brackets, which is also very similar to how generic types work in Haskell, since the prevalence of monads has made the last parameter more important than the others. This generics model is also much closer to Go's existing map type, the only problem now is that the first letter is uppercase "M" whereas the built-in map type has a lowercase "m". This leads us to our next section, which discusses another proposal.


Proposal #3: Add 'attrib' (or '_Attrib') keyword

This proposal combines two issues into a single solution. The first issue is nonessential attributes (such as alignment, packing, etc.) for which GCC uses the __attribute__((id)) syntax. The second issue is the public/private convention in Go which may be an issue in the future if it is used to implement existing APIs such as POSIX, which require lowercase external symbols. The solution I propose to both of these issues are a new syntax: _Attrib(id), which allows simple annotation of declarations in such a way as to override normal Go semantics. Consider two such attributes: public and private, which would allow us to define the built-in map type as follows:

generic attrib(public) map[KeyT]T []struct{
hash int
key KeyT
value T
}

Using this proposal, there would be no difference between this defined type and the built-in map type. This would allow very small Go compilers which need only handle arrays and slices, and leave the map type, and possibly other built-in functions such as cap and len to a library implementation.

The syntax was designed to force the attrib specifier immediately before the identifier, to make it clear which identifier it is referring to. The changes to the MethodDecl production are questionable, and require more consideration and discussion. I have also considered moving AttribSpec to before the keywords which would simplify the grammar significantly. This is the summary of all the required extensions to the Go specification.

ConstSpec    |= AttribSpec IdentifierList 
[ [ Type ] "=" ExpressionList ]
TypeSpec |= AttribSpec identifier Type
VarSpec |= AttribSpec IdentifierList
( Type [ "=" ExpressionList ]
| "=" ExpressionList )
MethodDecl |= "func" Receiver AttribSpec
MethodName Signature [ Body ]
FunctionDecl |= "func" AttribSpec identifier
Signature [ Body ]
GenericDecl |= "generic" AttribSpec identifier
GSignature Type
AttribSpec = "attrib" "(" identifier
[ "(" ExpressionList ")" ] ")"

Proposal #4: Add 'pragma' (or '_Pragma') keyword

Another method used for nonessential attributes are top-level pragmas. Using pragmas, you can specify information to be used on a per-file or per-package basis. This could be a useful and simple extension to the language that could bring existing GCC syntax to Go compilers. Here is an example of using pragmas in a Go source file:

pragma("GCC poison strdup")
pragma("DSGO prefix pthread_")

Other possibilities for pragmas are to use Go with OpenMP, even though it might sound funny at first, because most of the features of OpenMP are already in Go! However, we should never underestimate what people will do with compilers, given the chance. Here is a summary of the associated extensions to the Go specification:

TopLevelDecl |= PragmaDecl
PragmaDecl = "pragma" "(" string_lit ")"

Conclusion

We have reviewed four possible extensions to the Go programming language, which may not seem in the spirit of Go right now, but over time may become necessary. If Go is to be used for serious projects, developers may come to expect these features, rather than hope for them. As we have learned from Java, if we wait for too long to introduce new features to Go, it may polarize the community, which would be an undesirable outcome for everyone.