Herbie platforms are the main way to customize Herbie's behavior. The typical platform just defines the set of representations and operations that available to Herbie when compiling. However, platform files can technically contain arbitrary Racket code, and thus can call other Herbie APIs, including importing external libraries, defining new representations, and so on. This page documents such APIs. Note that a level of comfort with Racket is assumed.
Please note that all of the APIs on this page are considered unstable and may change version to version. If you run into issues, please file a bug.
Representations what Herbie calls different number formats. They play a central role in platforms. Concretely, a representation is a set of Racket values that represent both real numbers and bit patterns.
Specifically, a representation value needs to be convertible to
Racket bigfloat
values (which are basically MPFR floats) and also
to ordinals, meaning integers between
-2w−1 and
2w−1−1 for some bit width w.
Create representations with make-representation
:
(make-representation
#:name name
#:total-bits width
#:bf->repr bf->repr
#:repr->bf repr->bf
#:ordinal->repr ordinal->repr
#:repr->ordinal repr->ordinal
#:special-value? special?)
The #:name
should be either a symbol, or a list
containing symbols and integers. The #:total-bits
value
should be a positive integer. The
#:total-bits
parameter determines the total ordinal range your format takes up,
not just its significand range, so for example for double-precision
floats you need a #:total-bits
of 64.
The #:bf->repr
and #:repr->bf
values
should be that convert between representation values and Racket
bigfloats. The repr->bf
function should use as large a
bigfloat precision as needed to exactly represent the value, while
the bf->repr
function should round as per the current
value of
the bf-rounding-mode
parameter.
All non-NaN bigfloat values should result in non-NaN
representation values. For example, (bf->repr (bf
"1e1000000"))
should yield the largest real value in the
representation. Infinite values, as in (bf->repr
+inf.bf)
, should be interpreted as really large real values,
not as infinite values. For example,
the posit format has an
"infinite" value, but it behaves more like a NaN, so converting
bigfloat infinity to a posit should yield its largest real value
instead.
The #:ordinal->repr
and #:repr->ordinal
functions represent ordinals as Racket integers between
-2width−1 (inclusive) and
2width−1 (exclusive). Ordinals
must be in real-number order; that is, if (repr->bf x)
is less than (repr->bf y)
, then (repr->ordinal
x)
should be less than (repr->ordinal y)
.
The #:special
function should return true for NaN
values (or whatever your representation calls values that don't
represent any real number) and false for all other values. Special
values can be anywhere in the ordinal range, and you can have as
many or as few of them as you want.
make-representation
returns a representation object,
which you can then use
in define-representation
and define-operation
.
Generators are helper functions for generating
implementations in define-operation
. For
example, from-libm
and from-rival
are generators.
To define a generator, use define-generator
:
(define-generator ((from-foo args ...) spec ctx)
body ...)
Here, from-foo
is the name of your
generator, and args are any additional arguments the
generator takes. For example, from-libm
takes one
argument, the symbol name.
Then, inside the body, you can use those arguments as
well as spec
and ctx
, to construct an
actual implementation function.
The specification spec
is a Racket tree made up of
lists and symbols and numbers.
The signature ctx
is "context" object; you can
access its context-repr
to get the operation's output
representation, its context-vars
to access its variable
names (as a list of symbols), and its context-var-reprs
to access its input representations (as a parallel list of
representations). The context-lookup
function can be
used to look up a input argument's representation by name.