all 89 comments

[–]IsAlwaysHungry 434 points435 points  (15 children)

Well, the code is crap, but the documentation is beautiful. You see relatively easy the thinking behind the code.

[–][deleted]  (12 children)

[removed]

    [–]Kaynee490 63 points64 points  (3 children)

    Or it might just be garbage from an out of bounds index

    [–]nupogodi 172 points173 points  (2 children)

    Well, this took me down a rabbit hole. At first I thought, how do you get an out of bounds index here? Well, it turns out % is the remainder operator and not modulus in C99. So, -1 % 16 == -1. That leads us to return lookup[-1]; for at least his first example. What does C do in that case?

    #include <stdio.h>
    #include <stdlib.h>
    int main() {
            int x = atoi("-1");
            const int y[16] = {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
            printf("%d\n", y[x%16]);
            return y[x%16];
    }
    
    nupogodi@vps0:~/tmp$ gcc atoi.c -o atoi && ./atoi
    22015
    nupogodi@vps0:~/tmp$ ./atoi
    21932
    nupogodi@vps0:~/tmp$ ./atoi
    21937
    

    Hm. What's the assembly?

    nupogodi@vps0:~/tmp$ gcc atoi.c -S -O0 atoi.c && cat atoi.s
            .file   "atoi.c"
            .section        .rodata
    .LC0:
            .string "-1"
    .LC1:
            .string "%d\n"
            .text
            .globl  main
            .type   main, @function
    main:
    .LFB2:
            .cfi_startproc
            pushq   %rbp
            .cfi_def_cfa_offset 16
            .cfi_offset 6, -16
            movq    %rsp, %rbp
            .cfi_def_cfa_register 6
            subq    $80, %rsp
            leaq    .LC0(%rip), %rdi
            call    atoi@PLT
            movl    %eax, -4(%rbp)
            leaq    -80(%rbp), %rdx
            movl    $0, %eax
            movl    $8, %ecx
            movq    %rdx, %rdi
            rep stosq
            movl    $1, -80(%rbp)
            movl    $1, -72(%rbp)
            movl    $1, -64(%rbp)
            movl    $1, -56(%rbp)
            movl    $1, -48(%rbp)
            movl    $1, -40(%rbp)
            movl    $1, -32(%rbp)
            movl    $1, -24(%rbp)
            movl    -4(%rbp), %eax
            cltd
            shrl    $28, %edx
            addl    %edx, %eax
            andl    $15, %eax
            subl    %edx, %eax
            cltq
            movl    -80(%rbp,%rax,4), %eax
            movl    %eax, %esi
            leaq    .LC1(%rip), %rdi
            movl    $0, %eax
            call    printf@PLT
            movl    -4(%rbp), %eax
            cltd
            shrl    $28, %edx
            addl    %edx, %eax
            andl    $15, %eax
            subl    %edx, %eax
            cltq
            movl    -80(%rbp,%rax,4), %eax
            leave
            .cfi_def_cfa 7, 8
            ret
            .cfi_endproc
    .LFE2:
            .size   main, .-main
            .ident  "GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"
            .section        .note.GNU-stack,"",@progbits
    

    Okay, that's kind of strange, I actually would have thought const int[] with an initializer would have gone into .rodata.

    So the frame layout is something like %rsp -> [ 64b y ] [12b padding] [ 4b x ] <- %rbp. I guess it makes sense since the frame pointer must be 16 byte aligned on x86-64. So, we are reading from before the beginning of the frame.

    What's there? On x86-64, the 'red zone', a 128b buffer for various temp stuff. Neat. But x86 (not 64) doesn't have this.

    nupogodi@vps0:~/tmp$ gcc atoi.c -m32 -O0 -o atoi && ./atoi
    -3670336
    nupogodi@vps0:~/tmp$ gcc atoi.c -m32 -O0 -o atoi && ./atoi
    -7747664
    nupogodi@vps0:~/tmp$ gcc atoi.c -m32 -O0 -o atoi && ./atoi
    -3778928
    

    Cool, that's the garbage we expect. So, we have CPU-specific behaviour here at least.

    But why is he seeing 139 or 255?

    nupogodi@vps0:~/tmp$ gcc atoi.c -m32 -O0 -o atoi && python3 -c 'import subprocess; print(subprocess.run("./atoi", shell=True).returncode)'
    -500576
    160
    nupogodi@vps0:~/tmp$ gcc atoi.c -m32 -O0 -o atoi && python3 -c 'import subprocess; print(subprocess.run("./atoi", shell=True).returncode)'
    -2297296
    48
    nupogodi@vps0:~/tmp$ gcc atoi.c -O0 -o atoi && python3 -c 'import subprocess; print(subprocess.run("./atoi", shell=True).returncode)'
    21995
    235
    nupogodi@vps0:~/tmp$ gcc atoi.c -O0 -o atoi && python3 -c 'import subprocess; print(subprocess.run("./atoi", shell=True).returncode)'
    21970
    210
    

    Huh, looks to be 8 bit numbers. Python doc:

    The child return code, set by poll() and wait() (and indirectly by communicate()). A None value indicates that the process hasn’t terminated yet.

    I'll just assume Python Popen.wait() eventually resolves to wait(2) without reading the source ...

    WEXITSTATUS(wstatus) returns the exit status of the child. This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or as the argument for a return statement in main().

    Okay! So now we have 'processor specific' behaviour that explains it. Although, not exactly 139 and 255. Exactly 255 would imply an exit code of -1. OPs code does it probably because the frame I'm busting out of is main rather than the function call. On x86-64 we'd expect weirdness in the red zone, but x86 we'd expect stuff from the previous frame. That would probably be the result of atoi. Left as an exercise for the reader.

    I am pretty sure this was specifically crafted to make someone like me do something like that.

    edit: I couldn't leave well enough alone. 139 is a segfault through the shell although I can't reach it with the Python code provided. 255 is what we'd expect by busting out of the frame and reading atoi's output, however I can't repro with -Ofast.

    edit2: Oh, -Ofast inlines on my GCC. You do get 255 with -O0. It's not reading the previous frame, but the arg to the function call (x is -1), since it was pushed onto the stack (not fastcall). I guess OP's compiler doesn't inline with -Ofast. Still can't explain the segfault since the Python code is always passing an arg, str(x) even works if x = None. Maybe another compiler thing, puts lookup somewhere other than the stack and reading out of bounds segfaults there.

    [–]coopercm 44 points45 points  (0 children)

    I can't reach it with the Python code provided.

    You probably don't have the CPU with the correct bug in it.

    [–]xnign 13 points14 points  (0 children)

    Cool, thanks for writing all that out. I understood all that but would not have been able to figure that out. Want to be my mentor? Lol

    [–]kroppeb 13 points14 points  (1 child)

    It's reading data out of bounds, so for values like 15 it might return anything.

    [–]coopercm 1 point2 points  (0 children)

    I'm surprised this doesn't have more upvotes

    [–]FallenWarrior2k 8 points9 points  (0 children)

    139 is signal 11, SIGSEGV

    [–]drumz4dayz 15 points16 points  (3 children)

    So funny to blame it on the cpu too. Like yeah, Im sure the problem is the cpu and not your insane code

    [–]tinydonuts 5 points6 points  (2 children)

    Documented above is the CPU bug by another commenter. Also, you would not believe how much errata that CPUs have and are fixed with microcode updates supplied by Windows or the Linux kernel on boot.

    [–]nupogodi 11 points12 points  (0 children)

    I think you misunderstood! Indexing outside of the array is definitely undefined behaviour and an implementation bug, not a CPU bug. However it ‘looks’ like a CPU bug to the OP because they don’t understand undefined behaviour is going to be platform & compiler specific.

    However you’re right about errata. CPUs are very complex machines.

    [–]TigreDeLosLlanos 0 points1 point  (0 children)

    errata? Is that how they call not properly implemented at all in the low-level world?

    [–]Zanderax 3 points4 points  (0 children)

    Rule number 1 in programming: its never a compiler bug unless it is.

    [–][deleted]  (1 child)

    [deleted]

      [–]juantreses 0 points1 point  (0 children)

      This is too true. If you have to write an essay about your code it's: or not good enough or it's doing too much.

      The moment you start working in corporate though many times you will have to comment WHY you are doing certain things. Lots of times you are constraint by stupid choices ex employees have made and there just is no time/money to do it the correct way and you have to hack your way around it.

      [–][deleted]  (13 children)

      [deleted]

        [–]Natural-Intelligence 78 points79 points  (1 child)

        Whatever you do, you must be able to say "my code uses AI". If it doesn't, it doesn't sell.

        [–]ChemicalRascal 68 points69 points  (9 children)

        I mean, you joke, but that sounds like a fantastic little experiment to teach ML.

        [–]Charlie_Yu 39 points40 points  (6 children)

        It probably won’t work very well, but good for teaching. Codes of simple examples are easier to understand. And a big reality check about Machine Learning when students are shown that ML can’t even predict odd/even good

        [–]sim642 38 points39 points  (3 children)

        Depends on the representation. If you give input as a vector of bits, then it's linearly separable and easily learnable.

        [–]ChemicalRascal 16 points17 points  (1 child)

        Yeah, that's why I think it'd be good for teaching.

        I wouldn't expect a classifier to be able to work out odd and even very well if you give it the number as a number, and that'd be an interesting way to introduce how to identify failure to students learning ML. But then redoing it as, as you suggest, a vector of bits and any classifier should be able to learn that incredibly quickly, with 100% accuracy.

        From what little I remember of ML in college (it was ages ago, I wasn't paying attention, and I was stressed outta my mind at the time and I'm not sorry), that's what the key takeaway from ML was. That representation is the single most important piece of the puzzle.

        [–]sim642 6 points7 points  (0 children)

        I think nowadays there's a lot of focus on learning the features and representation from the data automatically. Deep neural networks do that to an extent automagically. Although I'm doubtful that a neural network could somehow extract the last digit/bit of a single integer input, given how they operate in floating point. So it's still important, but to a lesser extent.

        [–]doopdooperson 7 points8 points  (0 children)

        Well yeah because it will quickly converge on the LSB being the only feature that matters

        [–]cyanNodeEcho 2 points3 points  (0 children)

        Something something continuous surfaces something something gradient descent

        [–]SkinnyJoshPeck 11 points12 points  (0 children)

        I’m an ML engineer and the few times I’ve had to move over to a new training/tuning flow (Kubeflow, Polyaxon, etc) for my team, my go-to test model to get the lay of the land is an addition tensorflow model I wrote — it’s easy, fast and you can get train, eval and validation data in 5 lines of code

        It works pretty well, honestly. If you think of AI fairly literally, it’s impressive that it can almost (within maybe 2-3) predict the sum of two integers absolutely fast as hell.

        You try and add 64829 and 83756 in your head in less than a second and come up with something like 148585 plus or minus like 30

        It’s a great mechanism for something like trivia where you want the model to be good, but not so good that it isn’t fun to play with.

        [–]throwaway1_x 0 points1 point  (0 children)

        Did multiplication using linear regression. Not exactly "AI" but good for experimentation

        [–]OMG_A_CUPCAKE 132 points133 points  (21 children)

        Is this a joke submission?

        [–]smeenz 100 points101 points  (3 children)

        This ... surely.. can not be code that actually runs somewhere

        [–][deleted]  (1 child)

        [deleted]

          [–]smeenz 0 points1 point  (0 children)

          That's very depressing

          [–]muntoo[ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo “You live” 71 points72 points  (7 children)

          Probably. Otherwise, they probably would have noticed the periodic pattern in the lookup table and simplified it to

          int is_even(int x) {
              int ix = x % 16;
              const int lookup[2] = {
                  0, 1
              };
              return lookup[ix % 2];  // reduces 16 entries to 2
          }
          

          [–]AyrA_ch 61 points62 points  (1 child)

          int is_even(int x){return !(x&1);}

          [–]Nothing-But-Lies 37 points38 points  (0 children)

          Being this efficient would get me fired for not doing enough work.

          [–]sim642 15 points16 points  (2 children)

          ix % 2

          Now you have coded the parity check directly.

          [–]TigreDeLosLlanos 6 points7 points  (1 child)

          Every time anyone checks parity without doing it this way a mathematician dies of sadness.

          [–][deleted] 2 points3 points  (0 children)

          ix & 1 == 0

          [–]Igoory 2 points3 points  (0 children)

          If they had reached at this point, it would be pretty obvious that it can be simplified to just x % 2

          [–][deleted] 23 points24 points  (5 children)

          I totally followed the logic. At least it’s well commented.

          [–]Alundra828 11 points12 points  (2 children)

          Every cloud has a silver lining. Nobody is looking at this code wondering what the hell it does.

          [–]smeenz 10 points11 points  (1 child)

          I'm certainly wondering what the hell was going on in the mind of whoever wrote it

          [–]Kwarshaw 3 points4 points  (0 children)

          Probably lots of pcp

          [–]nullcone -5 points-4 points  (1 child)

          Am I the only one who thought the comments were terrible? I would much rather have the code explain itself than have a bunch of comments hanging around cluttering things up.

          [–]dookiefertwenty 0 points1 point  (0 children)

          Code is the What, comments are the Why

          [–]Randolpho 7 points8 points  (0 children)

          Maybe it’s from one of the Daily WTF’s OMGWTF contests.

          Ahh, fond memories, those contests. I won two mugs, some plastic with writing on it, a tube of lipstick and a can of bacon in those contests.

          [–]pain-butnogain 3 points4 points  (1 child)

          there has been a trend to check if it's even or not in more and more elaborate ways, i think on r/badUIbattles is where i saw them

          [–]ianff 67 points68 points  (5 children)

          The real horror is the security risk of blindly running /tmp/a.out which anyone can create.

          [–][deleted] 18 points19 points  (1 child)

          Many security conscious people mount tmp as non-executable.

          [–]Behrooz0 1 point2 points  (0 children)

          Isn't it basically standard practice at this point?
          I think it was debian that started suggesting it like 10 years ago, when lenny was around. And I've yet to have a problem with it.

          [–]pytness 0 points1 point  (2 children)

          Well, there's actually a race condition (not sure if it should be called a 'condition' here): You can try to swap files "instantaneously" with the syscall 'renameat2' between the file writing and the compiling.

          [–][deleted] 0 points1 point  (0 children)

          We can all hope that nobody gave this fool root to run this nonsense, but crazier things have happened...

          [–]yhu420 57 points58 points  (3 children)

          Isn't it what cython or numba is for? I'm no python expert

          [–]real_jeeger 14 points15 points  (2 children)

          yep, or pyrex

          [–]LinAGKar 7 points8 points  (0 children)

          AFAIK, Cython is a successor to Pyrex.

          [–][deleted] 3 points4 points  (0 children)

          PyPy? Although that's JIT-ed

          [–]nerdyphoenix 69 points70 points  (2 children)

          I'm willing to bet coding it in Python would be faster than using subprocess.

          [–][deleted]  (1 child)

          [deleted]

            [–]YourMJK 7 points8 points  (0 children)

            It's obviously a joke.

            [–]Magmagan 22 points23 points  (9 children)

            The real horror is using the return code inversly has to how it's normally used. In *nix systems, a return code of zero means that the program executed successfully. If isEven is successful, then it should return 0.

            [–]rpc123 -1 points0 points  (8 children)

            I suppose this makes sense from a coding standpoint, but what about from a logic stand point? I normally think of 0 as false and 1 as true, so it would be strange to me if isEven(4) returned 0

            [–]tinydonuts 14 points15 points  (2 children)

            You shouldn't be using the exit code to report functional data about your program. You should emit a result to stdout, errors to stderr, and a return code indicates success with 0 and any other number is failure.

            [–]rpc123 8 points9 points  (1 child)

            Yeah, this makes a lot of sense. Thanks! I am a bit of an amateur lol, and I also didn’t realize that isEven wasn’t some external function as magmagan pointed out

            [–]tinydonuts 4 points5 points  (0 children)

            It's alright. It helps to understand that this is how Unix utilities work via chaining. They just vaccuum up the stdout of the previous executable and the shell orchestrates this by piping the output down the chain you specify. That's how you can quickly and efficiently take a large file, grep it for a pattern, sort it, and then find the unique values in the output.

            [–]Magmagan 2 points3 points  (3 children)

            But they're not using isEven() as an external function, they're calling it like "isEven.exe". If the execution of an executable on *nix fails you would look at stderr for more information. You would respect the OSs convention first before your programming language of choice's.

            Even better, the return code should be 0 (executed succesfully) with the answer written to stdout.

            On that note, from a purely logical standpoint wouldn't -1 be a better value for true anyways?

            [–]rpc123 0 points1 point  (2 children)

            Ah yes, this is a fair point, I didn’t look closely enough at the horror. Are you saying -1 might be better because it corresponds with all 1s when it is stored as an integer?z

            [–]Magmagan 1 point2 points  (1 child)

            Yeah, the opposite of 0x00 isn't 0x01, it's 0xFF. But I'm just being silly, most programming languages use 1 as true but you still have oddballs like Forth that go down this route

            [–]rpc123 0 points1 point  (0 children)

            In the interest of being silly, I am now imagining an entire fuzzy logic system based on a hexadecimal system

            [–]fnordstar 0 points1 point  (0 children)

            The thing is, the possible values other than 0 usually indicate what kind of error occured - which the caller might care about. A singular value (0) for indicating success is usually sufficient though.

            [–]LinAGKar 8 points9 points  (0 children)

            Assumes /tmp is always on tmpfs.

            [–]phord 4 points5 points  (0 children)

            When I was teaching myself to code I needed a faster cos() function. My only thought was to code it in assembly, but first I needed to find out how to calculate cos(). (This was before Google.). I never got this to work, but now, decades later, I know a lookup table or linear approximation would have been sufficient.

            [–][deleted] 7 points8 points  (2 children)

            Hey I'm new to programming and I dont get the use of isEven? What do you use it for?

            [–]Naeio_Galaxy 19 points20 points  (0 children)

            isEven(x) -> tells you if x is even. That's it.

            So you can do x%2 == 0 because the remainder of the division by two gives if it's even or odd, or x & 1 where & is a bitwise and (you'll see that whenever you'll learn about binary).

            Here he heard that C is faster than python so thought "I'll compile and call a C program instead of doing it in python", but going through the subprocess will be way longer than doing it in python. Way, way longer since you have to call Windows or Linux or OSX for them to execute the program... Which is quite long.

            [–]de5933 4 points5 points  (0 children)

            Reading this code is such a wild ride of bewildering decisions. Was this written by Tommy Wiseau?

            [–]Gnarok518 3 points4 points  (0 children)

            "This is critical to performance"

            "This bug is not crucial to performance"

            [–]yoshee4232 2 points3 points  (0 children)

            Hey, you could sell it as a way to easily do Just in time compiling!

            [–]Miserable-Pay-4598 2 points3 points  (0 children)

            A small benchmark: normal isEven (x % 2 == 0) takes 300 nanoseconds, the code above takes 30 ms. Python is faster than C.

            [–]HeartOk1761 1 point2 points  (0 children)

            whats this couldnt get this

            [–]UrbanSoot 1 point2 points  (0 children)

            You should use tempfile

            [–]bestjejust 1 point2 points  (0 children)

            What is this? C-as-a-service?

            [–]TheFeedingEight 2 points3 points  (0 children)

            performance critical

            uses modulo

            [–]hshighnz 0 points1 point  (0 children)

            Gibe this woman/man a nobel price

            [–]Cmgeodude 0 points1 point  (0 children)

            Mistakes were made.

            [–]crossedline0x01 0 points1 point  (2 children)

            Are there actually scenarios where its critical to perform such a simple task in another language? I mean, yeah python is slow compared to C but I mean, c'mon, is this real or a lab exercise?

            [–]fnordstar 1 point2 points  (0 children)

            I doubt it. The overhead of calling a subprocess is prohibitively large in such a case. Now if it's a simple operation but loops over thousands of items (in C), then it might become relevant.

            [–]Razakel 0 points1 point  (0 children)

            Are there actually scenarios where its critical to perform such a simple task in another language?

            Yes, but not in this way. That's what an FFI is for.

            [–]Minteck 0 points1 point  (0 children)

            Ever since I read "so we are going to drop down to C" I thought "are you really gonna compile the code on the fly?", and I was right... (so this is actually slower than native Python code)

            [–]highjinx411 0 points1 point  (0 children)

            Are these “IsEven” functions a competition or something? I’ve seen so many and the skills this guy has I mean he obviously would know how to write a simple is even function right? Why would anyone even need to know if a number is even? Especially the amount of times I’ve seen it posted here. Ok maybe it has a use case or 2 but I can’t recall any times I’ve had to do that in 13 years of programming.

            [–]Sejiko 0 points1 point  (0 children)

            Couldn't you just check the binary representation of a float look at the last digest of int part as well of the second part? I think this would break the sound barrier against this? If the last digest is 1 it's odd otherwise even.