gsl-design.texi
来自「用于VC.net的gsl的lib库文件包」· TEXI 代码 · 共 1,338 行 · 第 1/4 页
TEXI
1,338 行
Similarly, if you have a frequently recurring code fragment within a
single file you can define a static or static inline function for it.
This is typesafe and saves writing out everything in full.
@node Comments, Minimal structs, Object-orientation, Design
@section Comments
Follow the GNU Coding Standards. A relevant quote is,
``Please write complete sentences and capitalize the first word. If a
lower-case identifier comes at the beginning of a sentence, don't
capitalize it! Changing the spelling makes it a different identifier.
If you don't like starting a sentence with a lower case letter, write
the sentence differently (e.g., "The identifier lower-case is ...").''
@node Minimal structs, Algorithm decomposition, Comments, Design
@section Minimal structs
We prefer to make structs which are @dfn{minimal}. For example, if a
certain type of problem can be solved by several classes of algorithm
(e.g. with and without derivative information) it is better to make
separate types of struct to handle those cases. i.e. run time type
identification is not desirable.
@node Algorithm decomposition, Memory allocation and ownership, Minimal structs, Design
@section Algorithm decomposition
Iterative algorithms should be decomposed into an INITIALIZE, ITERATE,
TEST form, so that the user can control the progress of the iteration
and print out intermediate results. This is better than using
call-backs or using flags to control whether the function prints out
intermediate results. In fact, call-backs should not be used -- if they
seem necessary then it's a sign that the algorithm should be broken down
further into individual components so that the user has complete control
over them.
For example, when solving a differential equation the user may need to
be able to advance the solution by individual steps, while tracking a
realtime process. This is only possible if the algorithm is broken down
into step-level components. Higher level decompositions would not give
sufficient flexibility.
@node Memory allocation and ownership, Memory layout, Algorithm decomposition, Design
@section Memory allocation and ownership
Functions which allocate memory on the heap should end in _alloc
(e.g. gsl_foo_alloc) and be deallocated by a corresponding _free function
(gsl_foo_free).
Be sure to free any memory allocated by your function if you have to
return an error in a partially initialized object.
Don't allocate memory 'temporarily' inside a function and then free it
before the function returns. This prevents the user from controlling
memory allocation. All memory should be allocated and freed through
separate functions and passed around as a "workspace" argument. This
allows memory allocation to be factored out of tight loops.
@node Memory layout, Linear Algebra Levels, Memory allocation and ownership, Design
@section Memory layout
We use flat blocks of memory to store matrices and vectors, not C-style
pointer-to-pointer arrays. The matrices are stored in row-major order
-- i.e. the column index (second index) moves continuously through memory.
@node Linear Algebra Levels, Exceptions and Error handling, Memory layout, Design
@section Linear Algebra Levels
Functions using linear algebra are divided into two levels:
For purely "1d" functions we use the C-style arguments (double *,
stride, size) so that it is simpler to use the functions in a normal C
program, without needing to invoke all the gsl_vector machinery.
The philosophy here is to minimize the learning curve. If someone only
needs to use one function, like an fft, they can do so without having
to learn about gsl_vector.
This leads to the question of why we don't do the same for matrices.
In that case the argument list gets too long and confusing, with
(size1, size2, tda) for each matrix and potential ambiguities over row
vs column ordering. In this case, it makes sense to use gsl_vector and
gsl_matrix, which take care of this for the user.
So really the library has two levels -- a lower level based on C types
for 1d operations, and a higher level based on gsl_matrix and
gsl_vector for general linear algebra.
Of course, it would be possible to define a vector version of the
lower level functions too. So far we have not done that because it was
not essential -- it could be done but it is easy enough to get by
using the C arguments, by typing v->data, v->stride, v->size instead.
A gsl_vector version of low-level functions would mainly be a
convenience.
Please use BLAS routines internally within the library whenever possible
for efficiency.
@node Exceptions and Error handling, Persistence, Linear Algebra Levels, Design
@section Exceptions and Error handling
The basic error handling procedure is the return code (see gsl_errno.h
for a list of allowed values). Use the GSL_ERROR macro to mark an
error. The current definition of this macro is not ideal but it can be
changed at compile time.
You should always use the GSL_ERROR macro to indicate an error, rather
than just returning an error code. The macro allows the user to trap
errors using the debugger (by setting a breakpoint on the function
gsl_error).
The only circumstances where GSL_ERROR should not be used are where the
return value is "indicative" rather than an error -- for example, the
iterative routines use the return code to indicate the success or
failure of an iteration. By the nature of an iterative algorithm
"failure" (a return code of GSL_CONTINUE) is a normal occurrence and
there is no need to use GSL_ERROR there.
Be sure to free any memory allocated by your function if you return an
error (in particular for errors in partially initialized objects).
@node Persistence, Using Return Values, Exceptions and Error handling, Design
@section Persistence
If you make an object foo which uses blocks of memory (e.g. vector,
matrix, histogram) you can provide functions for reading and writing
those blocks,
@smallexample
int gsl_foo_fread (FILE * stream, gsl_foo * v);
int gsl_foo_fwrite (FILE * stream, const gsl_foo * v);
int gsl_foo_fscanf (FILE * stream, gsl_foo * v);
int gsl_foo_fprintf (FILE * stream, const gsl_foo * v, const char *format);
@end smallexample
@noindent
Only dump out the blocks of memory, not any associated parameters such
as lengths. The idea is for the user to build higher level input/output
facilities using the functions the library provides. The fprintf/fscanf
versions should be portable between architectures, while the binary
versions should be the "raw" version of the data. Use the functions
@smallexample
int gsl_block_fread (FILE * stream, gsl_block * b);
int gsl_block_fwrite (FILE * stream, const gsl_block * b);
int gsl_block_fscanf (FILE * stream, gsl_block * b);
int gsl_block_fprintf (FILE * stream, const gsl_block * b, const char *format);
@end smallexample
@noindent
or
@smallexample
int gsl_block_raw_fread (FILE * stream, double * b, size_t n, size_t stride);
int gsl_block_raw_fwrite (FILE * stream, const double * b, size_t n, size_t stri
de);
int gsl_block_raw_fscanf (FILE * stream, double * b, size_t n, size_t stride);
int gsl_block_raw_fprintf (FILE * stream, const double * b, size_t n, size_t str
ide, const char *format);
@end smallexample
@noindent
to do the actual reading and writing.
@node Using Return Values, Variable Names, Persistence, Design
@section Using Return Values
Always assign a return value to a variable before using it. This allows
easier debugging of the function, and inspection and modification of the
return value. If the variable is only needed temporarily then enclose
it in a suitable scope.
For example, instead of writing,
@example
a = f(g(h(x,y)))
@end example
@noindent
use temporary variables to store the intermediate values,
@example
@{
double u = h(x,y);
double v = g(u);
a = f(v);
@}
@end example
@noindent
These can then be inspected more easily in the debugger, and breakpoints
can be placed more precisely. The compiler will eliminate the temporary
variables automatically when the program is compiled with optimization.
@node Variable Names, Datatype widths, Using Return Values, Design
@section Variable Names
Try to follow existing conventions for variable names,
@table @code
@item dim
number of dimensions
@item w
pointer to workspace
@item state
pointer to state variable (use @code{s} if you need to save characters)
@item result
pointer to result (output variable)
@item abserr
absolute error
@item relerr
relative error
@item epsabs
absolute tolerance
@item epsrel
relative tolerance
@item size
the size of an array or vector e.g. double array[size]
@item stride
the stride of a vector
@item size1
the number of rows in a matrix
@item size2
the number of columns in a matrix
@item n
general integer number, e.g. number of elements of array, in fft, etc
@item r
random number generator (gsl_rng)
@end table
@node Datatype widths, size_t, Variable Names, Design
@section Datatype widths
Be aware that in ANSI C the type @code{int} is only guaranteed to
provide 16-bits. It may provide more, but is not guaranteed to.
Therefore if you require 32 bits you must use @code{long int}, which
will have 32 bits or more. Of course, on many platforms the type
@code{int} does have 32 bits instead of 16 bits but we have to code to
the ANSI standard rather than a specific platform.
@node size_t, Arrays vs Pointers, Datatype widths, Design
@section size_t
All objects (blocks of memory, etc) should be measured in terms of a
@code{size_t} type. Therefore any iterations (e.g. @code{for(i=0; i<N;
i++)}) should also use an index of type @code{size_t}.
Don't mix @code{int} and @code{size_t}. They are @emph{not}
interchangeable.
If you need to write a descending loop you have to be careful because
@code{size_t} is unsigned, so instead of
@example
for (i = N - 1; i >= 0; i--) @{ ... @} /* DOESN'T WORK */
@end example
@noindent
use something like
@example
for (i = N; i > 0 && i--;) @{ ... @}
@end example
@noindent
to avoid problems with wrap-around at @code{i=0}.
If you really want to avoid confusion use a separate variable to invert
the loop order,
@example
for (i = 0; i < N; i++) @{ j = N - i; ... @}
@end example
@node Arrays vs Pointers, Pointers, size_t, Design
@section Arrays vs Pointers
A function can be declared with either pointer arguments or array
arguments. The C standard considers these to be equivalent. However, it
is useful to distinguish between the case of a pointer, representing a
single object which is being modified, and an array which represents a
set of objects with unit stride (that are modified or not depending on
the presence of @code{const}). For vectors, where the stride is not
required to be unity, the pointer form is preferred.
@smallexample
/* real value, set on output */
int foo (double * x);
/* real vector, modified */
int foo (double * x, size_t stride, size_t n);
/* constant real vector */
int foo (const double * x, size_t stride, size_t n);
/* real array, modified */
int bar (double x[], size_t n);
/* real array, not modified */
int baz (const double x[], size_t n);
@end smallexample
@node Pointers, Constness, Arrays vs Pointers, Design
@section Pointers
Avoid dereferencing pointers on the right-hand side of an expression where
possible. It's better to introduce a temporary variable. This is
easier for the compiler to optimise and also more readable since it
avoids confusion between the use of @code{*} for multiplication and
dereferencing.
@example
while (fabs (f) < 0.5)
@{
*e = *e - 1;
f *= 2;
@}
@end example
@noindent
is better written as,
@example
@{
int p = *e;
while (fabs(f) < 0.5)
@{
p--;
f *= 2;
@}
*e = p;
@}
@end example
@node Constness, Pseudo-templates, Pointers, Design
@section Constness
Use @code{const} in function prototypes wherever an object pointed to by
a pointer is constant (obviously). For variables which are meaningfully
constant within a function/scope use @code{const} also. This prevents
you from accidentally modifying a variable which should be constant
(e.g. length of an array, etc). It can also help the compiler do
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?