It's quite astonishing how the historical inability of commonly used programming languages (from FORTRAN all the way to, say, Java/C#) to ergonomically return two values from a function call has caused people to invent all kinds of insane APIs/contracts.
[double x, errno_t errno] = strtod(s, nullptr);
if (errno != 0) {
// ...
}
[x, _] = strtod(s, nullptr); // When you're certain the error is impossible or just don't care
Instead, we're stuck with "check if the return value is -1 and if it is, check the global variable errno" at best, and with "the function doesn't really reliably reports its errors so don't feed it malformed data, idc" at worst.
C++/C#/Python absolutely do have ergonomic ways to do this (and have for decades by this point...) but programmers continue to use sentinel values. It is a bit puzzling.
I'd argue it's as much a mentality as language barrier; high assurance code would typically always return an error code, and use out parameters rather than return values. This has been possible for as long as C has existed (I imagine), it's just not common practice.
I have a pet theory that new languages benefit at least as much from an opportunity to reset "common practice" / defaults as they do syntax/semantic improvements.
Heck, even using multiple returns for this feels like a kludge to me at this point, when more advanced languages like TypeScript, Rust or Zig offer sum types/tagged unions.
Basically all commonly used languages support this nowadays. Even ones you might not expect like C++ and C# have added it. The only major exceptions that come to mind are C and Java.
C number parsing functions are fundamentally broken, since they are locale-dependend. There are many bugs when one parses a number assuming it has decimal point, but later on systems with locale different from one used on developer's machine it may be suddenly comma instead of point. So, one should avoid using them almost always and use std::from_chars (available in new C++ versions) or some other thirdparty library.
For good reasons. The VAX floating point specification was much worse, even if somewhat cheaper to implement.
IEEE 754 was one of the few standards that caused a significant progress of the standardized domain, because what is important for users had precedence over what is important for vendors.
Before IEEE 754, all floating-point implementations attempted to minimize the hardware cost, regardless of what aberrant behavior this would cause, which would confuse the naive users.
Even putting aside the ugliness of errno, this is pretty bad. I'm at the point with libc where I try to use as little of it as I can, though I'm not writing a lot of C these days
I would say there are 3 broad classes of libc functions
1) functions that you cannot implement in portable C - syscalls, signals, ABI specific things like longjmp and setcontext
2) functions that you could implement by yourself in portable C, but they are expected to play nice with the rest of the system - a lot of POSIX user stuff, dns management, dlopen, all the nsswitch mess. Can't think of any standard C examples because of the extreme portability requirements of C. Maybe the locale stuff, although it's usefulness is dubious.
3) functions that are there for convenience and sometimes compiler special-casing, like math, str*, <db.h> stuff that was thankfully removed from glibc
Most of #3 should be ignored. #2 is optional, especially if your software is for end users and not packaged by a distro. Sometimes it's safer to ship your own MDNS implementation for example rather than hope that the distro has sane defaults.
I'd much rather push for a world where we use a reasonable DNS/mDNS implementation on Linux rather than add to the zoo of bespoke variants. I have written code that tries to appease different DNS managers across different versions of them for different distros. We need to stop. Everyone believes that the common way of handling DNS is insane, so they create their own new way of doing it.
Sometimes I wish we could get to a timeline where the FPU format standards have something like Crockford’s DEC64 end up winning or evolving into the prevailing standard. Simpler & consistent math ought not to be this hard.
That doesn't detect overflows and underflows. Also changes based on locale, which is not thread safe (so you cannot set locale, convert, then unset locale).
That's a feature: Where did the string come from if not the user?
> you cannot set locale, convert, then unset locale
So what? You're not handling multiple locales: You're using the user's locale. If you're making a server, using anything but LANG=C or a utf8 variant is asking for trouble.
I think the point is it's absolutely possible to portably detect errors, some libc's strtod() just don't which ain't the same thing.
> That's a feature: Where did the string come from if not the user?
Many games and applications store floating point values in text file formats. This has caused bugs in those programs many times over because they didn't realise it was locale dependent and the data parsing broke in another locale.
Just don't make the errors, then you don't have to detect them.
When your app stops issuing these errors, then you won't have to write blog posts complaining that someone elses code can't handle your errors.
Definitely you should never be converting strings to floats ever anyways, see how I'm going on a limb here without any knowledge of your application, but then you know that I'm right because there's no reason why you would ever do that?
Still had something as sane as:
if (double.TryParse("5.x5", out var val)) { Console.WriteLine(val); } else { Console.WriteLine("err"); }
I have a pet theory that new languages benefit at least as much from an opportunity to reset "common practice" / defaults as they do syntax/semantic improvements.
errno is a worse mistake than null pointers in general
> the fact that the man page never makes any note of this being an extension ([…]) is really notable to me.
> this is completely undocumented on the linux man page.
The Linux man page maintainers are really friendly in my experience, so it's probably worth reporting this to them [0] [1].
[0]: https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/...
[1]: https://lore.kernel.org/linux-man/
https://www.exploringbinary.com/visual-c-plus-plus-strtod-st...
https://lemire.me/blog/2026/02/01/converting-floats-to-strin...
https://en.wikipedia.org/wiki/IEEE_754
https://sixlettervariable.blogspot.com/2007/05/vax-floating-...
IEEE 754 was one of the few standards that caused a significant progress of the standardized domain, because what is important for users had precedence over what is important for vendors.
Before IEEE 754, all floating-point implementations attempted to minimize the hardware cost, regardless of what aberrant behavior this would cause, which would confuse the naive users.
1) functions that you cannot implement in portable C - syscalls, signals, ABI specific things like longjmp and setcontext
2) functions that you could implement by yourself in portable C, but they are expected to play nice with the rest of the system - a lot of POSIX user stuff, dns management, dlopen, all the nsswitch mess. Can't think of any standard C examples because of the extreme portability requirements of C. Maybe the locale stuff, although it's usefulness is dubious.
3) functions that are there for convenience and sometimes compiler special-casing, like math, str*, <db.h> stuff that was thankfully removed from glibc
Most of #3 should be ignored. #2 is optional, especially if your software is for end users and not packaged by a distro. Sometimes it's safer to ship your own MDNS implementation for example rather than hope that the distro has sane defaults.
https://www.crockford.com/dec64.html
For extra reassurance, initialize foo to NaN or DBL_MAX or something, and verify that it is no longer equal to that value.
A trivial change does though: Just round-trip it.
> Also changes based on locale,That's a feature: Where did the string come from if not the user?
> you cannot set locale, convert, then unset locale
So what? You're not handling multiple locales: You're using the user's locale. If you're making a server, using anything but LANG=C or a utf8 variant is asking for trouble.
I think the point is it's absolutely possible to portably detect errors, some libc's strtod() just don't which ain't the same thing.
> That's a feature: Where did the string come from if not the user?
Many games and applications store floating point values in text file formats. This has caused bugs in those programs many times over because they didn't realise it was locale dependent and the data parsing broke in another locale.
Gilfoyle: Anything to do with errors is more your vibe"
https://www.youtube.com/watch?v=oyVksFviJVE&t=49
Oh, this one is easy.
Just don't make the errors, then you don't have to detect them.
When your app stops issuing these errors, then you won't have to write blog posts complaining that someone elses code can't handle your errors.
Definitely you should never be converting strings to floats ever anyways, see how I'm going on a limb here without any knowledge of your application, but then you know that I'm right because there's no reason why you would ever do that?