16 ms·
My C code works with -O3 but not with -O0
- ginko 7y ago>I am still looking for a better way to check, if a double will convert cleanly to an integer of the same size or not. I'd say the cleanest would be to decode exponent and mantissa, check if the exponent is within the 64-bit limit of long, then check if there's any bits set below the decimal point. (+plus some extra care for two's complement negative numbers) The problem with this is of course that this would be platform dependent.
- clarry 7y agoI'd say the correct way to is to use lrint or lround and check for errors the standard way.
- hannob 7y agoThis may be an instance of "you should really know the gcc/clang sanitizers and use them to test your code": clang test.c -O0 -fsanitize=undefined ./a.out [...] test.c:17:12: runtime error: 9.22337e+18 is outside the range of representable values of type 'long' Interestingly gcc doesn't throw that warning.
- htfy96 7y agoI Second this. Most weird behaviors in C++ today can be detected by ASAN and UBSAN. There also exists low-cost random-sampling-based ASAN implementation that can be enabled in production: Google uses GWP-ASAN for all server-side applications as well as Chrome on Windows/Mac. See https://www.youtube.com/watch?v=RQGWMLkwrKc https://www.youtube.com/watch?v=RQGWMLkwrKc for details.
- pjmlp 7y agoAccording to most surveys, they aren't used as much on real life. Here 14%, https://www.jetbrains.com/lp/devecosystem-2019/cpp/ https://www.jetbrains.com/lp/devecosystem-2019/cpp/ Here 40 - 55%, https://www.bfilipek.com/2019/12/cpp-status-2019.html https://www.bfilipek.com/2019/12/cpp-status-2019.html At CppCon 2015 or something, at Herb's question during his keynote, about 1% of the audience as per his comment on the video.
- htfy96 7y agoThis looks interesting. I was wondering if it's due to poor build tool support. In real life I found rr + sanitizer as a strictly better choice than plain gdb.
- tyingq 7y agoDoes that verifier have a strong possibility of false positives? I'm curious why C compilers have such a strong history of making reasonable checks optional and hidden behind a bunch of switches.
- jzwinck 7y agoUBSan and ASan have essentially zero false positives. They do point out undefined behavior which happens to work on your platform, but at the very least those are still portability bugs.
- Liquid_Fire 7y agoThis is a runtime check and so has a (small) performance overhead.
- hannob 7y agoNo, actually the false positive rate of these flags is practically zero. (I'm not sure if it's 100% zero, but I used those extensively, reported many bugs and every time a developer told me "this is a false positive" they were wrong.) The reason they aren't enabled by default is that that's not what they're designed for. They have a significant performance impact, you can't enable them all at once, they conflict with other security features and they may introduce security issues. These are developer features. They aren't there to run your production code, they are there to test during development and bug finding.
- saagarjha 7y agoHere's one that I encountered that I cannot figure out: $ g++ -x c++ -fsanitize=undefined - #include <iostream> int main() { int *a = nullptr; std::cout << std::addressof(*a) << std::endl; } ^D $ ./a.out <stdin>:5:29: runtime error: reference binding to null pointer of type 'int' 0
- lallysingh 7y agoDoesn't addressof() take a reference?
- gameswithgo 7y agoor just start moving away from using c. the last five years have brought nice alternatives like zig and rust
- pjmlp 7y agoNot only the last 5 years, but I digress.
- Chlorus 7y agoYeah, it's kind of weird how little attention some of C's (historical)competitors in the systems programming space get. You'd think to see a lot of comparisons to whatever Ada* or Pascal would do in the same context. * Probably because the Ada practitioners are all trapped in Scifs somewhere in the inner mantle of the Earth.
- 0xdead 7y agoNo. I'd rather learn how to use my tools (that I have invested years to be comfortable with) properly than starting over.
- adamnemecek 7y agoC is not very productive.
- smabie 7y agoMaybe, but it’s super fun and elegant. C and Scheme are at the top of my “most elegant” list. Though, of course, for different reasons.
- erik_seaberg 7y agoI would have trouble convincing myself I'm capable of writing C that doesn't blow up randomly, when everyone else has been failing at that for my entire career and more.
- gnulinux 7y ago
- mulle_nat 7y agoThat's true. I am using memory sanitizers in my workflow, but I haven't been using the `undefined` sanitizer. This could have saved me a day worth of effort.
- ndesaulniers 7y agoJust yesterday I had upgraded the version of clang used to compile Android's emulator. It got reverted due to some post submit test failing suddenly. The test case was shifting values in a byte array into one value but the LHS of a left shift didn't have enough bits to represent the shift, which is explicit UB. The statement had multiple shift operations (and other sub expressions with templated types) so it wasn't immediately clear that was the issue. -fsanitize=undefined found it immediately. "Spot the UB" is seemingly becoming my pastime.
- yuriko 7y agoBased on my experience, this title is a strong hint that some undefined behaviour is triggered.
- CGamesPlay 7y agoHow did you decide that "the method works for LONG_MIN"? Did the method return the expected output of false? Because it really seems like the code is working correctly on `-O0` and incorrectly on `-O3`...
- eMSF 7y agoWhy would you expect the output to be false? On any typical system around (with 8-byte longs), LONG_MIN has the value of -2^63 which (converted to a IEEE double-precision float) passes the function's checks just fine -- even if the values around it don't.
- CGamesPlay 7y agoAh, my mistake. Still, seems like the -O0 is actually what's correct and the -O3 is reporting an incorrect answer.
- ThreeFx 7y agoA precise integer value is only guaranteed to be representable losslessly in a double if it is up to `64 - 1 (sign) - 11 (exponent) = 52` bits in magnitude. This should be fairly obvious with knowledge about how floating point numbers are represented internally IMO. Edit: Be more precise about what can be represented.
- yoz-y 7y agoI've seen quite a lot of errors stemming up from assumptions about floating point numbers. Not sure there is a good way of handling this in the end, except exert caution. Even basic assumptions like f + 1 > f will have this issue.
- magicalhippo 7y agoThe problem is that in most languages, to novices floating point numbers swim like a duck, quack like a duck, but it turns out they're alligators. A good way to get around this is reading https://floating-point-gui.de/ https://floating-point-gui.de/ to weed out any preconceptions, but yeah it's difficult to steer novices there without them stepping on one of the pitfalls first.
- stephencanon 7y agoNote that f + 1 > f can also fail for integers in many languages (e.g. it does not hold for signed integers in C or C++, because the behavior is undefined when you add 1 to INT_MAX, and unsigned integers always wrap around). This particular gotcha is not unique to floating-point.
- nayuki 7y agoYes, signed overflow can make f+1 > f be false. However in C/C++, because signed overflow is undefined behavior, the compiler is perfectly allowed to simplify the expression f+1 > f to always be true.
- stephencanon 7y ago
- mojuba 7y agoif( ! fits) Why this (constently) terrible formatting though? Never seen anyone using this style.
- petee 7y agoGotta say, without seeing it in context, it's flat-out clear what it means and non ambiguous
- mojuba 7y agoSure, but it's anti-mathematical let's say :) Left and right brackets should have symmetrical formatting. Never seen a style with such asymmetry, it's pretty odd.
- deleted 7y ago[deleted]
- inetknght 7y agoAhh you ought to see my style. I've been told it's unique and quite ugly. Linters tend to very much dislike it. Nonetheless my style has a purpose to me and I'm sure so does the author's.
- mojuba 7y agoI think glueing punctuation to the previous word but always having a space before the next one makes the words more readable. Probbaly makes sense although you always need research to back up such claims. For example there's research that says snake_case is more readable than camelCase (and yet most languages encourage camelCase for some reason)
- rootlocus 7y agoThat's perfectly fine if you're working alone.
- pjc50 7y ago> When something very basic goes wrong, I have this hierarchy of potential culprits: I don't know if this is supposed to be a joke or part of the setup for an explanatory post about undefined behaviour, but that list is in exactly the wrong order.
- scoutt 7y agoI agree, but I'd also say that silicon bugs are rarer, so I put them at the end of the list.
- syockit 7y agoI'd say just put volatile and be done with it. Now your -O3 will also break, but at least it's consistent with -O0 :p
- dfranke 7y agoClear your floating point exception register by calling feclearexcept(FE_ALL_EXCEPT). Convert to long by calling lrint(rint(x)). Then check your exception register using fetestexcept(). FE_INEXACT will indicate that the input wasn't an integer, and FE_INVALID will indicate that the result doesn't fit in a long. Edit: check for me whether just calling lrint(x) works. The manpage doesn't specify that lrint() will set FE_INEXACT, but it seems weird to me that it wouldn't.
- pjc50 7y agoI had no idea this feature existed! Does it behave usefully in a multithreaded context?
- dfranke 7y agoI think it uses thread-local storage like errno does, but I'd have to verify.
- clarry 7y agoYes it does. N1570 7.6: > The floating-point environment has thread storage duration. The initial state for a thread's floating-point environment is the current state of the floating-point environment of the thread that creates it at the time of creation.
- inetknght 7y agoAs someone who's had to read C and C++ code using `double`, it's been a few years since I've heard of `feclearexcept` and how important it is. Great, thanks, now I have to go back and restart some of those code reviews I've been doing of certain third party matrix math libraries...
- clarry 7y ago> The manpage doesn't specify that lrint() will set FE_INEXACT, but it seems weird to me that it wouldn't. Annex F: The lrint and llrint functions provide floating-to-integer conversion as prescribed by IEC 60559. They round according to the current rounding direction. If the rounded value is outside the range of the return type, the numeric result is unspecified and the ''invalid'' floating-point exception is raised. When they raise no other floating-point exception and the result differs from the argument, they raise the ''inexact'' floating-point exception.
- mokus 7y agoThe title is actually wrong - the -O0 version is correct, the -O3 version is not (despite giving the output the author expected). Casting the value to double ends up converting the long value 0x7fffffffffffffff to the nearest double value: 0x8000000000000000. As the -O0 version CORRECTLY reports, this does not round-trip back to the same value in the "long" type. Many other values, though not all, down to about 1/1024 of that value (1 / 2^(63-53)) will also fail to round-trip for similar reasons. Unless my coffee-deficient brain is missing something at the moment, it should be the case that any integer with 53 bits or fewer between the first and last 1 bit (inclusive) will roundtrip cleanly. Any other integer will not. Edit: fixed a typo above, and coded up the idea I expected to work and ran it through quickcheck for a few min, and this version seems to be correct ('int' return rather than bool is just because haskell's ffi doesn't natively support C99 bool): #include <limits.h> int fits(long x) { if (x == LONG_MIN) return 0; unsigned long ux = x < 0 ? -x : x; while (ux > 0x1fffffffffffffUL && !(ux & 1)) { ux /= 2; } return ux <= 0x1fffffffffffffUL; }
- deleted 7y ago[deleted]
- gok 7y agoOP wants the opposite; they're converting from double to long.
- Someone 7y ago“Unless my coffee-deficient brain is missing something at the moment” Add enough zeroes, and you’ll run out of exponent range.
- raverbashing 7y ago> converting the long value 0x7fffffffffffffff to the nearest double value: 0x8000000000000000 From what I understand from the spec it should be the nearest in value, no? Not the nearest in memory representation.
- eMSF 7y ago
- deleted 7y ago[deleted]
- heftig 7y agoSSE xmm registers might be 128 bits wide, but the precision is still 64 bits. The additional (high) bits are zeroed out. What you're seeing is not excess precision due to wide registers but excess precision due to optimization and constant propagation, which means GCC calculates a fast path for (argc == 1) that doesn't round correctly and ends up with "it fits". Interestingly it does optimize to the correct "doesn't fit" with -mfpmath=387 -fexcess-precision=standard, so I guess this is a bug in how GCC treats SSE math. The sanitizer (-fsanitize=float-cast-overflow) also notices the problem.
- pif 7y agoUnfortunately, the internet is full of lousy programmers who don't understand the difference between "my code works by accident" and "my code works".
- femiagbabiaka 7y agoRude and unnecessary. Now what could be a learning experience is diminished. Saying nothing is free.
- AstralStorm 7y agoNecessary. Some people, especially in management think that something which works but does so by exploiting accidental interaction of the build environment is acceptable, and then the blame falls on the person who makes the unrelated change that breaks the trashy code, so the thing is cast as blame on the guy who changes things rather than opportunity to fix latent bugs. It is a common attitude among non-programmers too and on people who think delivered fast matters the most.
- femiagbabiaka 7y agothat's a completely different situation than we're talking about here.
- metalliqaz 7y agoNot every driver can be Ayrton Senna. It is best to make automobiles work in such a way as to mitigate slopping driving if possible.
- microtherion 7y agoAm I misreading your comment, or are you holding up a driver who died by driving into a wall at 145mph as an example of how to do things carefully?
- gnulinux 7y ago"Lousy programmers" was very uncalled for, and is not productive. We're all learners.
- Aardwolf 7y agoThe most surprising thing for me out of this is that casting a high positive integer to double will output the nearest double which could be higher, not the highest one smaller than or equal to the integer value. Is there a way to get the largest double smaller or equal than some positive integer?
- NullPrefix 7y agoThe frst rule of floating point comparison is you do not compare them for equality, but instead calculate the difference and check if the difference is less than epsilon.
- ska 7y agoThis is super common advice but it is generally wrong, at least the second part. Comparing floats is more subtle than most programmers realize, and there really isn't a one-size-fits-all solution. Things to consider due to the nature of fp representation - comparing results close to zero is different (i.e. "is a small" needs a differen test than "are a & b close" - the distance between fp numbers depends on their magnitude, so comparing two large numbers to each other shouldn't have the same bounds as comparing two numbers near 1, say[1]. - if you aren't quite careful you can easily create tests where a == b but b != a , which can cause sorting issues, etc. Hand-wavily speaking if you want to do this "right", you should probably look at doing the analysis in ULP (units in last place) rather than directly on the floats. Don't do it for values near zero though. And have a fast path for differently signed values. The above doesn't even get into denormalized values. [1] note that what people usually mean for epsilon is the version of machine epsilon that is the difference between 1 and the next representable float above 1 [2]. So by definition this is smaller than the representable difference between any two numbers in larger decades [2] MS .NET somewhat confusingly defines Epsilon as the smallest representable normalized number.
- apta 7y agoThis is why using safe languages is important. Even frequent users of C and C++ end up making mistakes that are difficult to track down.
- syockit 7y agoI'm not sure what you mean by safe in this context. A language that forces type casts to be explicit? That throws runtime error when invalid/imprecise cast is done?
- g82918 7y agoTo save some time for new readers, the author is unfamiliar with floating point representation and thinks that a double precision number since it is 64 bits can hold any 64 bit integer and is somewhat confused as to what an xmm register can hold(they believe that it has 128 bits of precision instead of being able to hold 2 64-bit doubles, or 4 32-bit singles). They attempt to find the issue a few ways. The correct solution is not to convert any integer larger than 2^53 in absolute value since only integers that large can be successfully converted to double and back( aside from a few others that exist sparsely).
- 0xff00ffee 7y agoHoo boy. This is what happens the first time C programmers start to work with floating point and don't know the fundamentals. When you work with floating point, you need to remember you work with a tolerance to epsilon for comparisons because you are rounding to 1/n^2 precision and different floating point units perform the conversion in different ways. You must abandon the idea of '==' for floats. This is why his code is unpredictable, because you cannot guarantee the conversion of any integer to and from float is the same number. Period. The LSBs of the mantissa can and do change, which is why we mask to a precision or use signal-to-noise comparisons when evaluation bit drift between FP computations. He has the first part correct, < and > are your friend with FP. But to get past the '==' hurdle, he needs to define his tolerance, the code should be something like: if (fabs(f1 - f2) > TOLERANCE) ... fits = true. I was irked by his arrogance when he asks, "Intel CPUs have a history of bugs. Did I hit one of those?" First, learn about floating point, then, work on an FPUnit team for 10 years, and even then, don't assume you're smarter than a team of floating point architects, you're not.
- chungus_khan 7y agoAbsolutely. It's true that Intel CPUs have a history of bugs, and it's always a danger when working with any CPU. But unless you really, really know what you're doing you probably didn't, especially if you are doing something relatively basic and think you've somehow uncovered a new bug.
- thanatropism 7y agoHow is == still defined for floats (in typeful languages)? Fixed precision decimals should be on offer and suggested by compilers in response to an equality-on-floats.
- ambrop7 7y agoEach floating point value that is not a NaN represents a certain real number, -inf or +inf (this can be expressed in terms of the sign bit, exponent and mantissa). Knowing that, a == b when neither operand is a NaN is defined as equality of what they represent, in purely mathematical terms. Similar can be said for inequality operators. Be aware that +0.0 and -0.0 are different floating point values but represent the same real number, so +0.0 == -0.0 follows. People who say == means nothing for floating point and you always need epsilon checks are wrong, plain and simple. == is very well defined. Don't confuse the definition of floating point operations with common practices for using them effectively. You can iterate through all non-NaN values and check that successive ones are indeed not equal: #include <math.h> #include <stdint.h> #include <assert.h> #include <stdio.h> #include <inttypes.h> int main() { float x = (float)-INFINITY; uint64_t count = 1; while (x != (float)INFINITY) { float y = nextafterf(x, (float)INFINITY); assert(y != x); x = y; ++count; } printf("Found %" PRIu64 " floats.\n", count); return 0; } $ gcc -std=c99 -O3 a.c -lm -o a $ ./a Found 4278190081 floats. (a little bit harder for doubles) Interestingly, this only finds one zero (-0.0), hence the assert doesn't actually fail around zero.
- correct_horse 7y ago> When something very basic goes wrong, I have this hierarchy of potential culprits: the compiler buggy hardware OS vendor last and least me, because I don’t make mistakes :) I really dislike the arrogant programmer trope. Can we all stop?
- adammunich 7y agoI had something similar happen but with GCC generating an internal compiler error and just plain failing. Still haven't figured out why.
- mulle_nat 7y agoWith the help of your comments, I could now write the conclusion to my article. In a nutshell this is the solution: #include <math.h> #include <fenv.h> int fits_long( double d) { long l_val; double d_val; // may be needed ? // #pragma STDC FENV_ACCESS ON feclearexcept( FE_INVALID); l_val = lrint( d); d_val = (double) l_val; if( fetestexcept( FE_INVALID)) return( 0); return( d_val == d); } The article explains it in more detail. Thanks for the help.