For integers that don't have NaN/infinity values the options are less clear. Note that languages with two's complement fixed width signed types like int32 have the same problem with INT32_MIN / -1, since the result is not representable as an int32.
Yeah, but they usually just say that the result is undefined. At least that's what C and C++ do. And it's useful for optimizers, because they can assume more about basic arithmetic in signed types and do some neat tricks.
Yeah, the compiler typically assumes it works, and encodes assumptions that it does work into the surrounding code. On some systems you will get SIGFPE. On other systems, the result is something defined like 0. On some systems, the result is whatever happened to be in some register or whatever.
Sounds like a new PHP in the making, someone even mentioned it in the link. I think it's the only language I have worked with that's so happy to chug along and ignore errors that arise in execution. And the exception system was just thrown on top of it in PHP5 as an afterthought. Ugh.
I greatly dislike the language myself, but to be fair, it does allow the case where even a notice will terminate execution; and then to special-case functions that can safely fail with an @ prefix. Most people just turn all of that off with error_reporting()
Thinking back to when I was a novice programmer, and the consequences of my mistakes when working on stuff that was probably over my head, "terminate with extreme prejudice" seems like the only acceptable option. No result and a call from a user seems better than a wrong result. Folks will use a tool for safety (personal or financial) critical tasks without really thinking about failure modes.
Things like pocket calculators, Excel, and even web forms, will cough up a lung and quit when they hit an exception. So I think that novices of today are probably amply familiar with this mode of operation, even before learning to program.
It's bad enough even when you've got years of experience of debugging programs and you find an error that only reveals itself at the end of some long sequence of operations. For a language that's supposed to be targeted at the inexperienced, letting bogus results propagate is just cruel.
I'd definitely incline towards an immediate abort. But for processes that run overnight, presumably because they take a long time, who can say? If there's a 1 day turnaround time, and the results are so important that it absolutely must not stop, and yet inexperienced programmers are responsible for writing the code that runs... well, I'm not sure one thing is going to be a huge improvement over something else. It's probably going to end up a bit painful whatever you do.
If it's used in a video game with no save slots, where a crash is just going to lose lots of progress, I'd rather attempt to keep going even if there's an error, but only in release builds. Crash in debug builds to try and alert the programmer.
If it's used to save important work, where the bad divide could lead to data corruption, it's probably better to crash than risk the corruption. Although if you have the capability to ask the user what to do, that would be ideal.
If it's to develop something that handles money or lives, or is eg deployed on a satellite, not even crashing is acceptable. In that case, I'd force the language to have the user test with something like Haskell's "Maybe". For instance:
if(result = x / y) { //x / y returns an object: maybe<int>
z = result.get()
} // } else { handle division by zero case }
(edit: of course even that isn't fool-proof if the user tries hard enough to work around your safeguards. But you can't blame the language if the user is actively thwarting it.)
There's two competing software philosophies which are sometimes called "banker" and "moon rover." If you are writing a bank program, you want to catch errors immediately, to ensure that mistakes can't silently propagate. But if you are writing a moon rover, under no circumstances abort: keep going no matter what!
We have two Mars rovers right now, Opportunity and Curiosity. Fittingly, these use PowerPC chips, which are in the moon rover camp: divide-by-zero is zero. Really! Go get a PowerPC Mac, do a divide-by-zero, and you just silently get zero. Solution #1 baked into the ISA. Not many people seem to have noticed, so it can't have caused too much trouble.
Yep, this is also the case with some gaming systems; eg the SNES CPU's ALU, and also its Sony APU (SPC700). Division by zero continues on instead of crashing. They usually return 0 for the quotient, and ~0 ("-1") for the remainder.
So if it's not too much work, I'd like my language to give me the option based on the type of application I want. But if it were forced to be a choice, I'd probably err on the side of safety.
I was writing games in C++ for a long, long time, and recently started working on a very different project—a server using libuv and a lot of crypto operations with OpenSSL. I defined an ASSERT macro, and not thinking about it, wrote it so that in release builds, ASSERT does nothing. It's a very common thing to do in game engines: you do assertions on all sorts of things to catch user errors, but they slow the engine down a lot, and you don't want to crash the whole game because of some (maybe innocuous) error so you just ignore failed assertions.
Imagine my surprise when I did a release build for the first time and realized what I'd done :)
Well, this was a server, and it was also multithreaded, so many synchronization bugs that didn't happen in debug builds did happen in release ones because of timing. Without assertions, though, none of these was caught on time, so I was left with mystical crashes, garbage data sent out to clients, etc etc. I spent hours and hours in valgrind and gdb, and only then realized that none of the assertions worked. When I re-enabled them, I was able to snipe (almost) all of the bugs easily.
But this is generally the kind of thing that happens when you turn a blind eye to errors somewhere where that's not a good way to go. In gamedev, sure, nobody cares, but if you're dealing with crypto and security-sensitive stuff, well, what I did was very, very bad. It's a small proof-of-concept pet project, but if I was ever to release something like that to the public, it would be very irresponsible of me.
With total respect to what you're saying, I'd just like to say that I care about games and other non-mission-critical software. I am continually disappointed by buggy glitchy unreliable applications which have clearly been rushed out to meet a release window. It's as if we've all been trained to accept poor quality, based on the excuse that "it's not handling payments or running a nuclear reactor so it's OK to be sloppy".
But would you pay double to get it? Or would you buy the buggier one that comes out first, and hey now you've spent your game budget so the 'good one' will have to wait.
Its commerce, and 'just in time' and 'just good enough' is all that sells.
I am sorry, but you have completely missed my point. All software has bugs, period. With games, you can omit assertions so that these bugs cause glitches in the game (or cause no visible change at all), or you can keep them in, slowing down the engine because of numerous runtime checks and crashing the whole game if a single bug occurs. That's way more sloppy design than the former.
Yes. To elaborate, this construction is called the "surreal numbers", and it is the largest ordered field. All ordered fields are isomorphic to subsets of the surreal numbers.
Would this not be an excellent case for an optional type or Maybe monad? Instead of all NaN operations returning NaN, have them all return an optional type, similar to how swift dictionaries return an optional for property access. This is enforceable by the compiler.
A really sly option would be to, effectively, allow infinitesimals. So 10/0 would return 10/0 (perhaps a kind of NaN but with more information retained), and if you multiplied it by 0 elsewhere you'd get 10. Probably a bad idea for novice programmers but interesting to consider.
In order to work with infinitesimals in any reasonable way, you have to define how they work. There is a reasonable way to extend the real numbers in a larger field containing infinitesimals. Such a field is non-Archimedean and has very surprising properties: for example, you can construct a geometry in which Euclid's fifth postulate is false, and yet the angles on a triangle still always add up to 180. These fields also allow you to formalize your intuition about the way infinitesimals like "dx" and "dy" in calculus and analysis work: you can take an equation and multiply both sides by dx, for example. This is ordinarily meaningless, since the term dy/dx is ordinarily just notation that does not actually signify a fraction. In non-standard analysis, it IS a fraction. I think it's a shame that analysis classes in college don't use non-Archimedean fields because the proofs are so much simpler.
However, even in these fields which admit infinitesimals, there is no definition for x/0. You can prove it for yourself.
So this is just an all-around bad idea, because it permits us to say that 10 = 20. And all we had to say that 10/0 had a value... we didn't even say what that value was.
P.S. Although infinitesimals exist, non-standard analysis still does not permit you to, say, plug ∞ into an equation, because there are many different infinite numbers. However, other systems (projective geometry) admit ∞ as a value.
But in an environment manipulating symbols where one of them 'x' has the property that multiplying by zero gives ten, I don't think the above sequence could happen, or at least the symbolic evaluator would arrange things so that the first time it sees '0 * x' it replaces that with 10. So to evaluate the truth of the expression '(a+b) * x = 10' with 'a' and 'b' equal to zero, we just need to ensure the 'a+b' is evaluated to zero before the multiplication, using the axiom 'n+0=n' and we are back to '0 * x' on the LHS, which is ten.
I think it should be possible with appropriate precedence of the evaluation rules?
You're turning something which is merely wrong into a total nightmare. Suddenly, 0+0 is not 0, because you have to wait to see if you multiply it by x first before you can evaluate 0+0! Now there are multiple distinct values for 0! Chaos and disharmony abound! In your system, 0+0=0+0, and 1+1=1+1. You can't simplify any of your equations! In your system, 1+1=2 is FALSE!
No, seriously. Quit while you're ahead. Now that you know that dividing by zero and getting a result is not a logically consistent proposition, you can stop trying. That's the awesome thing about logic: once you PROVE something is impossible, you're done.
I liked the ternary operator where you provide an alternate value in case the denumerator is 0.
One more idea I didn't see is to mark the variable as "invalid". A general "this variable doesn't have a value because the program did something bad", which carries a full stack trace with itself. Then, if you ever use that value anywhere, the result is again invalid, with the new operation attempted on top (i.e. if the invalid value ever appears in an expression, the entire expression yields invalid and no function calls are attempted; an exception would be boolean operators - true or invalid is true and false and invalid is false - there may be other exceptions to that, too). Once that value is no longer referenced anywhere, you log a nice long description of the entire chain where things were wrong and continue business as usual. Maybe email the trace to the project lead or something.
Something like the Maybe monad basically. It will work with anything else that can fail, too, and you can allow people to construct their own invalids. And the program will continue to run as long as the error isn't too deep, which is what is really wanted here, I think.
Not quite, marking a variable as invalid in this context is essentially a job for the type system. For example, in C, you can have a void* which can be null. This can lead to seg faults, and NaN has the same pitfall. Instead, with the Maybe idea above, the type system explicitly makes you handle the NaN case since the type is a discriminated union. This is like `int?` in C#.
I like the idea of providing an alternate value or even alternate flow of execution.
The action to take when dividing by 0 is based on context: it is a business behavior. In my opinion, it is no different than any other business requirement (for example: passwords must be longer than 8 characters, etc.).
A language should allow a developer to configure the action to take when a division by zero occurs within a given context: the default being an exception and/or termination of program execution.
> One more idea I didn't see is to mark the variable as "invalid". A general "this variable doesn't have a value because the program did something bad", which carries a full stack trace with itself.
A simple way to handle exceptions like that (and I didn't see it mentioned among the answers) is to return 2-tuple from the function: result value and error information. The latter could be empty, but both could be present.
Though this might be overcomplicating things, given that OP targets the language at novices.
Yep. You’d think that in, say, Haskell, the division operator would return Maybe Num. It’s much more obvious when you’ve got Nothing than when you’ve got NaN.
Actually, it doesn't even compile in go, because / doesn't return two arguments. But he's writing his own language, not using go, so this limitation need not apply.
Not really. Sometimes it's faster to do the calculations and check after the fact if NaN flag was set instead of checking for division by zero at every point. Languages that auto crash on division by zero will always be slower at such applications.
This isn't related to the question at hand, but one of the most interesting ways to handle this was in a smalltalk app i saw, which turned the result into a custom NaN and stuffed the execution context into the variable so even if you restored it from the database, you'd know exactly where the divide by zero occurred. That's different from all the other languages where NaNs propagate but you don't know their origin.
NaNs are an incredibly good idea, but it turns out we really need only one.
Also, two's complement signed integers have an asymmetry that INT_MIN < -INT_MAX.
Therefore, I were to design a CPU today, I would add arithmetic instructions that treated INT_MIN as a NaN, thus removing the asymmetry, and giving us an integer NaN.
16 bit int values would range from -32767 to +32767, with the bit pattern now used for -32768 being NaN. You would use it for everything that isn't representable as an int (overflow, underflow, 3/2 (as opposed to 3 mod 2), etc)
Yes, that CPU would also would have instructions for doing the regular modulo 2^16 arithmetic, but 'sane ints' would help not penalize programming languages that want to do sane integer arithmetic.
2's complement (the binary representation of numbers in a cpu) has one awesome feature, if you add two signed numbers, even if one or both are negative, you get the right answer. This means that you can add two numbers without checking if one is negative, saving time and scarce circuit space. In order to add a NaN, you'd need a check for NaN. This would basically double the amount of time an add would take, and add more circuit paths to the core of the cpu.
If we use a single bit dedicated to signalling NaN, the overhead might be worth it. Of course, we lose half of our range in doing so.
It wouldn't double the amount of time for an add (or any other operation), if it's implemented in hardware the comparison can be done in parallel with the addition, and the result of the addition ignored at the end if the compare returned true.
It would increase the use of space by the circuit, but not by much. And we already have circuitry doing similar things to set the flags (zero, carry, overflow, parity...).
If you want a real-world system with a single extra bit for every value (on registers at least), see the Itanium and its NaT bit (used for speculative loads).
After writing it, I realised that you could just do both in parallel, but it soon makes things much more complicated since nearly everything is based on adds.
You get the right answer _modulo_2^N_. In many cases, that is not the right answer.
Also, most CPUs will set an overflow bit if addition overflows or underflows. I don't see how it would be that hard to also force the result to 0b100....000 if that happens.
And 'scarce circuit space'? There are areas where that is still true, but they are getting rarer and rarer. Certainly, for desktop CPUs, x86 shows that one can waste circuit space by keeping around the classical x87 FPU logic, a few variants of SSE that one should not use anymore, a few attempts at vector units, instructions for branching hints that the CPU ignores, etc, and yet be successful.
I think the price paid for the detection of logic errors would be worth it.
"If we use a single bit dedicated to signalling NaN, the overhead might be worth it. Of course, we lose half of our range in doing so."
You don't need to lose half your range; you can also add a bit. You only need to add a sticky overflow bit to each register, keep the arithmetic hardware as-is, and modify loads to set the sticky overflow when reading the NaN pattern, and modify writes to write 0b1000...000 when writing a register whose sticky overflow bit is set.
Hardware cost would, I think, be very small, as you would only need a bit per register, and keep memory as-is.
Given that Apple has non-wrap around ints in Swift and designs its own CPUs, it wouldn't surprise me if they added something similar or their CPUs, just as I expect them to add instructions for loading from tagged pointers (https://www.mikeash.com/pyblog/friday-qa-2012-07-27-lets-bui...)
Sounds to me like a language targeted at novice developers (and I suppose with that author means people with no programming background) should add a very strong emphasis on simulations and notifications.
Won't it be ok that program crashes if :
1. it was likely the error was catch during a simulation before deployment
2. author of code received a mail immediately to let know something bad occurred
Such a language should probably be much bigger than our usual programming language and include at core what is usually added by dev tools (tests and notifications).
There was a discussion on HN a few days ago on "test only development" (link has been marked "dead" since then, though), that is tests that produce code. It reminds me of what "programming" a holodeck looks like in star trek : you describe what you want and computer manage to produce code that achieves it.
Without getting so far, you could ask your users to describe what data looks like in input and what it should looks like on ouput, as a core feature of the language rather than on a separated and optional layer. Then, on build, you run a "simulation" (yes, that is just testing) to ensure code behaves as expected.
And you build notifications (by mail, for exemple) as a core feature of the language too, maybe even include version control (you have to think hard how to make that user friendly for your target) in order everybody has a idea of what changed before the crash (this could be reported in notification).
When you have all of that, it does not matter if division by zero makes program crash.
My favorite non-discussed answer on SE or here on HN (or at least I didn't find it) is to disallow division other than as syntactic sugar for literal definitions (and just output an error message if your syntactic sugar has a zero denominator)
But but but how would they ... don't worry about it, its a programming language for novices. If they knew what to do, they wouldn't be novices using a novice language anyway.
I like this design because its self limiting. If they don't understand the problem, they certainly won't understand how to fix it when it breaks anyway.
The problem with giving 3 year olds matches and knives and guns is not some weird inherent moral argument about the items, its because the kids have no idea how to fix some resulting problems and even the non-noobs can't fix some of the resulting problems. So don't give noobs the ability to shoot themselves in their foot. Its pretty simple.
Of course a logical next extension is removal of logarithms and many other math functions. Thats OK. Its a language for noobs so it doesn't have to have a full set of arithmetic. Unless this guy is implementing the next "mathematica" or "R" or similar, in which case he has big problems.
65 comments
[ 4.3 ms ] story [ 224 ms ] threadFor integers that don't have NaN/infinity values the options are less clear. Note that languages with two's complement fixed width signed types like int32 have the same problem with INT32_MIN / -1, since the result is not representable as an int32.
Just to clarify, I think you're talking about the INT_MIN / -1 case?
I think that floating-point division by zero is defined in C and C++.
And for security exploits.
Other technologies designed to render content for web browsers do this also, such as javascript and the django template rendering engine.
Things like pocket calculators, Excel, and even web forms, will cough up a lung and quit when they hit an exception. So I think that novices of today are probably amply familiar with this mode of operation, even before learning to program.
I'd definitely incline towards an immediate abort. But for processes that run overnight, presumably because they take a long time, who can say? If there's a 1 day turnaround time, and the results are so important that it absolutely must not stop, and yet inexperienced programmers are responsible for writing the code that runs... well, I'm not sure one thing is going to be a huge improvement over something else. It's probably going to end up a bit painful whatever you do.
If it's used in a video game with no save slots, where a crash is just going to lose lots of progress, I'd rather attempt to keep going even if there's an error, but only in release builds. Crash in debug builds to try and alert the programmer.
If it's used to save important work, where the bad divide could lead to data corruption, it's probably better to crash than risk the corruption. Although if you have the capability to ask the user what to do, that would be ideal.
If it's to develop something that handles money or lives, or is eg deployed on a satellite, not even crashing is acceptable. In that case, I'd force the language to have the user test with something like Haskell's "Maybe". For instance:
(edit: of course even that isn't fool-proof if the user tries hard enough to work around your safeguards. But you can't blame the language if the user is actively thwarting it.)We have two Mars rovers right now, Opportunity and Curiosity. Fittingly, these use PowerPC chips, which are in the moon rover camp: divide-by-zero is zero. Really! Go get a PowerPC Mac, do a divide-by-zero, and you just silently get zero. Solution #1 baked into the ISA. Not many people seem to have noticed, so it can't have caused too much trouble.
(Apologies for reposting my reddit comment here)
So if it's not too much work, I'd like my language to give me the option based on the type of application I want. But if it were forced to be a choice, I'd probably err on the side of safety.
Imagine my surprise when I did a release build for the first time and realized what I'd done :)
But this is generally the kind of thing that happens when you turn a blind eye to errors somewhere where that's not a good way to go. In gamedev, sure, nobody cares, but if you're dealing with crypto and security-sensitive stuff, well, what I did was very, very bad. It's a small proof-of-concept pet project, but if I was ever to release something like that to the public, it would be very irresponsible of me.
Its commerce, and 'just in time' and 'just good enough' is all that sells.
In combinatorial game theory, the star game is fuzzy with zero: not less or greater, but not equal either.
The zero game is (iirc) the game where neither the left nor the right player have any moves, so whoever's turn it is loses.
The star game (*) is iirc the game where both players have the move to move to the zero game, so whoever starts wins.
But the star game is fuzzy with the zero game, less than the 1 game, and more than the -1 game.
Iirc.
let x = 1231231 let z = 0
if z == 0 { println("Cant divide by zero") } else { println(x / z) }
But swift doesn't support exceptions either. :/
In order to work with infinitesimals in any reasonable way, you have to define how they work. There is a reasonable way to extend the real numbers in a larger field containing infinitesimals. Such a field is non-Archimedean and has very surprising properties: for example, you can construct a geometry in which Euclid's fifth postulate is false, and yet the angles on a triangle still always add up to 180. These fields also allow you to formalize your intuition about the way infinitesimals like "dx" and "dy" in calculus and analysis work: you can take an equation and multiply both sides by dx, for example. This is ordinarily meaningless, since the term dy/dx is ordinarily just notation that does not actually signify a fraction. In non-standard analysis, it IS a fraction. I think it's a shame that analysis classes in college don't use non-Archimedean fields because the proofs are so much simpler.
However, even in these fields which admit infinitesimals, there is no definition for x/0. You can prove it for yourself.
Suppose x = 10/0.
So this is just an all-around bad idea, because it permits us to say that 10 = 20. And all we had to say that 10/0 had a value... we didn't even say what that value was.P.S. Although infinitesimals exist, non-standard analysis still does not permit you to, say, plug ∞ into an equation, because there are many different infinite numbers. However, other systems (projective geometry) admit ∞ as a value.
I think it should be possible with appropriate precedence of the evaluation rules?
No, seriously. Quit while you're ahead. Now that you know that dividing by zero and getting a result is not a logically consistent proposition, you can stop trying. That's the awesome thing about logic: once you PROVE something is impossible, you're done.
One more idea I didn't see is to mark the variable as "invalid". A general "this variable doesn't have a value because the program did something bad", which carries a full stack trace with itself. Then, if you ever use that value anywhere, the result is again invalid, with the new operation attempted on top (i.e. if the invalid value ever appears in an expression, the entire expression yields invalid and no function calls are attempted; an exception would be boolean operators - true or invalid is true and false and invalid is false - there may be other exceptions to that, too). Once that value is no longer referenced anywhere, you log a nice long description of the entire chain where things were wrong and continue business as usual. Maybe email the trace to the project lead or something.
Something like the Maybe monad basically. It will work with anything else that can fail, too, and you can allow people to construct their own invalids. And the program will continue to run as long as the error isn't too deep, which is what is really wanted here, I think.
That's what NaN is.
The action to take when dividing by 0 is based on context: it is a business behavior. In my opinion, it is no different than any other business requirement (for example: passwords must be longer than 8 characters, etc.).
A language should allow a developer to configure the action to take when a division by zero occurs within a given context: the default being an exception and/or termination of program execution.
A simple way to handle exceptions like that (and I didn't see it mentioned among the answers) is to return 2-tuple from the function: result value and error information. The latter could be empty, but both could be present.
Though this might be overcomplicating things, given that OP targets the language at novices.
https://mail.mozilla.org/pipermail/rust-dev/2014-June/010363...
Also, two's complement signed integers have an asymmetry that INT_MIN < -INT_MAX.
Therefore, I were to design a CPU today, I would add arithmetic instructions that treated INT_MIN as a NaN, thus removing the asymmetry, and giving us an integer NaN.
16 bit int values would range from -32767 to +32767, with the bit pattern now used for -32768 being NaN. You would use it for everything that isn't representable as an int (overflow, underflow, 3/2 (as opposed to 3 mod 2), etc)
Yes, that CPU would also would have instructions for doing the regular modulo 2^16 arithmetic, but 'sane ints' would help not penalize programming languages that want to do sane integer arithmetic.
If we use a single bit dedicated to signalling NaN, the overhead might be worth it. Of course, we lose half of our range in doing so.
It would increase the use of space by the circuit, but not by much. And we already have circuitry doing similar things to set the flags (zero, carry, overflow, parity...).
If you want a real-world system with a single extra bit for every value (on registers at least), see the Itanium and its NaT bit (used for speculative loads).
Also, most CPUs will set an overflow bit if addition overflows or underflows. I don't see how it would be that hard to also force the result to 0b100....000 if that happens.
And 'scarce circuit space'? There are areas where that is still true, but they are getting rarer and rarer. Certainly, for desktop CPUs, x86 shows that one can waste circuit space by keeping around the classical x87 FPU logic, a few variants of SSE that one should not use anymore, a few attempts at vector units, instructions for branching hints that the CPU ignores, etc, and yet be successful.
I think the price paid for the detection of logic errors would be worth it.
You don't need to lose half your range; you can also add a bit. You only need to add a sticky overflow bit to each register, keep the arithmetic hardware as-is, and modify loads to set the sticky overflow when reading the NaN pattern, and modify writes to write 0b1000...000 when writing a register whose sticky overflow bit is set.
Hardware cost would, I think, be very small, as you would only need a bit per register, and keep memory as-is.
Given that Apple has non-wrap around ints in Swift and designs its own CPUs, it wouldn't surprise me if they added something similar or their CPUs, just as I expect them to add instructions for loading from tagged pointers (https://www.mikeash.com/pyblog/friday-qa-2012-07-27-lets-bui...)
Won't it be ok that program crashes if :
1. it was likely the error was catch during a simulation before deployment
2. author of code received a mail immediately to let know something bad occurred
Such a language should probably be much bigger than our usual programming language and include at core what is usually added by dev tools (tests and notifications).
There was a discussion on HN a few days ago on "test only development" (link has been marked "dead" since then, though), that is tests that produce code. It reminds me of what "programming" a holodeck looks like in star trek : you describe what you want and computer manage to produce code that achieves it.
Without getting so far, you could ask your users to describe what data looks like in input and what it should looks like on ouput, as a core feature of the language rather than on a separated and optional layer. Then, on build, you run a "simulation" (yes, that is just testing) to ensure code behaves as expected.
And you build notifications (by mail, for exemple) as a core feature of the language too, maybe even include version control (you have to think hard how to make that user friendly for your target) in order everybody has a idea of what changed before the crash (this could be reported in notification).
When you have all of that, it does not matter if division by zero makes program crash.
If when faced with a problem you say "I know, I'll make my own computer language!" Now you have 2 problems.
But but but how would they ... don't worry about it, its a programming language for novices. If they knew what to do, they wouldn't be novices using a novice language anyway.
I like this design because its self limiting. If they don't understand the problem, they certainly won't understand how to fix it when it breaks anyway.
The problem with giving 3 year olds matches and knives and guns is not some weird inherent moral argument about the items, its because the kids have no idea how to fix some resulting problems and even the non-noobs can't fix some of the resulting problems. So don't give noobs the ability to shoot themselves in their foot. Its pretty simple.
Of course a logical next extension is removal of logarithms and many other math functions. Thats OK. Its a language for noobs so it doesn't have to have a full set of arithmetic. Unless this guy is implementing the next "mathematica" or "R" or similar, in which case he has big problems.