Runtime: Add all remaining IEEE754 float types (binary16, binary128, decimal32, decimal64, decimal128), BigDecimal and BigRational to System.Numerics

Created on 18 Mar 2017  路  17Comments  路  Source: dotnet/runtime

While BigInteger is useful for really big integers, it cannot represent really big decimals.

BigRational would be useful in fractional operations (think of 1/3) while BigDecimal could represent something like the square root of 23E-234.

BigDecimal could be straight forked from the J# library source (which Microsoft should have) and BigRational could also be straight forked from another pile of Microsoft code so it is easy to implement.

I would like to see these two get added :)

Also from @tannergooding:

I think completing out the five basic formats from IEEE spec would be desirable. That is:

  • binary32
  • binary64
  • binary128
  • decimal64
  • decimal128

It also directly defines:

  • binary16

    • Several graphics formats use it today and it is available in some graphics hardware.

    • The x86 F16C instruction set also provides hardware support for converting between single and half-precision.

  • decimal32

It also sets a 'precedent' for a BigRational and BigDecimal format:

This standard defines binary interchange formats of widths 16, 32, 64, and 128 bits, and in general for any multiple of 32 bits of at least 128 bits. Decimal interchange formats are defined for any multiple of 32 bits of at least 32 bits.

api-needs-work area-System.Numerics

Most helpful comment

I think completing out the five basic formats from IEEE spec would be desirable. That is:

  • binary32
  • binary64
  • binary128
  • decimal64
  • decimal128

It also directly defines:

  • binary16

    • Several graphics formats use it today and it is available in some graphics hardware.

    • The x86 F16C instruction set also provides hardware support for converting between single and half-precision.

  • decimal32

It also sets a 'precedent' for a BigRational and BigDecimal format:

This standard defines binary interchange formats of widths 16, 32, 64, and 128 bits, and in general for any multiple of 32 bits of at least 128 bits. Decimal interchange formats are defined for any multiple of 32 bits of at least 32 bits.

All 17 comments

Update: dotnet/runtime#14853 already has BigRational covered, so this issue will focus on BigDecimal.

/cc @dcwuser

What is the point of BigDecimal if you already have BigRational? Is it different performance characteristics?

BigDecimal certainly couldn't represent sqrt(23E-234) accurately, because that is not a rational number.

Yea, different performance characteristics.
BigDecimal: Irrational numbers where double/decimal's accuracy/range is too low
BigRational: Rational numbers
That is a difference to me.

By the way, the corefx team does not seem to like BigRational so they closed the issue. Why not add BigDecimal if BigRational gets denied?

I'd love to see an extended precision floating point type added to .NET, but BigDecimal probably isn't a good candidate.

Decimal vs Binary Floating Point

First off, as the name implies, BigDecimal is a base-10 FP type like decimal. Such types are an order-of-magnitude slower than analogous base-2 FP types, and are pretty much only of interest to people doing financial programming. So unless you're here to tell us that you do financial programming and the ~28 digit accuracy of that type isn't enough for your bank's business logic, it's not a base-10 extended FP type you should be asking for.

Extended Floating Point Types

Turning to base-2 FP types, which are of interest to primarily to mathematicians and scientists who need more than the ~16 digit accuracy of double, there is a hierarchy of these kinds of types and we should decide which one we really want and are willing to put in the effort to develop and maintain:

DoubleDouble: This uses two doubles internally to create a type with the same range as double (10^-324 to 10^308), but about twice the precision (~31 digits). It's the easiest extended FP type to implement. It will also have the best performance, at maybe only ~4 times slower than double. Practically, it's quite useful, but it's a little inelegant; for example, it doesn't loose precision gracefully at the extremes in the same way the official binary FP types do. It might be a little too "seat-of-the-pants" to become an official part of the .NET Framework.

Quad: This is the official IEEE754 128-bit binary FP type, so it's memory footprint is the same as DoubleDouble. It's range is 10^-4965 to 10^4932, and it has ~34 digit accuracy. Eventually, FPUs will have support for this type, so eventually it will be fast. But until then, it needs to be implemented via bit-wise operations in software. This makes it not only more difficult to program than DoubleDouble, but also quite a bit slower, maybe ~8 times slower than double. It's quite official though, and certainly "belongs" in the .NET Framework eventually. If we did it now in software, we could transition seamlessly to hardware implementations when they appear.

The remaining types are even more difficult to program, and are the first ones that might honestly be named BigFloat.

BigFloat (Simple): This is an type that allows you to specify the precision when you create an instance, then have that precision propagated through operations. For example you could specify two 100 digit BigFloats, and when you multiplied them, you would get a 100-digit BigFloat result. One well-known existing library that works like this is MPFR. One question you need to ask when implementing such a system is how much you care about operations with really ridiculous precision (e.g. billion digits of pi computations); if you do care, you need to implement techniques like Strassen's FFT-based multiplication scheme.

BigFloat (With Error Estimate): One problem with the simplest BigFloat (which also exists for float and double) is that the stored precision isn't necessarily the actual precision. If you subtract two 100-digit numbers, you will get a 100 digit result, but only 10 of those might be accurate, due to cancellation. One way to deal with this is to have every BigFloat carry around an estimated error, and have operations return that estimate as well as the result. This is basically what Mathematica does internally. Doing this does add a bit more overhead on top of what the simple BigFloat does, but compared to the difficulty of implementing BigFloat, it's easy, and it adds a great deal of value, so it's probably worth doing.

BigFloat (With Error Bound): The state-of-the-art in extended precision, as represented by the arb library, doesn't just carry along an error _estimate_, it carries along a strict error _bound_. This isn't actually too much more difficult than an error estimate for simple arithmetic and basic math functions, but it's harder for the special functions that arb and Mathematica deal with.

BigInteger Plus Scale Factor

If you have BigInteger, one very simple way to create a BigDecimal or BigFloat is just to store a BigInteger and a scale factor. Reading the docs, I half-suspect this is what Java/J#'s BigDecimal did, and that would explain why I know of no one doing extended-precision numerics who uses that type, because that is a terrible way to do extended precision math. If you multiply two 100-digit numbers that have been represented this way, then you are going to all the trouble of computing the 200-digit integer result, then throwing away half of those digits, because your original numbers only have 100 digits of precision. FP is just a fundamentally different beast than integer math. You'll notice that double is not built on top of int or long, and BigFloat shouldn't be built on top of BigInteger.

Recommendation

If we want an extended precision floating point type in the BCL, we should implement Quad or one of the BigFloat variants described above. This is a huge undertaking, so we would need a couple people to work over an extended period, and their work should probably be offered for a while as a NuGet package before being integrated into the BCL.

Anyone who really needs the old J# BigDecimal type should scrounge up the old J# DLL. Maybe someone could make it easier by putting in a NuGet package. There is presumably a reason that it is one of the few types to ever actually be pulled out of the BCL.

What about this: most people will use Quad for mainstream floating-point, and those ranges outside BigDecimal will have you covered.

I think completing out the five basic formats from IEEE spec would be desirable. That is:

  • binary32
  • binary64
  • binary128
  • decimal64
  • decimal128

It also directly defines:

  • binary16

    • Several graphics formats use it today and it is available in some graphics hardware.

    • The x86 F16C instruction set also provides hardware support for converting between single and half-precision.

  • decimal32

It also sets a 'precedent' for a BigRational and BigDecimal format:

This standard defines binary interchange formats of widths 16, 32, 64, and 128 bits, and in general for any multiple of 32 bits of at least 128 bits. Decimal interchange formats are defined for any multiple of 32 bits of at least 32 bits.

There is Dec64 that could have better performances than Decimal64:
http://dec64.com/
(work on this was started on CoreFxLabs but it seems has stalled).

In general for these types to behave as real numeric values same special treatment of the one done for Decimal should be done:

  1. It should be possible to declare them without the need to parse a string (that is new literals should be defined for these) that is:
    cs quad x = 42.4242424242424242424242424242424242424242424242424242424242424242q
  2. It should be possible to use them as default values

this is important - in particular - for Decimal32, Decimal64 and Decimal128 that will make obsolete the actual .Net Decimal type.

binary32 is float (System.Single), and binary64 is double (System.Double).
All remaining types are not implemented though.

In some way related to the literals we need the possibility to define new ones from code: https://github.com/dotnet/roslyn/issues/263 (so we can add new numeric types easily without the need to do changes to the compiler) and const binary data that will permit easier to do the assignation without the use tricks that parse strings or worst passing "manually" the number components: https://github.com/dotnet/roslyn/issues/16095#issuecomment-269116237

Look here of how @ufcpp thinks this could be used:
https://github.com/dotnet/csharplang/issues/273#issuecomment-286959253

Some discussion over at csharplang for language literals: https://github.com/dotnet/csharplang/issues/1252

https://github.com/dotnet/corefxlab/issues/2635 tracks prototyping the various types defined by the IEEE 754:2008 spec.

Just my 2 cents - I think BigRational (based on BigInteger) should be (re)added to this list. Arbitrary-precision rational numbers would be very useful in many applications, and after much sleuthing around the internet, it seems that a lot of people wind up having to roll their own half baked versions of it because it's not provided as managed code anywhere.

Also, it seems it was scratched from this thread because it was tracked here, but that thread has been closed for a long time, and I want to keep it on your radar.

Here is an implementation that I find quite useful.
https://github.com/tompazourek/Rationals

@erjicles Re-included BigRational :)

I'm going to close this issue as it will be unactionable for the purposes of the API review process.

If someone feels strongly about a particular type, they should feel free to open a new issue following the layout of the "good example" given under step 1 of the review process linked above. Ideally the new issue would provide many of the sections given, but would at a minimum provide the base rationale and proposed API surface sections. You can also see the proposal for Half (binary16) here: https://github.com/dotnet/runtime/issues/936

Was this page helpful?
0 / 5 - 0 ratings