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 :)
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.
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.
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.
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.
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.
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:
It also directly defines:
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:
cs
quad x = 42.4242424242424242424242424242424242424242424242424242424242424242q
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
Easier to implement with https://github.com/dotnet/designs/issues/13
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
Most helpful comment
I think completing out the five basic formats from IEEE spec would be desirable. That is:
It also directly defines:
It also sets a 'precedent' for a BigRational and BigDecimal format: