Description
Background and motivation
(originally posted here dotnet/fsharp#14687)
Describe the solution you'd like
F# is marketed as a language good for Data Science and ML. Examples:
"F# for Data Science and ML F# is used in a variety of ways ... data science, and machine learning. ... are a great fit for data science and machine learning"
https://dotnet.microsoft.com/en-us/languages/fsharp
"F# is an excellent solution for programmatic data science"
https://fsharp.org/guides/data-science/
"Data analytics machine learning in F#"
https://learn.microsoft.com/en-us/events/dotnetconf-focus-on-fsharp/getting-started-with-data-analytics-machine-learning-in-fsharp
I would argue that F# or rather the dotnet platform is missing big feature to support this, a "BigDecimal" with arbitrarily many digits for mathematical operations. For example I stumpled upon finding decimal-repeats in the reciprocal of primes, some of which happen after many thousands of decimals. Opening up my favorite language F# to get started, I was a bit disspointed. There's no easy way to do this on the dotnet platform that I know of, other than calling other libraries such as GMP or the like.
Here's some code examples in Kotlin/JVM and Python how easy it is to work with, and then my attempt to workaround using System.Numerics.BigInteger in the end.
import java.math.BigDecimal
import java.math.RoundingMode
import kotlin.system.measureTimeMillis
fun main() {
var result: BigDecimal?
val executionTime = measureTimeMillis {
val prime = BigDecimal(60013)
result = BigDecimal.ONE.divide(prime, 1000000, RoundingMode.DOWN)
}
println(result)
println("Execution time: ${executionTime}ms")
}
Takes about 200 ms on my PC. Python seems almost instant, I'm guessing it calls GMP:
from decimal import *
getcontext().prec = 1000000
one = Decimal(1)
prime = Decimal(60013)
print(one / prime)
Doing it with bigint and multiply first works, takes around 1.5 seconds on my PC, but it's not very flexible to work with, if you really want a decimal number.
open System.Diagnostics
let prime = bigint 60013
let sw = Stopwatch.StartNew()
let zeros length = System.String('0', length)
let multiplier = bigint.Parse("1" + (zeros 1_000_000))
let ans = multiplier / prime
sw.Stop()
printfn "Execution time: %ims" sw.ElapsedMilliseconds
In conclusion.
Shouldn't F#/dotnet have a BigDecimal type?
Especially in the light of the data-science marketing.
API Proposal
I don't have a specific proposal, but the design could mimic bigint / System.Numerics.BigInteger: https://learn.microsoft.com/en-us/dotnet/api/system.numerics.biginteger?view=net-7.0
API Usage
Maybe something like this:
let prime = bigdecimal 60013
let ans = bigdecimal.Divide(bigdecimal.One, prime, decimals=1000000)
And printing a string of the output would be the 1 million decimals
0.0000166630563377934780797493876326795860896805692100044990252112042390815323346608234882442137536867012147368070251445520137303...
There's some questions about the API. How to do it? Like Python example setting a context? Or like JVM/example? Maybe a mix?
Alternative Designs
No response
Risks
No response