Created
March 27, 2020 02:05
-
-
Save cruxicheiros/4d3b887d95385c5e89ea53e24988fc59 to your computer and use it in GitHub Desktop.
Unpicking a lump of ternary into something meaningful
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // The original: | |
| function doMath(bias, significand, signal, exponent) { | |
| return exponent == (bias << 1) + 1 | |
| ? significand | |
| ? NaN | |
| : signal | |
| ? -Infinity | |
| : +Infinity | |
| : (1 + signal * -2) * | |
| (exponent || significand | |
| ? !exponent | |
| ? Math.pow(2, -bias + 1) * significand | |
| : Math.pow(2, exponent - bias) * (1 + significand) | |
| : 0) | |
| } | |
| // How the ternary operator works: It's basically a little if-else statement | |
| // that kind of works like a lambda function in that it happens in one line | |
| // and returns something. | |
| // | |
| // CONDITION ? RET_IF_true : RET_IF_false | |
| // So, for example, true ? 1 : 2 will always return true. | |
| // The ternary operator can also be *chained*, for use like: | |
| // CONDITION ? RET_IF_true : CONDITION2 ? RET_IF_true : RET_IF_false | |
| // where if condition 1 isn't met, condition 2 will be checked, and if none | |
| // of them are met, then the final value in the chain will be returned. | |
| // For more details read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator | |
| // The easiest way in my opinion to start reading a messy ternary expression is | |
| // to cut out everything except the outermost layer and start adding things | |
| // back in. I've added bools to stand in for the outputs just so the function | |
| // is runnable and you can see how this works if you run it. | |
| function gutted(bias, significand, signal, exponent) { | |
| return exponent == (bias << 1) + 1 | |
| ? true // Return this if the condition evaluates to true | |
| : false // Return this if the condition evaluates to false | |
| } | |
| // Now let's deal with what happens if the condition evaluates to true. | |
| // Notice how in the series `? significand ? NaN : signal ? -Infinity : +Infinity` | |
| // the pattern is `??:?:` - it contains a question mark followed by | |
| // another question mark. This is NOT the chain pattern previously specified, | |
| // which follows the repeated pattern `?:?:?:`. | |
| // The implication is that this is its own embedded expression, the result of | |
| // which is returned by the expression it's inside. | |
| // To illustrate this: | |
| function onlyFirstHalf(bias, significand, signal, exponent) { | |
| return exponent == (bias << 1) + 1 | |
| ? (significand ? NaN | |
| : signal ? -Infinity | |
| : +Infinity) | |
| : false // Return this if the condition evaluates to false | |
| } | |
| // The inner ternary expression has now been encapsulated in parentheses. | |
| // Part 2 looks daunting as well, but it can get a similar treatment. I'll add | |
| // parentheses to the expression being calculated: | |
| function onlySecondHalf(bias, significand, signal, exponent) { | |
| return exponent == (bias << 1) + 1 | |
| ? true // Return this if the condition evaluates to true | |
| : (1 + signal * -2) * // Return this if the condition evaluates to false | |
| ((exponent || significand) | |
| ? !exponent | |
| ? Math.pow(2, -bias + 1) * significand | |
| : Math.pow(2, exponent - bias) * (1 + significand) | |
| : 0) | |
| } | |
| // It may be easier to see what's going on if I take out the deepest-nested | |
| // ternary expression... | |
| function orExpressionGutted(bias, significand, signal, exponent) { | |
| return exponent == (bias << 1) + 1 | |
| ? true // Return this if the condition evaluates to true | |
| : (1 + signal * -2) * // Return this if the condition evaluates to false | |
| ((exponent || significand) | |
| ? 1 | |
| : 0) | |
| } | |
| // ... or if I wrap it in parentheses. | |
| function orExpressionRefilled(bias, significand, signal, exponent) { | |
| return exponent == (bias << 1) + 1 | |
| ? true // Return this if the condition evaluates to true | |
| : (1 + signal * -2) * // Return this if the condition evaluates to false | |
| ((exponent || significand) | |
| ? (!exponent | |
| ? Math.pow(2, -bias + 1) * significand | |
| : Math.pow(2, exponent - bias) * (1 + significand)) | |
| : 0) | |
| } | |
| // Fortunately, this final part is a chained ternary expression that | |
| // doesn't contain any more ternary expressions, so we can stop adding | |
| // parentheses here. Here's our slightly more modular equivalent: | |
| function doMathWithParentheses(bias, significand, signal, exponent) { | |
| return (exponent == (bias << 1) + 1) | |
| ? (significand ? NaN | |
| : signal ? -Infinity | |
| : +Infinity) // Return this if the condition evaluates to true | |
| : (1 + signal * -2) * // Return this if the condition evaluates to false | |
| ((exponent || significand) | |
| ? (!exponent | |
| ? Math.pow(2, -bias + 1) * significand | |
| : Math.pow(2, exponent - bias) * (1 + significand)) | |
| : 0) // Return this if the condition evaluates to false | |
| } | |
| // Stopping here, though, doesn't actually help us much. The next step is to | |
| // split out the expressions into their own functions. This will help | |
| // look at the expressions modularly rather than as a big block of code. | |
| // I am not paying attention to their actual functions yet. | |
| // This will initially look more confusing but it will facilitate | |
| // the next steps where we rewrite the ternary expressions into something | |
| // easier to manage. | |
| // Starting with the innermost ternary blocks: | |
| function doThisIfSignificandIsTruey(exponent, bias, significand) { | |
| return (!exponent | |
| ? Math.pow(2, -bias + 1) * significand | |
| : Math.pow(2, exponent - bias) * (1 + significand)) | |
| } | |
| function doThisIfInitialExpressionIsTruey(significand, signal) { | |
| return (significand ? NaN | |
| : signal ? -Infinity | |
| : +Infinity) | |
| } | |
| // Recombining these, it's easier to see what's happening in the outer expressions. | |
| function doMathWithFunctions(bias, significand, signal, exponent) { | |
| return (exponent == (bias << 1) + 1) | |
| ? doThisIfInitialExpressionIsTruey(significand, signal) // Return this if the condition evaluates to true | |
| : (1 + signal * -2) * // Return this if the condition evaluates to false | |
| ((exponent || significand) | |
| ? doThisIfSignificandIsTruey(exponent, bias, significand) | |
| : 0) | |
| } | |
| // I am going to stop here because I only have a couple of ternary expressions | |
| // left. | |
| // | |
| // I need to note here that the way that JavaScript decides to interpret | |
| // what kind of number is positive or negative is a bit weird. | |
| // If you evaluate a negative number as part of a ternary expression, | |
| // negative non-zero numbers are evaluated as true. For example, | |
| // -2 ? 1 : 0 will always output 1. The same goes for using negative | |
| // numbers directly in `if` expressions: the code in if (-2) {...} will | |
| // execute. However, if you do a comparison with the == operator, as in | |
| // `-2 == true`, the result will instead be `false`. | |
| // | |
| // For total clarity, this is fully explained here: https://stackoverflow.com/a/3619813 | |
| // These three functions are the equivalent of the weird ternary blob we had | |
| // first. So what *does* it do? | |
| function doThisIfSignificandIsTrueyRefactor(exponent, bias, significand) { | |
| if (exponent) { | |
| return Math.pow(2, exponent - bias) * (1 + significand); | |
| } else { | |
| return Math.pow(2, -bias + 1) * significand; | |
| } | |
| } | |
| function doThisIfInitialExpressionIsTrueyRefactor(significand, signal) { | |
| if (significand) { | |
| return NaN; | |
| } else if (signal) { | |
| return -Infinity; | |
| } else { | |
| return +Infinity; | |
| } | |
| } | |
| function doMathWithFunctionsRefactor(bias, significand, signal, exponent) { | |
| if (exponent == (bias << 1) + 1) { | |
| return doThisIfInitialExpressionIsTrueyRefactor(significand, signal); | |
| } else { | |
| let factor = 0; | |
| if (exponent || significand) { | |
| factor = doThisIfSignificandIsTrueyRefactor(exponent, bias, significand) | |
| } | |
| return (1 + signal * -2) * factor; | |
| } | |
| } | |
| // The names of the inputs - bias, signal, exponent, and significand - clue | |
| // me in that this has something to do with IEEE 754. | |
| // Bias: a value added to the exponent to get the "actual" exponent that will be | |
| // applied to the significand. The bias is calculated as 2^(k-1) - 1 | |
| // where k is the size of the exponent. | |
| // (https://en.wikipedia.org/wiki/Exponent_bias) | |
| // | |
| // Signal: Based on its name and use in the code, this represents the sign bit. | |
| // Significand: The significant digits of the number, not including the leading 1 | |
| // Exponent: added to the bias to get the "actual" exponent that will be | |
| // applied to the significand. | |
| // So what does (exponent == (bias << 1) + 1) check for? | |
| // If an exponent is all ones, for example 1111 1111 for a single-precision | |
| // number, then it means the number is some kind of special case. | |
| // For a single-precision number, the bias will be 0111 1111. | |
| // After applying a left shift and adding 1, it will be 1111 1111. | |
| // This allows checking to see if the number is a special case. | |
| function isSpecialCase(exponent, bias) { | |
| return exponent == (bias << 1) + 1 | |
| } | |
| // So we can rename doThisIfSignificandIsTrueyRefactor... | |
| function handleSpecialCases(significand, signal) { | |
| if (significand) { | |
| return NaN; | |
| } else if (signal) { | |
| return -Infinity; | |
| } else { | |
| return +Infinity; | |
| } | |
| } | |
| // How about the other logic? Well, in the case that neither the exponent nor | |
| // the significand is a non-zero number, the number must be some representation | |
| // of zero. So this can be renamed... | |
| function calculateNumberFromComponents(exponent, bias, significand) { | |
| if (exponent) { | |
| return Math.pow(2, exponent - bias) * (1 + significand); | |
| } else { | |
| return Math.pow(2, -bias + 1) * significand; | |
| } | |
| } | |
| // What's up with (1 + signal * -2)? 0 represents positive numbers, and 1 represents | |
| // negative numbers. | |
| // So (-2 * 1) + 1 = -1, and (0 * 1) + 1 = 1. | |
| function applySignalToAbsoluteValue(signal, absoluteValue) { | |
| return absoluteValue * (1 + signal * (-2)); | |
| } | |
| // Now we can look at the main function again... | |
| function convertIEEE754ToNumber(bias, significand, signal, exponent) { | |
| if (isSpecialCase(exponent, bias)) { | |
| return handleSpecialCases(significand, signal); | |
| } else { | |
| let absoluteValue = 0; | |
| if (exponent || significand) { | |
| absoluteValue = calculateNumberFromComponents(exponent, bias, significand); | |
| } | |
| return applySignalToAbsoluteValue(signal, absoluteValue); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment