[rust-dev] Appeal for CORRECT, capable, future-proof math, pre-1.0

Daniel Micay danielmicay at gmail.com
Fri Jan 10 22:01:29 PST 2014


On Sat, Jan 11, 2014 at 12:15 AM, Lee Braiden <leebraid at gmail.com> wrote:
> This may be go nowhere, especially so late in Rust's development, but I feel
> like this is an important, relatively small change (though a high-profile
> one).  I believe it could have a large, positive impact in terms of
> targeting new developer communities, gaining more libraries and
> applications, giving a better impression of the language, AND on performance
> and futureproofing.
>
> However, a lot of people who are interested in performance will probably
> baulk at this, on first sight.  If you're in that group, let me encourage
> you to keep reading, at least until the points on performance improvements.
> Then baulk, if you like ;)
>
> Also, I said it in the post as well, but it's late here, so apologies for
> any readability / editing issues.  I tried, but sleep beckons ;)
>
>
>
> http://blog.irukado.org/2014/01/an-appeal-for-correct-capable-future-proof-math-in-nascent-programming-languages/
>
>
> --
> Lee

> The wrongness of float

There is no lossless implementation of real numbers on a computer. You
assume arbitrary precision floating point can cover every real number,
but there's no such thing. Arbitrary precision means you can choose
the precision used by the numbers. It's certainly no replacement for
hardware floating point because it's many orders of magnitude slower.
You may not understand IEEE754 floating point arithmetic, but it's
certainly not *wrong*.

> The wrongness of machine ints

Big integers are many orders of magnitude slower than hardware integer
arithmetic. It's not a replacement for it, but rather an alternative
when you need a larger range. You still need to validate inputs
because it's very easy to go out-of-memory, and that's going to bring
down the whole process or even other processes on the system.

> For those looking to reject this idea on the basis of optimisation: consider that abstracting away from 32-bit integers or 64-bit floats, or 256-bit ints, can potentially lead to better optimisation, if the compiler can then figure out that it’s on a 256-bit machine (or indeed, is targeting a 256-bit GPU) and is free to optimise accordingly.

This isn't how hardware works. Smaller floating point numbers are
faster than higher precision, because they're smaller. An array of
32-bit floating point numbers will always be half the size as the same
array of 64-bit floating point numbers. Double the amount of data can
fit into the layers of caches, and you can fit twice as many into SIMD
registers so there's double the amount of theoretical throughput.

> real becomes, not a 32-bit float (as I understand it currently is), but what it sounds like: a real number, covering all numbers in the real number range, using an arbitrary precision type.

Rust doesn't have a `real` type. It has `f32` and `f64`, which are an
explicit opt-in to IEEE754 binary floating point. If you don't want
this, you don't have to use them. Anyway, covering all numbers in the
real number range isn't possible. You can have the user pass the
desired precision in the constructor and then handle two inputs of
varying precision in a sensible way.

> int is not (misleadingly) a machine word, but represents what it sounds like: the entire range of integers, using a bigint type. The Big int type in extra would probably be fine, if it were optimised more. I noticed that there were pull requests for large performance improvements in bigint, but they were rejected for lack of comments. I suspect, if it where given its rightful place as the only fully capable integer type in the libraries, then it would rapidly gain performance through patches, too.

A pointer-size integer type is required. A big integer type cannot be
implemented without library support, and will always be significantly
slower than hardware integer types. A Rust big integer implementation
is not going to be competitive with libraries like `gmp` for at least
a decade. An enormous amount of work is required to implement the
whole range of algorithms with better asymptomatic performance, then
optimize with per-platform assembly and figure out the heuristics for
selecting the algorithms.

Placing a huge performance burden on all Rust code and pushing it far
behind Java in performance is not going to result in a fantasy world
where big integers are the same speed.

> Range checking be implemented in debug and optimisation compiler modes, to support the above, and for the general (ada-like) safety benefits that range-checking of numbers would provide. If it’s REALLY such a compilation-speed turn-off, it could be enabled with specific compiler flags.

Range checking also results in significant performance overhead. Two's
complement arithmetic is also often desired, especially in
cryptography and hashing algorithms. Checked overflow is already
provided in the standard library and alternate types handling overflow
in a different way than two's complement arithmetic could be added.
Pretty much the only useful thing here would be implementing an enum
using a hardware integer for small values, and overflowing to a big
integer.

> Rust compilers can optimise int to i32 etc. _automatically_, iff it knows there are no problems doing so, because of the number ranges involved in calculations.

This isn't going to happen in the real-world. Compilers are not magic,
and can't just go changing the size of a type across the program and
somehow figure out where all of the pointers go.

> Likewise, f64, etc. are considered optimisations of real, and can either be optimised by hand, or — potentially, if the compiler work is done — automatically.

Real number precision is always going to be a compromise. There is no
such thing as perfect precision, and Rust doesn't need to choose a
default. The only time I can imagine you would be able to lower the
precision is if someone casts a 32-bit floating point number to
64-bit, performs an operation and then stores it back as a 32-bit
number. It's not going to scale.

> equality comparisons are accurate for math by default

They are already accurate.

> number range checking would be available, either by default or as an optional compiler feature / optimisation flag

It's already available and you can write an integer type with checked
overflow. The performance is not impressive, and there's nothing Rust
can do about it. It's outputting the checked overflow intrinsics and
you can choose to handle the overflow flag however you wish.

> New x86_64 CPUs will (may already) implement decimal types in hardware — in a way that’s compatible with C++’s new decimal types

AFAIK this isn't implemented in hardware. Anyway, IEEE754 decimal
floats have the same caveats as binary floats except that they won't
have lower precision than literals written as decimals.

> Slower math by default. A compiler flag could override this, or people could manually specify a faster f32/f64/i64 type, for example, if they care. I do not think this is a significant problem.

This isn't a significant problem? Rust is a systems language, and as a
systems language it is going to expose the fast low-level primitive
types. If it doesn't offer C level performance, then it's not a very
useful language. Brushing aside performance issues by pretending
optimizing compilers are omnipresent, magical tools does not work. The
language you're looking for isn't Rust, because you don't care at all
about performance.

# What can actually be done?

I have already opened an issue about removing the fallback of integer
literals to `int` when another type can be inferred. If there's no
fallback, then it's only a matter of adding support for generic
literals like Haskell to have all integer types as first-class
citizens. Rust doesn't need a default integer type.

It doesn't need to make any choices about this at all. It can offer
32-bit, 64-bit and eventually 128-bit IEEE754 floating point numbers.
It can offer 8-bit, 16-bit, 32-bit and 64-bit variants of two's
complement arithmetic integers. It can offer big integers, rational
numbers, arbitrary precision binary floats and arbitrary precision
decimal floats. You can use the suitable tool for the job without
burdening everyone with significant performance overhead and turning
Rust into something that's not a systems language.


More information about the Rust-dev mailing list