COBOL and Julia comparison

Hello,

I’d like to state in advance that I have negligible familiarity with number representations, so please excuse any inaccuracies. I also enjoy getting rid of legacy systems.

I have recently read an article which pointed out that one of the reasons COBOL is still somewhat prevalent in the banking and governmental sectors is that it used fixed point number representations, which are less prone to accumulating inaccuracies during the typical calculations these sectors use. (There are other factors of course as many languages let you use fixed point numbers with varying levels of difficulty by now.)

This article describes Muller’s recursion formula, which is a good example of this erratic behavior. This series should converge to 5, but using doubles it converges to a 100, after some hectic jumps. I wanted to try this with Julia’s types, so I wrote this code.

module cobol

using FixedPointNumbers

mullers(y,z) = 108 - (815 - 1500/z)/y

function recur(t,n)
    v = zeros(t, n)
    v[1:2] .= t[4, 4.25]

    for ii = 3:n
        v[ii] = mullers(v[ii-1], v[ii-2])
    end

    v
end

end # module

For Float64 it reproduces the behavior of other languages, as expected. With BigFloat it properly converges to 5. With Rational (Float64.(recur(Rational,20)) it also converges to 5, but overflows before the double representation would reach it’s fixed point at iteration 29.

What I am interested in (apart from your insights in general) is that by using a fixed point representation N41f23 it also erroneously converges to 100 (albeit differently than doubles - at least no sign change) - is there a way to do it right with fixed point representation that I haven’t found?

3 Likes

I have little idea about the problem here, but you likely need to make sure to preserve the types used, otherwise, you’ll calculate all in-between calculations in another number type, and only convert back when writing into the array. See if this fixes it:

mullers(y::T,z) where T = T(108) - (T(815) - T(1500)/z)/y
2 Likes

I doubt that this is true. Error accumulation is typically vastly worse with fixed-point calculations than for floating-point calculations. Even simply summing a large list of N numbers accurately is much more difficult with fixed point than with floating point. Moreover, fixed-point arithmetic is vastly more susceptible to catastrophic overflow and requires continual attention to scaling. Muller’s recursion is apparently a carefully crafted exception to the rule (but even there, the article points out that fixed-point also gives the wrong answer if you don’t use a very particular number of iterations).

(This is not to say that floating-point calculations are immune to errors, just that fixed point is usually worse.)

The main advantage I’ve heard cited for fixed-point arithmetic in financial situations is that it represents human decimal inputs exactly, which is important in situations where you have a fiduciary requirement not to convert a $0.01 deposit into $0.0100000000000000002081668.... But this advantage disappears if you use decimal floating point.

The other advantage of fixed-point arithmetic is that it can be faster; e.g. you used to see a lot of fixed-point signal processing on embedded hardware lacking a floating-point unit. And it’s certainly faster than decimal floating-point on normal CPUs (which lack decimal floating-point hardware). But floating-point performance seems unlikely to be relevant in banking situations where exact decimals are required.

8 Likes

The place where fixed point is really nice is that it has 0 rounding error for addition, subtraction, and multiplication by integers

1 Like

So does floating-point arithmetic.

Until your results get too many digits to be represented exactly, of course — at which point floating-point accuracy gradually degrades, whereas fixed-point arithmetic catastrophically overflows.

The idea that every floating-point operation incurs an error is a pernicious myth.

5 Likes

Fixed point is a bit ambiguous. What they mean is like https://github.com/JuliaMath/FixedPointDecimals.jl

I believe decimal floating-point is relatively new, both as a standard and in hardware (rare), and historically COBOL has used decimal, meaning decimal fixed point, not decimal floating-point. I guess that’s still the case. Who knows if it supports both, by now it’s even object-oriented now (while I doubt much exploited).

Fixed-point (or integers) do not NEED to overflow (I’m not sure what DECIMAL in COBOL does, maybe you get some Inf value). In a bank, for sure you wouldn’t want owerflow, you could throw an exception (neither ideal, or saturate… for other situations).

Point taken — but halting the calculation with an exception can still effectively be catastrophic, especially if it occurs frequently. Moreover, in floating-point arithmetic you can raise an inexact exception you want to detect rounding errors as well.

2 Likes

Banks are not using Cobol because it is better than more modern languages for their purposes, but because it is too difficult / expensive / risky to replace legacy systems :wink:

3 Likes

The main point of the article seems to be that COBOL has the advantage of having fixed-point arithmetic built in:

Python (and for that matter Java) do not have fixed point built in and COBOL does. […] If you’ve ever worked on a project with a whole bunch of imports you already know that they are not free. In a language like Java (which is usually what people want to migrate to when they talk about getting rid of COBOL), the costs of the relevant library can be noticeably higher.

(They are citing an article saying that BigDecimal is slow compared to double, duh.)

I think the relevant point here for Julia is not whether one type of arithmetic is better than another (it depends on the context), but rather that in Julia there is no inherent advantage for “built-in” types, unlike most other languages. The fact that FixedPointNumbers are in a package does not inherently make them any slower than the built-in numeric types (which are, in fact, implemented in Julia code). The only limitation is what arithmetic operations the hardware supports vs what operation you are trying to implement.

6 Likes

I guess there’s a reason they introduced decimal-floating point, for banks and/or engineering. I can maybe see the point (pun intended) more for engineering as you just do not want to lose a cent, or more, gradually. There’s no way around that when you do not have enough bits.

But for huge values, the old Lira, and Zimbabwe dollar, decimal-floats are good. Bitcoin also does something clever thinking about values growing.

For the dollar, most individual entries aren’t going to be too big, and even if (would end in lots of zeros) so it might make sense to use DECFP32 to save space, and only DECFP64 for cumulative values?

Julia shouldn’t be overlooked as a COBOL replacement, while I likely think that will happen, not just because people will overlook the best type to use, that’s not in the standard library. Maybe we need a COBOL to Julia transpiler… anyone up for that? They already did (old) Fortran to Julia transpiler for just one library.

1 Like

I have to say that I think the original source article is fairly misleading; this is the second time in a few months that I’ve seen people walk away confused by the article. The author doesn’t really provide much evidence that fixed point numbers were better suited for the IRS’s use cases. They simply note that the results were different when the team wrote a naive Java port, then they show in a potentially overfit toy problem that the differences could sometimes go in favor of fixed point, then they wave theirs hands and assert it’s a potential problem that newer languages don’t have fixed point numbers as built-in types.

It all sounds like they just should have staffed the project with someone with real numeric computing experience before trying to port mission-critical numeric code.

1 Like

I think we’ve gotten off track from the original question here, which was not about fixed- vs floating-point arithmetic but rather on how to reproduce a particular calculation from either COBOL or Python:

The problem with this code may be that, in the current release of FixedPointNumbers.jl, / and * are implemented by converting to floating point, performing the operation, and then rounding back to fixed point.

So, perhaps you want a different arithmetic operation; maybe someone who is more familiar with the FixedPointNumbers package can comment on the best way to reproduce results from the Python decimal package.

1 Like

It is not an essential matter whether the calculation is done within floating point numbers. The multiplication and division for fixed-point numbers cause the decimal point to be in a different position. In the case of Fixed (not Normed), it is possible to calculate without numerical error by widening the types.

2 Likes

If you just want to reproduce the python example there, you can do:

module Muller

using FixedPointDecimals

muller(y, z) = 108 - (815 - 1500 / z) / y

function recur(T, n)
    v = zeros(T, n)
    v[1:2] .= T[4, 4.25]

    for i = 3:n
        v[i] = muller(v[i - 1], v[i - 2])
    end

   return  v
end

end # module

and then you’ll get

julia> using FixedPointDecimals

julia> Muller.recur(FixedDecimal{BigInt,25}, 20)
20-element Array{FixedDecimal{BigInt,25},1}:
 FixedDecimal{BigInt,25}(4.0000000000000000000000000)
 FixedDecimal{BigInt,25}(4.2500000000000000000000000)
 FixedDecimal{BigInt,25}(4.4705882352941176470588235)
 FixedDecimal{BigInt,25}(4.6447368421052631578947362)
 FixedDecimal{BigInt,25}(4.7705382436260623229461618)
 FixedDecimal{BigInt,25}(4.8557007125890736342039857)
 FixedDecimal{BigInt,25}(4.9108474990827932004342938)
 FixedDecimal{BigInt,25}(4.9455374041239167246519529)
 FixedDecimal{BigInt,25}(4.9669625817627005962571288)
 FixedDecimal{BigInt,25}(4.9800457013556311118526582)
 FixedDecimal{BigInt,25}(4.9879794484783912679439415)
 FixedDecimal{BigInt,25}(4.9927702880620482067468253)
 FixedDecimal{BigInt,25}(4.9956558915062356478184985)
 FixedDecimal{BigInt,25}(4.9973912683733697540253088)
 FixedDecimal{BigInt,25}(4.9984339437852482376781601)
 FixedDecimal{BigInt,25}(4.9990600687785413938424188)
 FixedDecimal{BigInt,25}(4.9994358732880376990501184)
 FixedDecimal{BigInt,25}(4.9996602467866575821700634)
 FixedDecimal{BigInt,25}(4.9997713526716167817979714)
 FixedDecimal{BigInt,25}(4.9993671517118171375788238)

which is the exact same list of values in the article, so I suspect that the Decimal type in python is probably equivalent to FixedDecimal{BigInt,25}.

As others have said that article is misleading, but if you just want to show you can recreate the sequence with a fixed-point Julia type, there you have it.

11 Likes

Not exactly new, but a niche until the 80s.

1 Like

No, that recursion is an extremely bad illustration of inaccuracy accumulation. As soon as you leave the curve y = 8 - 15 / z, which effectively every plausible rounding will do, the correct solution in exact arithmetics is to converge to 100.

See https://news.ycombinator.com/item?id=17637441 for a reasonable amount of analysis.

10 Likes