Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Ask HN: Inconsistent expression calculation between programming languages
83 points by mingodad on Aug 7, 2021 | hide | past | favorite | 62 comments
This expression shows that we have several disagreements between programming languages about what is the end result:

=====

EXPR = (((((((788)*(8.46))))+8342*1.803-1))*4186.4*(15))*(((22%284/((7530/((2)*(((((25))-421))))))*597%2663)+7283.8-9.60+167.8644%((3))))+(8871)

POSTGRESQL = 8291561284461.33301440

SQUILU = 8291561284461.3

JAVA = 8.291561284461331E12

D = 8.29156e+12

SQLITE = 8290383058308.3

PHP = 8035491468054

JAVSCRIPT = 8036090802426.098

MYSQL = 8036090802312.071

PYTHON = 1.1107636287e+13

RUBY = 11107636286950.146

LUA = 11665910614443

=====



This is solely due to differences in the / and % operators:

  # int/int -> int
  POSTGRESQL = 8291561284461.33301440
  SQUILU = 8291561284461.3
  JAVA = 8.291561284461331E12
  D = 8.29156e+12

  # int/int -> float
  LUA = 11665910614443
  PYTHON3 = 11665910614443.387

  # int/int -> float, UNIQUE: pos%neg -> neg
  JAVSCRIPT = 8036090802426.098
  MYSQL = 8036090802312.071      # single-precision FLOAT

  # int/int -> int, UNIQUE: negint/posint -> negint (the floor)
  PYTHON2 = 1.1107636287e+13
  RUBY = 11107636286950.146

  # int/int -> int, UNIQUE: negfloat%posint -> negint (the ceil)
  SQLITE = 8290383058308.3

  # int/int -> float, UNIQUE: negfloat%posint -> negint (the ceil)
  PHP = 8035491468054


Interesting.

What's the correct answer?

Which programming languages specify which parts of IEEE 754 they follow (or not)?

https://en.wikipedia.org/wiki/IEEE_754

Ditto rendering of floating point numbers. Here's a prior thread about Steele & White, Grisu, Ryu:

"Here be dragons: advances in problems you didn’t even know you had" https://news.ycombinator.com/item?id=24917659


The correct answer is not making the computer guess what you mean. That's the root problem here. You're being super-sloppy about specifying what actual operations you want to take place. What are the types of the operands? What are the semantics of the arithmetic operators for the pairs of operand types you get?

Without bothering to tell the computer what you want for the types or select the correct operators for the types you provided, you're falling back on guessing. Not every language guesses the same way, because not every language designer has the same guess what people meant.

My ideal is a language that tells you that expression is bad and to actually clarify what you mean. But a lot of people seem to hate having to explicitly communicate their intent.


Who even programs like this? If I saw that expression in code I think I’d have a fit.

I love Ruby for lots of things, and even with its duck typing system I’d never see someone plop down stuff like that. Ruby’s got quo, fdiv, divmod etc for good reason!

So is this contrived, or do people encounter programmers who do this stuff on a regular basis (not newbies, like experienced people… this feels like programming 101 to me)


I love Ruby too, but "/" giving integer results when both sides of the expression is integer is a frequent source of error, and I'm not convinced it's a good choice - in my experience at least it is rarely (though sometimes) what people want.


This is why Python 3 changed division to return floats rather than ints. ("//" retains the old floor division behavior.)


> My ideal is a language that tells you that expression is bad and to actually clarify what you mean.

And there are existing language that do that.


IEEE 754 doesn't really have anything to say about which semantics you should choose for int/int division. If you choose the semantics of casting both operands to floating point and then dividing, IEEE 754 specifies both 1) how the int->float conversion should happen, and 2) how the float/float division should happen. But if you choose the semantics that int/int division returns an int without any floating-point conversions (like C's '/' operator), then no floating point numbers enter into the question at all, and it's out of scope for IEEE 754.


Over thirty years of programming I've slowly become convinced that implicit conversions are the devil.


This devil allows high productivity though.


Not everybody is convinced that it's "more productive" to produce more bugs.


Pareto, context and objectives are importort factors in that decision.

Coding a 5 lines bash script in rust brings safety but it's not a win.


If the bash script had an incorrect value due to an implicit conversion, then yes, the rust implementation is clearly a win.


So, context.


Not everybody, but probably enough people. Actually you could even build your career around it. Some guy got into office at night time to fire estinguish the problem his buggy code created. Got appraisal from management for such extra effort. Everybody else's not so buggy code didn't get that appraisal...


Bingo. When performance is the overriding goal, it is often a matter of choosing which deals to do with which devils.


Must be down to all those idle hands he can bring to bear on a given task.


There is no correct answer. There is no correct answer. The various ways different languages handle implicit type conversion with division and modulo operations are all both correct and incorrect at the same time. It's really an arbitrary choice. In fact, there are more choices than presented above. I think, for example, Perl6, uses rational numbers in some of the above situations.


Indeed. But please note Perl 6 has been renamed to the Raku Programming Language (https://raku.org #rakulang).


Well, ideally you'd want to treat int/int -> rational. The original form is quite obfuscated, some of the parentheses are to enforce evaluation of the given string as number (and the various programming languages will differ as what number that'll be, due to representation of decimal fractions and type conversion), some parenthesis are plainly superfluous, yet still there is remaining ambiguity (how is a%b/c to be interpreted? Most programming languages will do this left->right, but better make that explicit). Prefix notation to the rescue:

Common Lisp (with *read-default-float-format* -> SINGLE-FLOAT)

CL-USER> (+ (* (+ (* 788 8.46) (1- (* 8342 1.803))) 4186.4 15 (+ (* (/ (mod 22 284) (/ 7530 (* 2 (- 25 421) (mod 597 2663))))) 7283.8 -9.60 (mod 167.8644 3))) 8871)

8.03609e12

Wolfram Alpha finds for (((((((788)*(8.46))))+8342*1.803-1))*4186.4*(15))*(((mod [22, 284]/((7530/((2)*(((((25))-421))))))*mod[597, 2663])+7283.8-9.60+mod[167.8644, ((3))]))+(8871)

8.03609080242609957376254980079681274900398406374501992031872*10^12


They are possibly all correct.

Correct means that the requirements of a documented specification are being followed.


interesting note


So, we have (at least) different evaluation orders, different meanings of the % operator (How are negative numbers handled?), different meanings of the / operator (is 10/3=3 or 3.3333 — notice that Python3 joins the 8.29e+12 fold!). This shows two things: 1) Even though it is syntactically correct in N different languages, it does not necessarily mean the same thing, and 2) floating-point math has many traps. Neither should be news to the HN readership.


After removing all modulo operations and transforming all numbers to floating point:

====

EXPR = (((((((788.0)(8.46))))+8342.01.803-1.0))4186.4(15.0))(((22.0/((7530.0/((2.0)(((((25.0))-421))))))*597.0)+7283.8-9.60+167.0))+(8871.0)

SQUILU = 8259816920615.1

LUA = 8259816920615.1

JAVASCRIPT = 8259816920615.111

SQLITE = 8259816920615.11

POSTGRESQL = 8259816920615.11377654520912550160000

MYSQL = 8259816920615.113

AMPL = 8.25982e+12

PHP = 8259816920615.1

RUBY = 8259816920615.111

PYTHON = 8.25981692062e+12

====


See this interesting paper on the possible choices of definition for div and mod:

RT Boute, "The Euclidean definition of the functions div and mod" (1992)

https://biblio.ugent.be/publication/314490/file/452146.pdf


Your expressions omits many necessary operators, and thus becomes ambiguous.


It’s because Hacker News parsed the asterisks in the expression as italic markers.


With corrected formatting, they wrote EXPR = (((((((788.0)*(8.46))))+8342.0*1.803-1.0))*4186.4*(15.0))*(((22.0/((7530.0/((2.0)*(((((25.0))-421))))))*597.0)+7283.8-9.60+167.0))+(8871.0) which is unambiguous.


Other than nerdsniping, what is the point of this expression?

If you wanted to show how different languages treat modulo, type conversions, division, etc., you could just show individual examples. That would be significantly clearer.

Was there some practical reason behind creating this expression, or is it just obfuscation?


Well, one of the points is that even if things work properly/as expected 99.8% of the time, when you add layer after layer of "works ok most of the time", you end up with a very unreliable system. And given that all computing revolves around manipulating numbers, the fact that there's so much wiggle room (even if there are often good reasons for most of the differences we are seeing here, which are fundamentally semantical differences anyway), should at least make you slightly uncomfortable when thinking about building complex systems. And that's without entering the realm of what happens with overflows, underflows, NaNs and everything else. The bases are solid, but we can still see quite a lot of holes.


Thank you for explaining your understand of the issue presented here with a wide view.

I was shocked when I got the results shown here and thought of times in the past were I copied and pasted formulas (and sometimes did some small changes to be accepted by the language I was using) without thinking that any language would interpret it differently without warning.

Other good worth replies in my opinion:

amichal https://news.ycombinator.com/item?id=28100036

hinkley https://news.ycombinator.com/item?id=28101243

hansvm https://news.ycombinator.com/item?id=28100156

I believe that most of us are acting in good faith most of the time and we need some common examples/tests like basic math expressions that we could use to check/evaluate any/most implementations in programming languages to try prevent or minimize divergences like the ones shown here by a random generated expression that has no other meaning than a uncommon valid? test case to show an issue.


My point was that the expression could be one sixth the length and contain no more than two parentheses, from what I gather. Hence the question what the purpose of constructing this expression was - it just looks like somebody facerolled their numpad and then made random edits until the syntax was ok.


Yeah, sorry about that. I'm generally against obfuscation, but in this case, the "why" of the resulting differences in calculations is well known to most programmers; the big issue is not "why we get different results", but rather "are we ok this type of inconsistent behavior".

If you simply showed that modulo and implicit casting work differently in different languages, we wouldn't have had any discussion. With the obfuscation, we got some discussion about the main point... and the discussion about the obfuscation. Honestly, none of us managed to capture all the perspectives well enough. Yeah, the "Ask HN" could have used a less obfuscated expression and been better worded to clarify that the specific semantic differences weren't the main point here. Similarly, most commenters (myself included in my previous comment) failed to realize/clarify that there were two discussions going on here, but that one of them is settled with a technical footnote.


That’s my question too. I really hope this is contrived! Otherwise I’m going to go to sleep having nightmares about expressions like this in code running important things out there!

Imagine if all the bolts on bridges were just kind of thrown on randomly hoping they hold. That’s the equivalent to this expression.


Fairly early on in my career I was on a team writing a GUI programming environment (think old school flow charts) that generated ASP (classic) or Java backends. We allowed simple expressions in assignments with the basic arithmetic operators and functions. We parsed them into a tiny AST and output Java or ASP code for them with lots of explicit parens and casts. After fiddling with minor differences in precedence, associativity and implicit casts we still had some cases that did not match. Pairing with a more senior dev we wrote a C program to generate and compare a large number (tens of millions) of random expressions trying to explore the important variables (small and large floats. Floats that cause funny rounding. Long sequences of almost the same precedence etc).

We got all three languages to agree to full precision for almost every case but in each run there were stubbornly alway a few dozen we couldn’t get to match for non obvious and seeming random reasons.

This was a prototype that never saw real production use and we spent a couple of weeks on it… it always bothered me that we never understood that last 0.00001%


Could it be that ASP (native to x86) was using x87 floating point in its default 80-bit mode, whereas Java (trying to get results more consistent cross-platform), had switched it to 64-bit mode?


Couldn't you have isolated the expressions that resulted in differences? That should have allowed narrowing it down to a minimal example...


You can't hold a language responsible for "mistakes" if you don't understand how the language works. Quotes intentional.


Different languages have different rules around some math operators, notably around order of operations and how % and / handle floating point values. There are much simpler expressions that will yield differences.

Also, you appear to be using Python 2, the results are different with any modern version of Python.


And here are the two differences I think your expression is hitting on:

    Expression: 750/300
    Python 2.7: 2
    Python 3.9: 2.5
    C: 2.000000
    SQLite: 2
    Node: 2.5
    Java: 2
    Go: 2

    Expression: -123%10
    Python 2.7: 7
    Python 3.9: 7
    C: -3.000000
    SQLite: -3
    Node: -3
    Java: -3
    Go: -3
There might be more. Though, there's a ton of needless obfuscation in your expression.


Certainly not floating point results in C for either of those.

All literals shown (750, 300, -123 and 10) are of type 'int', so the results would be, too.


Yep, I coerced the result to floating point in my harness.

If anyone cares to look: https://pastebin.com/7tLrs28V

I'm probably done, it's a mildly interesting side quest, but really, this is just reinforcing that different languages do things differently.


It's hardly incorrect that different languages have different semantics, especially considering the mix of types and use of % in that expression. You could just as well complain that they don't all print floating point numbers in the exact same way.


A big part of the oft-overlooked notion of Information Architecture are the twin concepts of Source of Truth and System of Record. Many systems mix or conflate the two or don't consider them at all, other than subconsciously when wrestling with issues of code duplication (split SoT).

If the result of a calculation is that important to the functioning of your code, then having three implementations in multiple languages is just madness. You need to pick one to believe, or constantly deal with random catastrophes. Personally I enjoy very much not being woken up at 7 am EST to solve production issues. Almost as much as I hate other people getting kudos for problems that we should have not signed up for in the first place. Arsonist-firefighters exist in every dark corner of the programming world, and in much greater numbers than actual arsonist-firefighters.

From a technology selection standpoint, all of this info is extremely important, because it will inform both my choice of tools and my immediate task list to deal with any answers I don't like. But if you're wrestling with this every day, you've already fucked up.


And after removing all modulo operations:

====

EXPR = (((((((788)*(8.46))))+8342*1.803-1))*4186.4*(15))*(((22/((7530/((2)*(((((25))-421))))))*597)+7283.8-9.60+167))+(8871)

SQUILU = 8515287402650.3

SQLITE = 8515287402650.34

POSTGRESQL = 8515287402650.347200

D = 8.51529e+12

JAVA = 8.515287402650345E12

MYSQL = 8259816920501.086

JAVASCRIPT = 8259816920615.111

PHP = 8259816920615.1

LUA = 8259816920615.1

AMPL = 8.25982e+12

GAMS = 8.25982E+12

RUBY = 7701542593121.873

PYTHON = 7.70154259312e+12

====


Julia 1.3.1: 8.259816920615111e12


PYTHON3 = 8259816920615.111


bc -l = 8259816920615.11375936705619635555


This kind of thing can bite you even when operating purely on integers. E.g., for a long time (it's been fixed for over a dozen years by now though), Python implemented x%n with an intermediate cast to float, with predictably bad results on inputs larger than typical test cases. I had the damnedest time tracking down the culprit since I assumed it had to be serialization, hashing, or one of the standard culprits --- not that integer math was broken.


Haskell: =====

(%) = Data.Fixed.mod'

expr :: Double = 8.036090802426098e12

expr :: Rational = 630330872315297185317 % 78437500

fromRational (expr :: Rational) :: Double = 8.0360908024261e12


And making all operands to modulo operator "%" integer:

=====

EXPR = (((((((788)*(8.46))))+8342*1.803-1))*4186.4*(15))*(((22%284/((7530/((2)*(((((25))-421))))))*597%2663)+7283.8-9.60+167%((3))))+(8871)

D = 8.29038e+12

JAVA = 8.290383058308305E12

MYSQL = 8034912576159.046

POSTGRESQL = 8290383058308.307200

SQLITE = 8290383058308.3

SQUILU = 8291561284461.3

JAVASCRIPT = 8034912576273.071

PHP = 8035491468054

AMPL = 8.03491e+12

GAMS = 8.03491E+12

PYTHON = 1.11064580608e+13

RUBY = 11106458060797.121

LUA = 11664732388290

====


Try to edit your post and add a \ before each * so it is not interpreted as italics here.

Have you tried to simplify it? My guess is that the problem is not in the last "+(8871)".



For Go, we get compilation error

./prog.go:8:129: invalid operation: 167.864 % 3 (operator % not defined on untyped float)


Julia 1.3.1: 8.036090802426098e12


In BYOND (Dream Maker/.dm):

8035491119104

(single floats on all numbers, unsure of negmod behavior)


You couldnt come up with a simpler example? Youve got at least 12 operations there. Pretty much a single division operation wouldve worked about the same


And that is why people still use FORTRAN.


Using https://www.onlinegdb.com/online_fortran_compiler

FORTRAN = 8.51528699E+12

====

Program Hello

Print *, (((((((788)*(8.46))))+8342*1.803-1))*4186.4*(15))*(((22/((7530/((2)*(((((25))-421))))))*597)+7283.8-9.60+167))+(8871)

End Program Hello

====


The literal constants in this code are not in double precision and the results will not be accurate up to 64bit representation. Also, Fortran compiler optimizations can change the order of computations to generate the result faster. This does not invalidate the final outcome, but not necessarily yield the same result. Fortran has the most precise, exact, and portable method of literal constant specification among all programming languages.


These are some shocking differences. How did you come up with this expression?


After looking for a text generator for grammars I found this https://github.com/dmbaturin/bnfgen and then to try understand the underlying algorithm I came out with a C simple program for a simple expr grammar see it here https://github.com/dmbaturin/bnfgen/issues/2#issuecomment-89... and also a thread on sqlite forum here https://sqlite.org/forum/forumpost/6885cf9e21




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: