Go should have a primitive decimal type


(Phillip) #1

You’ve probably heard this a million times. I know you have. So this is NumberOfDecimalRequests[1000000].

Consider the following: Go has an enormous amount of momentum right now. People recognize that the nature of goroutines and channels is far superior to any other form of asynchronous back-end code-flow. The philosophy of “Do not communicate by sharing memory; instead, share memory by communicating” is pure genius and blows everything else out of the water especially .NET/.NET Core ideas. I know. I’ve coded back-end APIs in Go and I have seen the results first-hand.

However, there is a market that you are going to completely fail to reach in terms of adoption of programming language unless you add native support for decimals. That market is: anything that has anything to do with money ever. If you have any common sense then you should realize that market is quite large. I work for a company developing a payroll application. It is practically a Greek tragedy that I have to rely on a third-party dependency and horrific functional notation to do any math.

I won’t waste your time with explanations of why float primitives fail because you know. I won’t waste your time with how inconvenient ‘big’ types are because you SHOULD know.

If you are not already working on native support for a Go decimal type, then start now. If you have been, then please reply and set my mind at ease.

Signed: every mathematician that writes Go code.


(Norbert Melzer) #2

There are a lot of programs in use in the financial sector, written in programming languages that do not support decimals (or better, arbitrary precision fixed point numbers) natively, but only through a library.

If though you consider the current interface of the available libraries clumsy, well, just write your own with a “better” interface.

But as you have already spoken about the big ints, their interface is designed to be able to control memory allocations. The interface in other “big” procedural and OO languages is quite similar.


(Ivan Matmati) #3

Hi,

Sure Java has BigDecimal, Python has decimal, etc. But when I worked for financial projects the rule was : no floating point at all ! You can work with cents instead of currency (or any other scale that fits your need). For example, a price of 1.82 euro would be stored as 182 cents. Now you’re sure of your maths !


(Norbert Melzer) #4

Neither Javas BigDecimal nor pythons decimal are “floats”, which is for “floating point number”, they are “fixed point numbers” and usually their precision is arbitrary, which makes them also computational and/or memory wise expensive.

But yes, even highest precision floating point numbers are not accurate enough for monetary values.

Also just assuming “cents” is dangerous in a monetary field. My sister works for a bank (not programming or even IT) has to deal with fractions of cents a lot. Its one of her responsibilites to sample-check bookings if those fractional cents that happen on interest or transfer-fees are applied correctly and not “lost” or transfered to the wrong account…

Still, I haven’t yet seen any language that had decimals “built-in” in a way the OP might actually mean… As he considers the math.Big interface clumsy, I assume that OP wants to use +/-/etc directly with those numbers as well as literals…

Though I’m not even sure how such a literal should look like, as one needs to provide precision as well…


(Ivan Matmati) #5

BigDecimal can be converted to a float and vice versa. From Decimal python documentation : Decimal “is based on a floating-point model";
But whatever.
Cents are not dangerous if they are used correctly, in fact they’re the safest way. Rounding a fraction of cents is harmless and calculation with cents must be done conserving the expression. That means you don’t calculate 100*(4/3) but (100*4)/3, you don’t want to multiply the error.
Have a nice day.


(Sean Killian) #6

I don’t understand; how do you represent 99.9¢?


(Maurizio Ferreira) #7

Why limit to cents ?
You can use thousandths or tenths of thousands
In the first case, 99.9c is translated to 999,
In the second case to 9990.


(Ivan Matmati) #8

Yes,

Exactly. You define the basis of your values, that means the threshold where you would round the value if a decimal part would appear. For example, if you pay with a limit of cents because you would never pay an amout of 1.678 euro for example, then you know that you should store the values in cents. A decimal is possible but only during a calculation never in the persistence layer. Any financial operation must define a rounding operation before its final step. Have a nice day.


(Norbert Melzer) #9

And exactly this is why we use proper decimal or even money types, as they can ensure these invariants better than plain integers which do not even carry the information about whether they are cents, or thousands of cents or even full dollars…


(Ivan Matmati) #10

Decimal will do the rounding for you ? It has semantic about the currency ? How is that ? I don’t see how it could. This system has proven its value long time ago … I remember in a technical interview a problem with this system. I don’t see how a decimal could round by itself … Of course a number has no semantic, but percentage as well, number, boolean, string, etc. and I’m sure you can deal with it and give it some. Just by curiosity, would you wait for every crypto money to be integrated in a currency library before using them ?


(Norbert Melzer) #11

Depending on the implementation a decimal could either extend the precision or round. And as you said, it carries semantic and metadata… No problem then because of metres vs. feet then… No crashes on jupiter… So decimals win over bare integer…


(Ivan Matmati) #12

An example of such an implementation ?


(Norbert Melzer) #13

Not in go, but just telling what it could do…


(Ivan Matmati) #14

Is there currently such an implementation in whatever language or is it just a theoritical point of view ? And I’ve never conceded that a decimal had any semantic.


(Norbert Melzer) #15

Elixir, decimal.

Per default it will extend the precision, but you can also have a process local context set that will limit precision and define a default rounding mode.


(Ivan Matmati) #16

Lucky Elixir programmers but that leaves a lot of programmers alone. But I found at least one library in JS that seems to agree with the method I exposed : https://currency.js.org

When working with currencies, decimals only need to be precise up to the smallest cent value while avoiding common floating point errors when performing basic arithmetic. currency.js resolves this issue by working with integers behind the scenes, so you don’t have to be concerned about decimal precision.

Or even this one : https://otree.readthedocs.io/en/latest/currency.html

But in our experience, it’s simpler to just keep points as integers, because with decimal places there are more complications about formatting and rounding.

(Norbert Melzer) #17

Yes, thats totally fine, and they do that in a typesafe manor, but sometimes fractions of cents are necessary as well, and I like to have a type that can deal with that easily.


(Jay Poss) #18

I use the following code for money calculations. Amounts are stored as float64.
Ex:
discount := Money(amt * percent)

// rounds to 2 decimal places and cleans up the least significant bits
// typically called after a mathematical operation that produces an inexact value due to nature of floating point math
// ex. 1.49999001 = 1.5000000
// ex. 1.50001115 = 1.5000000
func Money(amt float64) float64 {
	if amt == 0 {
		return 0
	}
	var intAmt int64
	var rounder float64 = .005
	if amt < 0 {
		intAmt = int64((amt - rounder) * 100)
	} else {
		intAmt = int64((amt + rounder) * 100)
	}
	return float64(intAmt) / 100
}

(Norbert Melzer) #19

Never do this!

Ive not read any of the following but I’m pretty sure at least one will contain the correct reason:

(5 top links for me when searching for “why you don’t use floating point for money” with google)


(Phillip) #20

You beat me to it. When working with currencies in payroll applications you absolutely require arbitrary precision.

The penny integer tactic is also asking for bugs because developers will come to points in code where they aren’t sure if they need to multiply by 100, divide by 100, or do nothing. This is surely alleviated by packages which manage it, but then integration can be an issue because you’ll see JSON like this:

{
    "payment": 1925
}

Is that $1,925 or $19.25?

Consider the following:

Suppose a person is paid $50,000 per year according to their employee salary agreement. However, they are paid biweekly. This means their pay is divided 26 or 27 times depending on how many paydays happen. If it was 27 you would get paid 1851.85 every two weeks 27 times. That comes back to $49.999.95 on your W2 for gross wages assuming you didn’t get a raise or something.

Not many people would complain about it, but it’s technically non-compliance and results in, at the very least, a headache. Also, the quarterly 941 tax form employers have to file literally has a fractions of cents adjustment field.

The biggest issue with using a dependency in Go is the functional notation due to lack of operator overloading. Go doesn’t need or want operator overloading, so decimal primitives which work with mathematical operators would make workflow immensely more efficient for software developers in the financial industry.

Most dismiss complaints about the clunky notation as just general moaning and complaining for nothing. However, it’s more than that. Mathematical infix notation is second-nature to all of us. This means the difference in time to write one meaningful line of code changes based on not only length of characters but also how much you have to think about how to use the functional notation.

Payroll calculation software is immense, and all states have different calculations. If each line required 10 seconds more thought because of the notation, that’s a 5 and a half hour difference at 2000 lines of such code which are kind of low-ball estimates. That’s not accounting for maintenance and bug fixes going forward.

One of the goals of Go seems to be making the development process more efficient, and the lack of decimal primitives runs counter to that.