My First Production Bug
robinverschueren.com
robinverschueren.com
float ts = 1 / FRAMERATE;
This blindly assumes that FRAMERATE is a floating-point constant, but there is no reason to assume it is; in theory, other parts of the code could depend on FRAMERATE being integral. The code should be written in a way that ensures conversion to float happens before division, for example: float ts = 1.0f / FRAMERATE;C++ gets this wrong for maximum drop-in compatibility with C, the classic "New Jersey Style" programming language where simplicity of implementation is prized over simplicity of use or correctness.
Even in Go we had a stupid problem where default json deserializer creates floats (when deserialized into any) and the number was high enough int64 where it lost precision.
I mean, we can go at it all night long what pitfalls await in what language. Perhaps Rust is safest with its own pitfalls where you just can't do it safely (looking at you BST and use of Arc).
Programming is full of such traps and only inexperienced engineers in a language would make such a mistake. This includes engineers with 20+ years of 1 year experience.
Careful though, if you don't have :Float on ts it still gives 0.
The post I was replying to mentions that this sort of problem does not exist in "better languages" but my point was that it does.
The problem here is that the code assigns an integer (result of division) to a float which is implicit "upgrade" in languages like C and C++ and required to be explicit in newer languages like Golang and Rust.
My point, which I seem to have failed to make, is that careless programmers would (and do!) make a silly thing like:
v := float64(operand1 / operand2)
just to satisfy the compiler error.
A common mistake for junior programmers but unforgivable (for some interpretations of unforgivable :) ) one for a senior.
What does this even mean?
First of all, several languages have distinct "integer divison" and "floating point division" operators, so there's no sense in which integer is "preferred" in those languages, they're unrelated operations.
Even allowing for your ignorance of such languages many modern languages do not have untyped constants, they're an attractive nuisance. If you don't have untyped constants then even if you're relying on implicit typing for constants (which I also don't like) you trip a mistake in the original expression anyway which is now unalike.
This mistake only occurs in a language with all of:
1. A single division operator despite two distinct operations
2. Untyped constants
3. "Promotion" so that type mismatches just do something unexpected and compile anyway.
We have two arguably distinct operations so we might want two operators, but you have instead invented a plethora, seeming to understand that somehow the operators ought to depend on the type, which is part of the original mistake.
(5 // 2 == 2) whereas (5 / 2 == 2.5) those are two distinct operations
In many places Rust for example chooses to provide multiple operations but to only grant the most C-like operation an operator, e.g. (-15_i32).div_euclid(2) is -8 that's Euclidean division but (-15_i32) / (2) is -7 as you'd likely expect in C
I tend to prefer the casting, because I had spending a bunch of time debugging 0s and NaNs, but sometimes it makes things look unnecessarily ugly and hard to read.
I don't know about other languages, but Python just lets you divide a float by an int, or vice versa, and it just always produces a float. Seems the most obvious thing to do.
[1] https://godbolt.org/z/vE9n14a54 [2] https://godbolt.org/z/aPa3P1Grc
Implicit conversion is always a mistake. The price in terms of reduced clarity and extra mistakes is too high for the marginal convenience of less typing.
I feel the same for boolean coercion even, which I know is more controversial than some of the really stupid C promotions, I do not believe in "truthiness". There's only one false, it's the constant false, it's not 0 or "" or 0.0 or an empty array or a null pointer or a billion other things, it's just itself and nothing else.
I don't see the "footgun". C and C++ allow you to perform divisions by integers. If you don't specify the decimals, it understands that they are integers. It's how it's meant to be. In my opinion, calling that a "footgun" is like saying using single quotes for characters is a "footgun" because someone could interpret them as strings. That's just not understanding the language.
I think Python 3 did the right thing by having 1/3 equal 0.333 (a float) rather than 0. It's more intuitive for the / operator to always do standard division and when you want integer division then you use the // operator instead. It's more consistent than having / return a completely different result depending on whether one of the operand happens to be 3 instead of 3.0
I would expect that no C or C++ programmer would find it intuitive
My guess is that if instead C and C++ had two operators here you'd find C and C++ programmers fiercely defending this reality as the only correct choice.
The tribalism particularly in C++ is very strong. There are a handful of accepted topics for disparagement, such as the std::vector<bool> specialisation but outside that any questioning of orthodoxy is not well tolerated.
1. Giving the integer and floating point division operations different operators. If we designate // as specifically the integer division operation then this bug never arises.
2. Don't have untyped constants. If the old constant had been a floating point type, the erroneous change jumps out because we're obliged to write that we now want an integer type. Unfortunately the pre-processor is just text mangling, so this constant had no type as far as C is concerned. Odin is uncommon in modern languages for having untyped constants, Ginger Bill can probably explain why he thought that's a good idea but I can't defend it.
3. Forbid coercion/ promotion of numeric types (perhaps as part of forbidding silent type conversions in general) so now the original expression won't compile anyway.
> It's more consistent than having / return a completely different result depending on whether one of the operand happens to be 3 instead of 3.0
I'd argue that you should be able to learn a few relatively simple rules that are baked into the language, and which don't change. That's not much to ask considering the value you get out of it.
On the other end of the spectrum, you have lots of people vigorously defending their manually overloaded arithmetic operators, acting on their custom types. And to have class methods overloaded statically (depending on argument types) and dynamically (virtual dispatch). I'm assuming you're not part of that group?
Not saying that it isn't a footgun, though. My recommendation is to turn on warnings and you should be able to catch most of mistakes early.
a / b
"Never" means "integer division" in regular math, so programming languages overloading it to sometimes mean integer division is a common suprise to newbies - especially when it's based on the types of `a` and `b`, which might not be determinable merely from reading the current file. Also, it's not equivalent to: a\b ≡ ⌊a/b⌋
As you might expect ( https://mathworld.wolfram.com/IntegerDivision.html ), but instead some round-to-zero nonsense that makes most uses of it on signed integers a bug IME. Some languages give integer division it's own syntax... others overload `a / b` to also mean path concatination.And while it's not on my top 100 list of C or C++ footguns, it is one of those things that occasionally helps lead to a facepalm-inducing moment when even professionals have a brain fart and fail to consider it's overloaded behavior... or misremember the type of `a` or `b`... or change the type of `a` or `b`.
> That's just not understanding the language.
Learning the footguns and how to avoid them is an important part of understanding any language. That doesn't mean they aren't footguns, just that they can be (partially) ameliorated.
Most of C++ choices come with both positive and negative tradeoffs, despite what Rust users will say.
Of course, the real fix is to use a proper constant.
float ts = 1.0f / (float)FRAMERATE;
I would still go further in modern times (C++23) and use the compiler to ensure fixed width types [1]: #define _float std::float32_t
_float ts = 1.0f / (_float)FRAMERATE;
Annoyingly though printing these numbers requires a cast which is not ideal.Granted it was 2013, don't sweat yourself.
The code reads correct, compiles just fine, and it runs! No clear error to hone in on.
These are some of the most difficult bugs with the "omg that's so dumb" response out the otherside. When everything appears correct, stepping through the data can take a long time because we know something is wrong but don't know which layer it's at.
Don't worry, I don't. Funnily, recently something very similar happened.
At work, there was this C++ class that had a 'void reset()' member function. Of course, at some point it was used with std::unique_ptr and we got some SIGSEGV. Took me a while to figure out that the '.reset()' was called instead of the '->reset()'.
I think these 'omg that's so dumb' bugs are just part of programming.
"The year was 2013 and I was smack in the middle of my master’s degree. Coding-wise, we were taught Fortran95 and not much else"
I'm grateful for the more theoretical courses that I attended instead.
The fastest growth I experienced was while working at an internet startup around '00.
I only recently got a CS degree just to check off the box. I enjoyed it, and I thought the material was pretty good. Some things were explained very well, other things could've been better, but overall the theory side and some algorithms were pretty ok.
On the programming side... Let's just say I'd rather hire anyone (a teenager, or 60 year old) who enjoys writing code than someone with just a CS degree and no ack for programming.
College, imo, is the wrong vehicle for knowledge transfer/acquisition for a lot of subjects. University Apprenticeship is the way to go.
#define FRAMERATE = 100.0
Should probably be: #define FRAMERATE 100.0I think you also meant the predict quantities were zero, not the update!
Pretty much every C textbook I can think of now is full of #defines for constants. I, too, am (de)formed by this.
The blog post was written with C++ in mind, but I just learned that C now also has constexpr: https://en.cppreference.com/w/c/language/constexpr.
Time to update those books (:
And `static const` by itself is not a good alternative, because constants can't be used in a lot of places, for example when declaring the size of an array.