[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Floating-point formats and standards



Brief summary: More praise, a few minor suggestions, and one major issue.

Bradd wrote:
>> Would you mind terribly making the A optional in the second syntax?
>> 
>>     array-prefix :: rank  `A'  [ dimensions ] [ `:' type-specifier ]
>>                   |      [`A']   dimensions   [ `:' type-specifier ]
>> 
>> I realize that it complicates parsing a bit ....

Aubrey Jaffer wrote:
> It took several hours to code, but it seems to work.

Great!

I thought of two more items for the syntax examples.

1. They should include examples of how to write arrays with empty
   dimensions. (I hope I got all these right.)
   
   #A0*2()
   #A2*0(() ()).
   #A2*0*3(() ()).
   #A2*3*0((() () ()) (() () ())).

2. It should be an error to write any array with inconsistent or
   ambiguous rank or dimension specifiers.

   #3A()           array shape is ambiguous
   #2A((1 2) (1))  column widths are inconsistent
   #3A1*1((1))     rank is inconsistent with dimension specifier

>> ... but it also unifies the dimension syntax with the #n(...) vector
>> syntax of Common Lisp and PLT Scheme.

> Can you describe the PLT compatibility?

PLT Scheme implements the Common Lisp #n(...) syntax with two extensions.

In Common Lisp, #n(v.1 v.2 ... v.k) creates a vector of size N, with the
final value, v.k, repeated (N - K) times. It's an error if (K > N) or if
(K = 0 and N > 0). Examples:

    #5(1 2) ==> #(1 2 2 2 2)
    #2(1 2) ==> #(1 2)
    #0()    ==> #()
    #1(1 2) ==> error: too many values
    #5()    ==> error: no values for non-empty array

PLT Scheme adds two extensions: If (K = 0), the vector is filled with 0,
and if (K > N) the reader raises a specific exception. If "it is an
error" means the same thing in Common Lisp as it does in Scheme, these
are pure extensions of the CL feature.

With the "A" optional, the array literal syntax is consistent with
CL/PLT: #3(1 2 3) == #A3(1 2 3) == #1A3(1 2 3) == #1A(1 2 3).

>> Don't forget to give semantics and examples for the shaped-array
>> syntax.  I don't particularly care whether you define the repeat-fill
>> rule; feel free to leave it for a later SRFI if you don't want to
>> write it up.

> Do whole rows, planes, etc also repeat?

I would expect it.

First, repeat the last object in each column as many times as necessary
to fill it. Then repeat the last column in each row, the last row in
each super-row, and so on, until the array is complete.

For example, consider this 4*4 matrix:

    | 1 2 3 3 |
    | 2 2 3 3 |
    | 3 3 3 3 |
    | 3 3 3 3 |

You could write this as:

    #4*4((1 2 3) (2 2 3) (3))

The reader would first complete each column:

    #4*4((1 2 3 3) (2 2 3 3) (3 3 3 3))

Then each row:

    #4*4((1 2 3 3) (2 2 3 3) (3 3 3 3) (3 3 3 3))

Obviously, this works best (typing-wise) for arrays that only vary near
the origin. While it would be nice to have a more general shorthand --
especially something that could handle identity matrices -- that's a
better subject for a later SRFI.

(Here's an idea in case somebody does want to implement an extension
like that. In the list decomposition, an exact integer M indicates that
the following row should be repeated M times, and an ellipsis indicates
that the preceding row or column should be repeated as many times as
necessary to complete the dimension. For example,

    #100x100(2(1 ...) (1 1 0 ... 1 1) ... 2(1 ...))

describes a 100x100 matrix with a border of two 1s surround a zero
center. I don't recommend it for this SRFI; it's too big of an
enhancement. I don't even mind if you also punt on the basic CL/PLT
tail-filling feature. Only do this if you're sure it's a good idea and
you really want to implement it.)

>> Anybody feel strongly about "*" vs "x" for the bound separator?

> I don't like x because it looks like mistyped hex numbers (#x33)

OK, that's good enough for me.

>> That covers all of the basic types.  Only binaryx [x86] is missing ....

> I think you will find that vectorized instructions don't pack
> non-power-of-2 bit widths.  Breaking operands over cache-line
> boundaries is a huge hassle for hardware.
> 
> So the 80-bit and 96-bit are likely stored into 128 bits.  We might as
> well call them 128b.

Ah, OK. That's good enough for now, since I know of no implementations
with both x86 and quad flonums. I suppose it could happen in the future,
if Intel keeps binaryx and adds binary128, but that's probably 5 to 10
years off, if it happens at all. If it ever becomes a problem, it
wouldn't be hard to add flox96b specifiers.

> This text I added describes how sizes are chosen:
>
>   Implementations are required to accept all of the type denotations.
>   Uniform types of matching sizes which the platform supports will be
>   used; the others will be represented as the next larger format of
>   the same type implemented.  If there is no larger format of the same
>   type and there is a bignum format for that element type, then the
>   array format defaults to vector; otherwise the largest uniform
>   format of that type is used.

I'll try an example to make sure I understand the intent correctly.
Suppose that I'm targeting all the machines in an HP data center
(a mix of x86, Itanium, and PA-RISC).

    flor128b => x86 extended (binaryx) on x86 and Itanium
    flor16b  => IEEE single (binary32) on all three systems
    fixz64b  => bignum on x86, or signed32 if there are no bignums

I have one major issues with the rule as written above: It's not clear
what to do if the system lacks a flonum format altogehter. For example,
if you have no decimal flonums at all, you should probably use rational
bignums instead (because binary flonums are unacceptable for the major
decnum applications). However, if rational bignums are allowed as a
"flonum" format, then the rule above would prefer them over binaryx.

The essential problem here is that flonum, decnum, and fixnum typically
have different requirements. In general, precision and range are non-
negotiable for fixnums and decnums: Users only specify the large types
when the data requires it. However, speed is usually more important than
precision for binary flonums.

It might be best to specify each major element type separately. Here are
my recommendations for defaults when there's format large enough. Each
offers a list of acceptable possibilities, roughly in order from most to
least desirable. An implementation should document its behavior (and
should allow users to reconfigure that behavior).

Binary flonum (floTWb)
a. largest available binary flonum
b. arbitrary-precision floating-point number (a "floating-point bignum")
c. rational bignum
d. none (signal an error)

Decimal flonum (floTWd)
a. arbitrary-precision floating-point number
b. rational bignum
c. binary flonum with at least 2W precision
d. none (signal an error)
e. largest available decimal flonum

Fixnum (fixTWb)
a. flonum with at least W bits precision in the significand
b. bignum
c. none (signal an error)

>> No fixq arrays for rational numbers?  C'mon, you know you want to!

> Although the syntax could easily specify arbitrary precisions, the
> prototype functions would need to take extra arguments.  Should the
> precision be specified as total bits and fractional bits; or integral
> bits and fractional bits?

Oh, I figured they'd work like complex numbers, e.g., a fixq32b would
have a 32-bit numerator and a 32-bit denominator. (That is how complex
numbers work, right?)

> It is the Swiss-army-knife of array syntax.  I hope the R6RS editors
> are okay with that.

Well, I like it. There are a couple issues to work out, but I'm very
happy with the spec overall.
-- 
Bradd W. Szonye
http://www.szonye.com/bradd