occz
Cool stuff!

The use of infix functions reads a bit weird to me.

If I were to design an API like this in Kotlin, I think I would have gone for regular extensions for many cases and perhaps extension properties, think as such:

    val fiveBucks = 5.usd
    val fiveBucks = 5.money("USD")
    val tenPercent = 10.percent
How come you went for "increaseBy" and "decreaseBy" instead of overloading `plus` and `minus`? Just curious, preference is a valid answer.
proper_elb
First, congrats on the library and thank you for sharing it!

1) A hint about potential prior art: F# (and/or C#?) have a first-class unit system (physical units I think), which sounds a bit similar to monetary calculations. However, physical unit are easier to model than monetary unit I think.

2) Currently, I am building something tangentially related in Rust: A backtester (test trading strategies on historical and/or simulated data) with focus on accuracy. So one thing I included already is that Assets (like etfs) are valued in a currency.

If I may be so bold: How would you approach these questions / where could I read more about that? 1) When simulating, can I always assume that the exchange will work? (For assets, that is not always the case, sometimes one has to wait a bit until there is a buyer, or there might not be a buyer at all) 2) Is there public domain data in exchange rates? 3) If I have to choose, which exchange rate should I pick? The lower one, higher? What would make the most sense in the context of trading etfs, stocks, options, crypto etc.? 4) How to approach rounding, is there a best practise? 5) I assume it is best to immediately substract taxes in every transaction, even if they are formally defined annually, right? 6) Would you model inflation? I currently plan to ignore it and present it at the very end: "Your portfolio has a final value of X.X ¥. Adjusted for inflation, that would be Y.Y ¥ today (2024-10-08)."

wiremine
Nice! Reminds me of the ergonomics of Rebol's money type:

https://www.rebol.com/docs/core23/rebolcore-16.html#section-...

> $100 + 11 $111.00

> $10 / .50 $20.00

In general, Rebol's type system was very expressive. Would love to see more libraries like this provide that type of experience.

hathawsh
I have questions about some edge cases I've encountered in dealing with money.

- Let's say I load two money values from a database. Value A is 1 USD and value B is 1 BTC. If I try to add them together, what happens? (I would expect a runtime exception. In fact, I would hope for a runtime exception, because the operation usually indicates a programming error.)

- If I divide $2.00 by 3, do I get $0.66 or $0.67? If I want to specify the rounding rule, is there a simple way to do it? I see there's support for allocation by percentage, but sometimes the allocation rules are more complex and I need to be able to specify the rounding rule.

- Does this library help parse user input? When parsing user input, what happens to extra digits? Does "0.015" get interpreted as $0.01 or $0.02?

Edit: one more question. Sometimes people bend the rules on the number of digits; for example, gas stations may charge $3.599 per gallon. Can the library deal with that or would I have to convert to decimal, multiply, and then convert back to money?

bojanz
I like the support for custom currencies, as that is an edge case that often pops up.

On the other hand, be careful about tying the symbol to the currency, as symbols are locale specific. For example, the symbol for USD is $ in eu-US but US$ in en-CA and en-AU (Canada and Australia), and then $US in French locales.

https://cldr.unicode.org/ is the magical dataset behind most good implementations that deal with currency display. Updated twice a year, available in JSON, providing currency symbols and formatting rules for all locales, as well as country => currency mappings and other useful information.

Disclaimer: I maintain a Go solution in this problem space: https://github.com/bojanz/currency

getfroggie
It's kind of strange that spreadsheet languages don't support money well. Using spreadsheets for escalator style automation is actually quite good and would really be amazing in a language that took typing seriously.
eriksencosta
I'm very grateful so far by the comments. I've learned a lot and it will help me on the next iterations of the library.
yett
C# has a decimal type: "The decimal type is a 128-bit data type suitable for financial and monetary calculations." https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
zoogeny
What I want more than a library is an exhaustive test suite for all of the edge cases segmented by currency or finance authority.

Tangentially, I also often think about super-strict typing. I know people mention some languages that already do this sort of thing (Units of Measure in F# was talked about). It seems silly to me that so many low level programming languages still use uint64, size_t, or char equivalents for the vast majority of the code. Why not `celsius` vs `farenheit`? Or `meters` vs `feet`. That would stop a lot of possible errors. And even more so, if the language supported things like (2 feet * 2 feet) = (4 square feet). Or (10 meters / 5 seconds = 2 m/s).

0rzech
Not a mainstream language, but Ada has Annex F - Information Systems, which can be used for currency handling. [1][2]

[1] https://ada-lang.io/docs/arm/AA-F/

[2] https://www.adaic.org/resources/add_content/standards/22rm/h...

donjigweed
Nice. Looks like it satisfies all the requirements detailed here [0]. Also, good discussion of the major difficulty dealing with money here [1].

[0] https://cs-syd.eu/posts/2022-08-22-how-to-deal-with-money-in...

[1] https://www.reddit.com/r/java/comments/wmqv3q/standards_for_...

jsiepkes
Java has JSR 354 for money [1]. There is also a reference implementation [2]. So there is official support for money in Java.

[1] https://jcp.org/en/jsr/detail?id=354 [2] https://javamoney.github.io/

hiddew
How does it compare to the Java money API (https://jcp.org/en/jsr/detail?id=354) and the related Kotlin DSL in https://github.com/hiddewie/money-kotlin/?tab=readme-ov-file...?
kens
The IBM 1401 mainframe (1959) optionally supported pounds/shillings/pence (£sd) arithmetic in hardware. In those days, there were 12 pence in a shilling and 20 shillings in a pound, so arithmetic with UK money was non-trivial. (This wasn't even microcode; this was literally boards full of transistors to add, subtract, multiply, divide, and format currency values.)
Etheryte
Does this library handle rounding rules [0]? In many countries, prices are rounded to the nearest 5 cent, but the rules can often be elaborate. It looks like the allocation interface might support this, but at the moment I didn't find any mention of it without digging into the docs themselves.

[0] https://en.wikipedia.org/wiki/Cash_rounding

xyst
I wonder if this would handle small decimals.

Edge case?

I want to calculate the installments of 265 Wei (0.000000000000000265 ETH) over a period of 144 months

textlapse
Kotlin has the problem of "I had N problems, so I think I can create 1 new solution, now I have N+1 problems" (obligatory https://xkcd.com/927/).

The 'money/percent/usd' are almost like new keywords - which to me seems bad. Why not a formatter or another tool that simplifies but doesn't make it too clever or worse, hard to read.

Kotlin is really cool and arguably better than Java. But the language is very hard to read ('easy' to write). And with so many different ways of doing a single thing (and many of them exist to support Java, I understand) plus new 'macro-like' things expanding the language automagically makes it very hard to grasp.

I hope I am not the only one with this same feeling...

pbreit
Are integers still a preferred method for working with and storing monetary amounts or can we go back to decimals now that most languages handle decimals decently? Monetary amounts pretty much always need to eventually be presented to humans so handling them as integers is quite a pain.
serial_dev
Congrats on the library, I’ll be looking into it for some nuggets of knowledge.

However, the Kotlin code looks absolutely… wrong? Strange? It just feels like a lot of magic to end up with ugly code.

This is all subjective, so my question is only this: is this how Kotlin is like in 2024?

wiseowise
Infix functions is one of the worst features of Kotlin.
Exerosis
My only complaint is that there seems to be too much infix. Why not just do 50.btc, 25.3.usd This would keep it inline with the time API doing 20.seconds Also percentages could be standard library if you ask me but would probably also need to be 2.3.percent.

Looks cool, always happy to see Kotlin love!

afh1
>However, no mainstream language has a first-class data type for representing money

Parcal has a Currency type. Though, I can understand not calling it mainstream... Fun fact it's a fixed point type which is just a Int64 behind the scenes, at least in Delphi.

zellyn
I'm new to Kotlin. Can someone explain how this function creates a Money object incorporating the given count of them?

This looks like it's ignoring the Number and creating a new Money object (of denomination 1?)

> public infix fun Number.money(currency: String): Money = money(currency, defaultRoundingMode, null)

DaiPlusPlus
> no mainstream language has a first-class data type for representing money

Visual Basic 6 and VGA had a `Currency` type (replaced by `Decimal` in VB.NET): https://learn.microsoft.com/en-us/office/vba/language/refere...

T-SQL has `money` and `smallmoney` types: https://learn.microsoft.com/en-us/sql/t-sql/data-types/money...

...am I missing something?

vintagedave
Delphi has a currency type, as does C++Builder (so, a native Currency type available in C++ with that toolchain.)

This one seems to carry units, as in, it is not a type with math for a known amount of fixed point precision that differentiates this library, but that it captures that plus the unit -- the currency, USD, EUR, etc -- that it is in.

rebeccaskinner
Neat. It seems to me like this is filling three separate gaps:

  - fixed point arithmetic
  - tagging values with units
  - localization for parsing currency names into units
I'm curious if Kotlin's type system is expressive enough to allow you to catch at compile time cases where you might be trying to, e.g. add USD to GBP.
yafetn
The currency codes could probably be inline value classes. That way, you can do

    val price = 100 money USD
Note the lack of quotes around USD.
ppeetteerr
What is up with that API? `1.25.percent()`? `1 money 'USD'`? Love the spirit of a money library, but this is a little odd.
bradley13
Look nice. I do find the wordy operators reminiscent of Cobol. Instead of "subtotal decreaseBy discount" in Kotline I would expect either "subtotal.decreaseBy(discount)" or perhaps "subtotal * (1 - discount)".
Ygg2
Nice library!

Manipulating money is probably trickiest thing since time was invented. Library looks very usable.

I have to ask though:

> val transactionFee = 1.25.percent() // 1.5%

How is it 1.5?

sandGorgon
just curious - what is the backend api framework that N26 uses ? is it kotlin specific ? or generically spring boot or something ?
sgt
What's the best library for JS or TypeScript?
Exerosis
My complaint is, bit too much infix :( What about 50.btc 25.usd Keeps it inline with the time API as well with 20.seconds and what not.
pmarreck
This covered the API nicely but said nothing about how the amounts were internally-modeled and how rounding was dealt with. It was only mentioned.

I had an idea for a fixed-decimal class (that would also be useful for a money class) that held all inflight amounts as fractions and only did the division and float rounding when you needed to view the amount.

mplewis
Banks typically use a fixed floating point of 1 integer step = 1/100 cent. Is this value configurable in your library?
atemerev
Cool! As underlying values, do you use integers, bigdecimals, or a decimalized double hack like in OpenHFT?
xiaodai
cool. whoever uses these libraries better validated it very well.
sam0x17
This is cool and it's great to see people adding better first-class support for currencies in as many languages as possible!

I am the author of a similar crate in the rust ecosystem: https://crates.io/crates/currencies

major features include:

* support for all ISO-4217 currencies (though not all have been explicitly tested as it is hard to find native users of some)

* compile-time macros for specifying an Amount in the native format (with symbol, etc)

* support for non-base-10 number systems (there are a few ISO currencies that needed this)

* every currency uses an appropriate backing data type, and new currencies and backing data types can be defined as long as they meet the trait requirements

* opt-in ability to enforce only checked math ops (but using the usual +,/,-,* etc symbols). This is critically important for crypto and finance applications where a panicking math op can, for example, brick a blockchain or real-time trading system

* support for parsing and printing currencies in their native format at runtime

* currencies use the appropriate format style (https://github.com/sam0x17/currencies/blob/main/core/src/cur..., i.e. symbol can be "suffix attached", "suffix spaced", "prefix attached", "prefix spaced")

* support for a number of cryptocurrencies, basically popular ones and ones I've bothered to add. Will always accept PRs adding others!

* ability to define your own currencies using the `define_currency!` macro. Though these will not be supported by the built-in `amt!` macro unless I add them to the crate.

e.g., here is how a few of the core currencies are defined:

define_currency!(USD, u64, 1_00, "$", "United States Dollar", PrefixAttached, true, false);

define_currency!(BTC, u64, 1_00000000, "BTC", "Bitcoin", SuffixSpaced, false, true);

define_currency!(ETH, U256, u64_to_u256(1_000000000000000000), "ETH", "Ethereum", SuffixSpaced, false, true);

One disadvantage right now is there is no ability to have a generic "amount of some arbitrary currency" other than through generics, as the underlying traits aren't object-safe. A good way to work around this is to define an enum that contains all the currencies you plan to support. I am working on a feature that will let you easily generate this enum at compile-time :)

parsing is done using my Quoth parsing crate which provides a very safe, lexer-less way to do parsing of UTF-8 strings that relies on recursive parsing in a way somewhat similar to syn, but there are no token streams https://crates.io/crates/quoth

psd1
> no mainstream language has a first-class data type for representing money

I don't think that's correct, absent some no-true-scotsman gymnastics.

F# has units-of-measure (UoM) out of the box, and it supports decimal numbers. I've come across a python library for UoM as well.

The big problem with handling money in code is not, IMO, the rounding (your allocate function is a nice utility but it's not core); it's unit confusion - adding baht to ren mi bi, adding cents to euros, etc. This problem is very well solved by F#'s UoM.

bayindirh
> However, no mainstream language has a first-class data type for representing money...

I beg to differ. Java has "Decimal" class which guarantees to be safe from IEEE754 floating number side effects, and specially created to handle cases like money and financial calculations.

In these days it's used as BigDecimal, it seems [1].

[0]: https://docs.oracle.com/javase/8/docs/api/java/text/DecimalF... [1]: https://docs.oracle.com/en/java/javase/23/docs/api/java.base...

boronine
I think most of this is covered by a good Decimal API, currency stuff probably shouldn't be embedded into a language because it changes: currencies come and go, get redenominated etc. Although one simple thing that would be useful is keeping track of abstract units, e.g. throwing an error when attempting to do 10 USD + 10 EUR.
shortrounddev2
> However, no mainstream language has a first-class data type for representing money

This is literally the entire point of COBOL

systems
what type of language is kotlin?

Is it functional , OOP or something else, which paradigm does it represent?

TZubiri
[flagged]
slt2021
I dont understand the need for this.

I always has used integer data type and counted cents. Why need this?

dlahoda
crypto needs support for decimals, determinism, rounding directions, uplifting to higher dimensions during long term accrual, down lifting fosome kind of quantization, path dependance.

eth is whole number 10*18. usdc is 10*6.

usd is if to speak is 10*2 number.

solana eth price is less than eth eth price because of bridge risk.

etc.

there are on decimal money to out of crypto.

there are logarithmic money in crypto.

so many many moneys.