Skip to content

Instantly share code, notes, and snippets.

@cruxicheiros
Created March 27, 2020 02:05
Show Gist options
  • Select an option

  • Save cruxicheiros/4d3b887d95385c5e89ea53e24988fc59 to your computer and use it in GitHub Desktop.

Select an option

Save cruxicheiros/4d3b887d95385c5e89ea53e24988fc59 to your computer and use it in GitHub Desktop.
Unpicking a lump of ternary into something meaningful
// 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