Skip to content

Commit

Permalink
Updates fixed-point content of SG14 to reflect changes in fixed-point…
Browse files Browse the repository at this point in the history
… repo

- makes changes to propose, now at revision 1:
  - changed default template parameter value
  - changed mixed-type operator promotion rules
  - other minor adjustments to API and notes WRT N3352
- updates Docs/fixed_point.md to point to new repo:
  https://github.com/johnmcfarlane/fixed_point
- removes fixed-point code from SG14 repo to avoid confusion
  • Loading branch information
john committed Sep 20, 2015
1 parent 5c9f432 commit beaf800
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 1,856 deletions.
78 changes: 50 additions & 28 deletions Docs/Proposals/Fixed_Point_Library_Proposal.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
**Document number**: LEWG, EWG, SG14, SG6: D0037R0
**Date**: 2015-09-12
**Document number**: LEWG, EWG, SG14, SG6: D0037R1
**Date**: 2015-09-19
**Project**: Programming Language C++, Library Evolution WG, Evolution WG, SG14
**Reply-to**: John McFarlane, [fixed-point@john.mcfarlane.name](mailto:fixed-point@john.mcfarlane.name)

Expand Down Expand Up @@ -82,12 +82,9 @@ multiplication.

The exponent of a fixed-point type is the equivalent of the exponent
field in a floating-point type and shifts the stored value by the
requisite number of bits necessary to produce the desired range.

The default value of `Exponent` is dependent on `ReprType` and ensures
that half of the bits of the type are allocated to fractional digits.
The other half go to integer digits and (if `ReprType` is signed) the
sign bit.
requisite number of bits necessary to produce the desired range. The
default value of `Exponent` is zero, giving `fixed_point<T>` the same
range as `T`.

The resolution of a specialization of `fixed_point` is

Expand Down Expand Up @@ -152,28 +149,53 @@ largely applies to `fixed_point` objects also. For example:

...equates to `true` and is considered a acceptable rounding error.

### Arithmetic Operators
### Operator Overloads

Any operators that might be applied to integer types can also be
applied to `fixed_point` specializations. A guiding principle of
operator overloads is that they perform as little run-time computation
as is practically possible.

With the exception of shift operators, binary operators can take any
combination of:

* one or two arguments of a single specialization of `fixed_point` and
* zero or one arguments of any arithmetic type.

The return value is the same `fixed_point` type as the input(s) in all
cases. The reason that heterogeneous specializations are not accepted
is that the type of the result would be problematic to determine and
possibly require shift operations to produce.
applied to fixed-point types. A guiding principle of operator
overloads is that they perform as little run-time computation as is
practically possible.

With the exception of shift and comparison operators, binary operators
can take any combination of:

* one or two fixed-point arguments and
* zero or one arguments of any arithmetic type, i.e. a type for which
is_arithmetic is true.

Where the inputs are not identical fixed-point types, a simple set of
promotion-like rules are applied to determine the return type:

1. If both arguments are fixed-point, a type is chosen which is the
size of the larger type, is signed if either input is signed and
has the maximum integer bits of the two inputs, i.e. cannot loose
high-significance bits through conversion alone.
2. If one of the arguments is a floating-point type, then the type of
the result is the smallest floating-point type of equal or greater
size than the inputs otherwise;
3. If one of the arguments is an integral type, the type of the result
is the input fixed-point.

The reasoning behind this choice is a combination of predictability
and performance. It is expained for each rule as follows:

1. ensures that the least computation is performed where fixed-point
types are used exclusively. Aside from multiplication and division
requiring shift operations, should require similar computational
costs to equivalent integer operations;
2. loosely follows the promotion rules for mixed-mode arithmetic,
ensures values with exponents far beyond the range of the
fixed-point type are catered for and avoids costly conversion from
floating-point to integer.

Shift operator overloads require an integer type as the right-hand
parameter and return a type which is adjusted to accommodate the new
value without risk of overflow or underflow.

Comparison operators convert the inputs to a common result type
following the rules above before performing a comparison and returning
`true` or `false`.

#### Overflow

Because arithmetic operators return a result of equal capacity to
Expand Down Expand Up @@ -257,14 +279,14 @@ to avoid overflow:
trunc_subtract(FixedPoint1, FixedPoint2)
trunc_multiply(FixedPoint1, FixedPoint2)
trunc_divide(FixedPoint1, FixedPoint2)
trunc_invert(FixedPoint)
trunc_reciprocal(FixedPoint)
trunc_square(FixedPoint)
trunc_sqrt(FixedPoint)
trunc_shift_left(FixedPoint, Integer)
trunc_shift_right(FixedPoint, Integer)
promote_multiply(FixedPoint1, FixedPoint2)
promote_divide(FixedPoint1, FixedPoint2)
promote_invert(FixedPoint)
promote_reciprocal(FixedPoint)
promote_square(FixedPoint)

Some notes:
Expand All @@ -280,7 +302,7 @@ Some notes:
5. the `_square` functions return an unsigned type;
6. the `_add`, `_subtract`, `_multiply` and `_divide` functions take
heterogeneous `fixed_point` specializations;
7. the `_divide` and `_invert` functions in no way guard against
7. the `_divide` and `_reciprocal` functions in no way guard against
divide-by-zero errors and
8. the `trunc_shift_` functions return results of the same type as
their first input parameter.
Expand Down Expand Up @@ -564,8 +586,8 @@ resolution.
The `fixed_point` class template could probably - with a few caveats -
be generated using the two fractional types, `nonnegative` and
`negatable`, replacing the `ReprType` parameter with the integer bit
count of `ReprType`, specifying either `fastest` or `truncated` for
the rounding mode and specifying `undefined` as the overflow mode.
count of `ReprType`, specifying `fastest` for the rounding mode and
specifying `undefined` as the overflow mode.

However, fixed_point more closely and concisely caters to the needs of
users who already use integer types and simply desire a more concise,
Expand Down
194 changes: 7 additions & 187 deletions Docs/fixed_point.md
Original file line number Diff line number Diff line change
@@ -1,190 +1,10 @@
# The `fixed_point` Class Template
# Fixed-Point Real Numbers

[John McFarlane](https://groups.google.com/a/isocpp.org/forum/#!profile/sg14/APn2wQdoie4ys78eOuHNF35KKYtO4LHy0d1z8jGXJ0cr5tzmqGbDbD7BFUzrqWrU7tJiBi_UnRqo)

## 1. Introduction

This document introduces one approach to representing fixed-point real numbers in C++11 using literal class template, `fixed_point`.

### 1.1. Background

It is developed as part of [SG14](https://groups.google.com/a/isocpp.org/forum/#!forum/sg14) which explores the game development and low-latency needs of C++ developers.

### 1.2. Resources

SG14 has an [unofficial](https://groups.google.com/forum/#!forum/unofficial-real-time-cxx) and an [official](https://groups.google.com/a/isocpp.org/forum/#!forum/sg14) discussion group which contains [a thread](https://groups.google.com/a/isocpp.org/forum/#!topic/sg14/1w5eiJsyT2Q) circulating the original fixed_point idea. A formal [proposal](Proposals/Fixed_Point_Library_Proposal.md) is also in the works.

There is an [SG14 source code repository](https://github.com/WG21-SG14/SG14) including a header file, [fixed_point.h](https://github.com/WG21-SG14/SG14/blob/master/SG14/fixed_point.h), in which a proof of concept, `sg14::fixed_point`, is actively developed. Accompanying tests can be found in [fixed_point_test.cpp](https://github.com/WG21-SG14/SG14/blob/master/SG14_test/fixed_point_test.cpp).

Anyone with an interest in game and low-latency development in C++ is welcome to contribute to the discussion and the project.

## 2 Overview

The proof of concept of fixed_point is located in [fixed_point.h](https://github.com/WG21-SG14/SG14/blob/master/SG14/fixed_point.h). For brevity, the project namespace, `sg14` - as well as standard namespace, `std` - is assumed from here on:
```
using namespace sg14;
using namespace std;
```

The header is intended to be self-contained and should only require a reasonably compliant C++11 compiler. The code is generally tested against Clang 3.5, GCC 4.9 and VC2015.

### 2.1 Basic Usage

Fixed-point real numbers are represented using the following class template:
```
fixed_point<typename REPR_TYPE, int EXPONENT>
```
where:
* `REPR_TYPE` is a built-in integral type used to store the value and
* `EXPONENT` is an integer value governing the number of bits by which the internal value must be shifted in order to represent the real number.

Note that if `REPR_TYPE` is signed, the `fixed_point` type is also signed.

#### 2.1.1 Converting to `fixed_point` Type

To declare a variable, `pi`, containing a signed real value made up of 3 integer bits and 12 fractional bits:
```
auto pi = fixed_point<int16_t, -12>(3.141592654);
```

You can construct an object from any integral, floating-point or other fixed-point type. Rounding is the same as for float-to-integer conversion. (That is to say, it has not been addressed at all!)

#### 2.1.2 Converting from `fixed_point` to Built-in Types

Object can be explicitly cast back to built-in types:
```
cout << static_cast<float>(pi); // output: "3.14163"
```

#### 2.1.3 Made-To-Measure Types

Most likely, you care how many integral bits are stored. There are two ways of creating a fixed_point type from this value:

You can simply specify the amounts of everything - including sign bit:
```
auto opacity = make_fixed<8, 8, false>(255.999); // 8:8 unsigned value
```

Alternatively you can specify the underlying type and the desired number of integer bits:
```
auto degrees = make_fixed_from_repr<int32_t, 9>(360); // 9:22 signed value
```

#### 2.1.4 Arithmetic Operations

The intent is for `fixed_point` to behave like a built-in integral or floating-point type. To this end, some basic arithmetic operations have been implemented with more to follow.

Done so far:
* unary `-`
* binary `==`, `!=`, `<`, `>`, `<=`, `>=`, `+`, `-`, `*`, `/`, `+=`, `-=`, `*=`, `/=`

#### 2.1.5 Math Functions

So far, `sqrt` and `abs` are available:
```
cout << sqrt(abs(fixed3_4_t(-5))); // output: "2.1875"
```

### 2.2 Advanced Topics

#### 2.2.1 Literal Types and `constexpr`

A quick look at [fixed_point_test.cpp](https://github.com/WG21-SG14/SG14/blob/master/SG14_test/fixed_point_test.cpp) will make it apparent that `fixed_point<>` is a literal type; anything one can do with it at run-time can also be done at compile time.

Some advantages to this are:

1. Rudimentary automated testing without the need for a framework - simply compile the test file as part of the project. In fact, the limited expressiveness of C++11 constexpr statements ensures that all tests are minimal in scope, in keeping with unit testing practice.
2. Initialization of instances using literals of built-in type is likely to mean that the object is constructed with virtually no overhead.
3. Further to this, relatively complex calculations can be performed at compile-time, e.g.:
```
enum { TWICE_THE_SQUARE_ROOT_OF_FIFTY = ufixed16_16_t(2) * sqrt(ufixed16_16_t(50)) };
cout << TWICE_THE_SQUARE_ROOT_OF_FIFTY; // output: 14
```

Disadvantages:

1. Especially when limited to C++11, `constexpr` functions are taxing to design as no loops or variables are allowed.
2. No run-time checks such as `assert` are possible and exceptions are costly, making run-time diagnostics difficult. It may turn out that contracts are a satisfactory fix to this situation. (TBD)

#### 2.2.2 Promotion

In the context of `fixed_point<>`, promotion refers to explicit conversion of one type to a compatible type of higher capacity. For example:
```
auto a = ufixed8_8_t(5.5);
auto b = promote(a); // type of b is ufixed16_16_t
```
This is akin to casting from `float` to `double`. One can perform precision-critical calculations using the promoted type. Finally, the `demote` function template can be used to convert a value back to the original type.

#### 2.2.3 'Shift' Conversion

Usually, it isn't worth the extra complication and performance loss associated with converting to a larger type. In the floating-point case, this is handled automatically by the type's variable exponent. Not so for `fixed_point` types!

Consider this operation:
```
cout << ufixed4_4_t(10) * ufixed4_4_t(2); // output: 4
```
Because `ufixed4_4_t` cannot store value 20, there is an overflow. The solution is to use `shift_multiply`:
```
cout << shift_multiply(ufixed4_4_t(10), ufixed4_4_t(2)); // output: 20
```
This function, and its partners:

* `shift_add`
* `shift_subtract`
* `shift_multiply`
* `shift_square`
* `shift_sqrt`

ensure that the result is returned in a type of the same size which is suitable for holding the result in the vast majority of cases. In the above case, `shift_multiply` returns a `ufixed8_0_t`.

With liberal use of type deduction, much work can be performed this way without the worry of losing significant digits:
```
template <typename REPR_TYPE, int EXPONENT>
auto constexpr dot_product(
fixed_point<REPR_TYPE, EXPONENT> x1, fixed_point<REPR_TYPE, EXPONENT> y1,
fixed_point<REPR_TYPE, EXPONENT> x2, fixed_point<REPR_TYPE, EXPONENT> y2)
{
return shift_add(shift_multiply(x1, x2), shift_multiply(y1, y2));
}
cout << dot_product(ufixed4_4_t(10), ufixed4_4_t(0), ufixed4_4_t(5), ufixed4_4_t(5)); // output: 50
```

#### 2.2.4 Extreme Values of Exponent

In the previous example, the function template specialisation of `dot_product` returns a value of type, `fixed_point<uint8_t, 1>`. In other words, it can only express even numbers. It may seem unusual to have a fixed-point type with a negative number of fractional bits, but it is perfectly normal for floating-point types to represent very large numbers this way.

Equally, `fixed_point<uint8_t, -9>` can only represent values in the range [0, 0.5). Again, if that is what is called for, there's no reason not to allow this. Obviously, it makes conversion between different types a little more complicated but otherwise, is simpler that not allowing it.

## 3 Future API Work

Some rough notes on where to go next with the design of the API:

### 3.1 To Discuss

* better name for `shift_` functions
* 'number of integer digits' might make a better 2nd parameter to `fixed_point<>`.
* should the first template parameter default to int?
* fixed_point::data is named after the std::vector member function but is this the best choice here?
* Too many disparate ways to declare specializations of fixed_point? If so, what is a good subset?

### 3.2 To Do

* Operators
* unary: `!`, `~`
* binary: `%`, `<<`, `>>`, `<<=`, `>>=`, `&`, `|`, `^`, `&&`, `||`
* pre and post: `++`, `--`
* many more overloads of cmath functions
* shift_divide? inverse_t?
* standard traits and `std::numeric_limits`
* either remove `open_unit` and `closed_unit` or replace with `open_interval` and `closed_interval`
* consider removing `lerp` as it can probably be done as well using arithmetic operations
* streaming operators are placeholder and fall back on `long double` conversion.
* better name for `fixed_point_by_integer_digits_t`

### Possible Additions

* an alias that creates a fixed_point specialization from a maximum, e.g.:
* auto n = fixed_point_can_hold_t<int16_t, 4000>(); // type of n is fixed_point<uint16_t, -3>
* a routine for generating text representations in arbitrary bases.
* consider complimenting them with types that can store [-1,1] for use with trig functions
Support for fixed-point arithmetic in the standard library is
proposed. It is described in paper,
[LEWG, EWG, SG14, SG6: D0037](Proposals/Fixed_Point_Library_Proposal.md).
An [experimental implementation](https://github.com/johnmcfarlane/fixed_point)
is in development. Potential users are invited to provide feedback on
the [SG14 forum](https://groups.google.com/a/isocpp.org/forum/#!forum/sg14).

0 comments on commit beaf800

Please sign in to comment.