I know decimals in fable aren't really decimals, but this was quite a surprise for me:
let items = [
290.8M
290.8M
337.12M
6.08M
-924.8M
]
if List.sum items = 0M then
// do something if sum is zero
else
// do something else
I was expecting that the sum of the following values to be 0.
The actual value is 1.1368683772161603e-13.
This is quite unexpected for an F# developer where one of the language slogans is robustness.
The result is totally expected for floats, but it is a bug for decimals.
The following solutions would help avoid imprecise decimal values and gain trust in Fable:
We needed decimals to compile the F# compiler with Fable, compiling them as decimals was only a temporary solution but it's been working so we haven't "fixed" it yet. We do have a warning when explicitly converting floats to decimals but we don't for every use of decimals to prevent polluting the logs.
In any case I do agree we should implement decimals the correct way (as we do for int64), I wanted to do it for Fable 2 but other tasks were given higher priority at the end. The main part should be to port this file to F#, any volunteers?

Or maybe using a tool to directly translate the C# code to JS? Or is there already a JS lib which is easy to integrate and has the proper semantics? Pinging @ncave for ideas.
@alfonsogarciacaro A lot of things can work, e.g. Bridge.NET uses decimal.js, or we can use F#'s BigRational, or port from C#, etc.
@alfonsogarciacaro Or, more appropriately, decimal.js-light, which is the smaller version (without the trig. functions), or the even smaller one, big.js.
Hmm, maybe this is something I could contribute to Fable... 🤔
Maybe we could port the implementation from elm: https://github.com/torreyatcitty/the-best-decimal/blob/1.0.0/src/Decimal.elm
This solution is based on BigInt (BigRational).
Is BigInt implemented in Fable? Fable REPL doesn't seem to be aware of big integers?
_Anyways, an implementation of BigInt can be found here: https://github.com/cmditch/elm-bigint/blob/master/src/BigInt.elm_
What do you think, should we port elm's (functional) implementation or the C# (imperative?) code?
Slightly off topic:
There is at the moment an ongoing discussion at hackernews about how to deal with monetary values in JavaScript: https://frontstuff.io/how-to-handle-monetary-values-in-javascript and https://news.ycombinator.com/item?id=18334865
I think with a correct implementation of decimals we can do the right thing out of the box in Fable! 😄
BigInt is implemented, but the literal doesn't seem to work:
bigint.Parse "12345678912345" works and gives 1234567891234512345678912345I gives 1942903641@toburger As mentioned above, there is F#'s BigRational (based on F# BigInt which Fable already has, so nothing to port), but IMO we should be using the best performing solution, since performance is a feature.
@toburger Thanks for the report, I opened an issue #1623 for it.
Thanks for letting me know about BigRational, @ncave! This probably would be the easiest way to go as we already have BigInt (though with some problems in the REPL as @toburger points out) and is in line with our efforts to bring more F# modules to fable-core.
However, you're also right that this can have a performance hit (I guess especially in the REPL). Porting System.Decimal from C# is probably not an option as I just realized it uses unsafe code. So the main alternative would be to use decimal.js-light. I assume we would have to restructure the code as we did for long.js if we want to take advantage of tree shaking and have a better integration in the replacements module. It's a bit of work but I can do it (I'm only concerned about introducing bugs, we would need to port some tests from dotnet).
Fantastic work on the decimal support! Thank you! 👏
I have a little quirk with toLocaleString, which doesn't work with decimals anymore.
let formatAsCurrency (d: decimal) =
d?toLocaleString(locale, createObj [
"style" ==> "currency"
"currency" ==> "EUR"
])
This outputs (now) the plain value 3305.45 for instance...
The workaround is to converts it to a float before calling toLocaleString.
let formatAsCurrency (d: decimal) =
(float d)?toLocaleString(locale, createObj [
"style" ==> "currency"
"currency" ==> "EUR"
])
which outputs the desired value 3.305,45 €.
Wow, didn't know about the toLocaleString API, thanks for pointing it out! 😅 Well, it makes sense it doesn't work with decimals now. I'm reluctant to add support to System.Globalization in Fable because that'll be a lot of work, but it could be nice to add number formatting to Fable.Powerpack with a nice API. Would you like to PR it?
That would be really cool to have!
I've created some helper functions for my app but it would be really nice to have a nice statically typed API for formatting.
There exist four toLocaleString implementations for the following types:
I think it would make sense to implement them for numbers and dates.
Although I have no idea how a nice API could look like. Maybe something like this?
open Fable.PowerPack.Locale
Number.currency Euro // discriminated union?
|> Number.format 20.10 // use browsers locale
Number.percent
|> Number.withMinimumFractionDigits 2
|> Number.formatAs "de-DE" 0.10 // use specific locale
Date.date
|> Date.withMonthAs Long
|> Date.format (System.DateTime.Now)
Looks good! @MangelMaxime added the date formatters to Fable.PowerPack, what do you think?
Most helpful comment
@toburger Thanks for the report, I opened an issue #1623 for it.