Fixed point decimals for accounting

What are some efficient ways to implement fixed point decimals, for tasks like accounting?

https://juliahub.com/ui/Search?q=decimal&type=packages

Both Decimals.jl and DecFP.jl look quite good.

Alternatively, you could just do all computations in integer cents and divide by 100 for display.

3 Likes

ok thanks, I found fixedpointdecimal.jl looked really good. But, the output is not just a number, but also a description.

I thought of dividing integers, but that means inputting values multiplied by 100, and also could create problems when weighting.

I wasn’t sure from the documentation for Decimals.jl or DecFP.jl, to actually fix the decimals, when numbers are manipulate.

That’s because they are floating-point decimal types. Why do you need fixed point specifically, as opposed to decimal floating-point?

2 Likes

Yes, exactly.

2 Likes
struct AccountingNumber <: Real
    x::Int

    AccountingNumber(x::Number) = new(round(Int, 100*x))
end

Base.show(io::IO, x::AccountingNumber) = Printf.@printf(io, "%.2f", x.x/100)
...

Add some conversion and promotion methods and you should be home.

3 Likes

But what if I have a number like 1.999999, it would round to 2.00 and not 1.99

Correctly, I would have thought. Is the accounting standard to round down? In that case, replace round in the constructor by floor.

OK, that would work. Usually, there is only 2 decimals, there are cases, of 1/10th cent, there normal rules apply. The problem is that I’m not rounding, and the values need to balance exactly.

1 Like

You could have

struct AccountingNumber
x::Int
nofdecimals::Int

AccountingNumber(x, nofdecimals=2) = new(floor(Int, x.x*exp10(nofdecimals)), nofdecimals)
end

Might be a bit overkill for those mills though.

I think I have an idea of things to play with. How do I call a struct?

If it has the constructor i suggested above, then n = AccountingNumber(6.24) will create an AccountingNumber representing 6.24 (by way of the fields x=624; nofdecimals=2). Is that what you mean by “call a struct”?

Yes, I thing it is. But;

struct AccountingNumber
	x::Int
	nofdecimals::Int

	AccountingNumber(x, nofdecimals=2) = new(floor(Int, x.x*exp10(nofdecimals)), 		nofdecimals)
end

n=AccountingNumber(6.25)

gives an error;

Type Float64 has no field x

Why is there no field?

My bad, the constructor should have x, not x.x. As an argument to the constructor it’s a normal number, so it doesn’t have any fields.

1 Like

It seems like you are re-inventing the wheel here…

(I really don’t understand why you wouldn’t just use decimal floating point.)

1 Like

If your goal is to use the numbers for accounting I agree. But if you want to figure out how to implement the numbers (which is how I read the question), that’s its own challenge and this is how I’d start solving it.

It might make sense to make a specific type for this (even with decimal numbers), since apparently the rounding rules in accounting are funky.

There are multiple fixed-point and decimal-float packages; why not look at them?

It might make sense to make a specific type for this (even with decimal numbers), since apparently the rounding rules in accounting are funky.

Decimal floating-point supports multiple rounding modes already, and will typically accumulate errors (in a long sequence of calculations) much more slowly than fixed point.

But the OP hasn’t clearly defined their application or their requirements, so we are just guessing here. That’s part of why I keep asking to find out what specifically is wrong (if anything) with decimal floating point for their application.

3 Likes

I’ve looked at several, and this is much the same. I’m thinking floor would be useful here, if each term had 3 or more decimals. I’ll see if there is a way to concact 0. It might not be an issue to just use floats, but the problem, is that I’m worried that some of the tests won’t work right, if the difference between certain values is not exactly 0. I could make a MWE to illustrate this better.