lobsters score: 4 · tags: c
if you reeally need to check for underflow, and you need to use the libc function for some reason
Indeed: in many cases the standard lib is best avoided altogether (this was about errors, but there are locales too. And if you want your locale to influence how you interpret the comma and the point, you probably want to be explicit about it).
If proper floating point parsing isn’t too important, perhaps just do the most expedient thing that’s barely good enough. If it does matter though, you should probably roll your own. With any luck, there are good implementations out there you can steal from.
score 1 · canonical
It’s even more fun when you have to support multiple floating point formats. I wrote an 8-bit assembler that supports IEEE-754 (because there’s a ROM that implements it for the 8-bit CPU), and the Microsoft Binary Format (because the BASIC that comes with the computer is from Microsoft, and it came out prior to IEEE-754 was standardized). Fun times.
score 1 · canonical
I use
strtod, and I have never had the author’s requirement to detect that a small number has underflowed to 0.From my perspective, strtod returns an approximation: the floating point number closest to the number described by the digit string. If the floating point number closest to the numeral is 0, it returns that. I don’t have a use case where choosing 0 as the approximation needs to be handled differently from choosing some other float as the approximation. In what circumstance is this particular case an “error” when returning an approximate non-zero result is not an error?
I think the author has a specialized need that may not be encountered by most people. In these cases I just use a third party library, as the standard C library doesn’t cover all use cases for which you may need a library function.
score 1 · canonical
It’s also amusing that
floattointconversion in C is UB on overflow. It’s actually exploitable in LLVM. Rust had to inject range clamping into its float casts to prevent it.
score 1 · canonical
Yup, there are good, but also silly reasons for this. Most ISAs that have an FPU have some float-to-int conversion instruction but the rules are not the same (I think they are for anything that implements IEEE754, but C predated that and still doesn’t mandate it, though C23 lets you check at compile time if the storage representation is IEEE754 [which doesn’t necessarily imply the operations are: i386, for example, operates on 80-bit floats but can store them as 32- or 64-bit IEEE754 values]).
In theory, this is a good fit for ‘implementation defined’ but that requires the compiler to write down exactly what will happen. On most ISAs, this is possible but some older ones didn’t specify the behaviour and left it up to implementations, so the resulting compiled code would behave differently on different CPU versions.
C++ recently introduce a notion of ‘erroneous behaviour’ that fits this. Ideally, compilers diagnose on erroneous behaviour. If they don’t, it may give non-deterministic values but they are stable during a program execution and may not directly cause UB. I’m not sure if this was made EB in C++. It’s similar to uninitialised values: they are now EB in C++ and reading them will give an arbitrary value but branching on that value is guaranteed to enter either the if or else branch, whereas when they were UB it could branch anywhere (or crash or do anything else).
I am not sure if C introduced EB.
score 1 · canonical
I checked what IEEE 754 says, and I was surprised! I wonder if any languages do what it specifies.
The 2008 and 2019 editions have the following paragraph. The 1984 edition has the same effect but not so clearly set out in one place.
When a NaN or infinite operand cannot be represented in the destination format and this cannot otherwise be indicated, the invalid operation exception shall be signaled. When a numeric operand would convert to an integer outside the range of the destination format, the invalid operation exception shall be signaled if this situation cannot otherwise be indicated.
Java and Rust specify NaN converts to 0 and overflow saturates when these situations cannot otherwise be indicated.
More entertainingly,
A language standard that permits implicit conversions or expressions involving mixed types should require that these be implemented with the inexact-signaling conversion operations below.
I tend to think that when a specification has a “should”, it needs a clear discussion of the considerations for when an implementation can disregard the “should”. Even more so when the “should” is widely ignored by existing implementations, in which case the rationale needs an honest explanation of why the spec and the implementations diverge.
score 2 · canonical
I checked C23, and nope, EB (erroneous behavior, I had to look this up) is considered UB (undefined behavior) per 3.5.3.
score 1 · canonical
This sounds interesting. Do you happen to have an article-link with more details handy?
score 1 · canonical
Sometimes I get tired of working in higher-level languages like Python and I feel inspired to try my hand at something lower-level and closer to the machine. Go and Java are too corporate, Zig and Odin and Hare are too trendy, Rust is too industrial and C++ too Lovecraftian.
“I know”, I say, “I’ll use C - it’s been around for fifty years, it’s small and simple and got to be polished to a sheen by now.” Then I read an article like this one.
Maybe I need to look more into Forth…
score 1 · canonical
“I know”, I say, “I’ll use C - it’s been around for fifty years, it’s small and simple and got to be polished to a sheen by now.” Then I read an article like this one.
Which, you will note, is not about C. It’s about its standard library.
I know, I know. In practice a programming language isn’t just syntax and semantics, it’s also the standard library, the tools, the third party libraries, the package managers, the community, the entire freaking ecosystem. With C however, you can save yourself quite a bit of trouble if you avoid the standard library as much as you can, be extremely picky about your dependencies, then vendor and statically link pretty much everything.
Many depressing articles would suddenly be irrelevant to you if you did that. In this case, all you have to do is parse those numbers yourself. It’s work, but at least you can escape most of the madness.
score 1 · canonical
I think there’s a lot to love about the idea of C, but its reality is much messier and more annoying.
Being around for fifty years doesn’t automatically mean it get polished to a sheen, because C is not one single artifact that one single group worked on for fifty years. Instead, it’s got fifty years of weird legacy cruft accumulated from divergence between various implementations, concessions to historical constraints that no longer apply and (yes!) human error.
This is kind of by design. The process by which C is developed (including the ANSI/ISO standardization processes) is very focused on backwards compatibility. That makes it an amazingly stable environment, there is tons of decades-old C code being used unchanged in production with modern compilers. However, that also means that all the really bad parts can’t ever truly be fixed. If you want something that looks like a perfectly cut and polished gem after fifty years, you need a process that is able to sand off some rough edges once in a while. I think we can have a systems programming language designed through such a process, even for decades, but it can’t also simultaneously have the kingpin role that C plays for compatibility and interoperability.
score 1 · canonical
Honestly, assembly is fun…
That said, don’t avoid trendy things only because they are trendy. I sympathize though.
score 1 · canonical
