SOUP: Function Pointers
Today's snippet demos function pointers (objects in Python), in particular an array of function pointers. We use them to print a table of arithmetic operations on all of the IEEE special values.
IEEE 754 defines the behaviour of floating point numbers, in particular what happens when numbers get unrepresentable, whether that is too large, too small or plain not-numbers.
Infinity is a familiar enough concept and in floating-point it mostly means a number which is too large to be represented. There's a positive and a negative infinity and most arithmetic works as expected.
Negative zero (-0.0) seems quite odd at a first glance. The sign-bit is set, but in every other way, and in general, it is equal to positive zero. Comparisons like `0.0 == -0.0` are defined to be true and `-0.0 < 0.0` is false. Most languages have a function to copy the sign from one number to another though, and -0.0 works as expected.
NaN, Not a Number, mostly appears as a lament, "Why is it NaN!?" or worse "Why is it NaN again!?" Any operations involving NaN also give NaN as do several others.
It seems strange that `Inf * 0.0` or `Inf + -Inf` both give NaN where they could both reasonably give zero. Philosophically, both are numbers, but completely unknown ones. Inf isn't mathematical infinity, it is just too large to represent, and 0.0 stands in for any number too small to represent. Their product then can be absolutely anything, hence defining it as NaN.
Code snippets in C, Fortran and Python are in our SOUP repo under 001_IEEE. All three use named functions to "dry out" the code: in fact they use an array of them. Note in Fortran this needs us to use a custom type to hold the function pointer, as we can't have an array of simply pointers.
The core of all snippets is the loop over operations and inputs
In C:
float (*op)(float, float);/*Holds operation function*/ float(*allOps[4])(float, float); allOps[0] = &add; allOps[1] = ⊂ allOps[2] = &mul; allOps[3] = ÷ for(opNum=0; opNum< 4; opNum++){ op = allOps[opNum]; for(rowNum = 0; rowNum < 7; rowNum++){ row = allRows[rowNum]; /*print result of op(row, column)*/ } }
In Fortran (where f_ptr is our type holding a pointer):
TYPE(f_ptr), DIMENSION(4) :: allOps TYPE(f_ptr) :: currOp allOps(1)%ptr => add allOps(2)%ptr => sub allOps(3)%ptr => mult allOps(4)%ptr => div DO opNum = 1, 4 currOp%ptr => allOps(opNum)%ptr DO rowNum = 1, 7 row = allRows(rowNum) !Print results of currOp%ptr applied to row, column ENDDO ENDDO
And in Python (using range-based for loops on lists):
allOps = [add, sub, mul, div] for opNum in range(0, 4): op = allOps[opNum] for rowNum in range(0, 7): row = allRows[rowNum] #Print result of op(row, column)
Note all three are subtly different in how one assigns the active operation. In C you just get a pointer to a function with the usual address-of operator, &. In Fortran you point your pointer to the proper target using the points-to operator, =>. In Python we just set the operator to equal the function, omitting the brackets () turning this into an assignment, not a call.
Function pointers also have a powerful use in High-Performance code. A long if-else chain inside a loop, which calls a different function in each branch, can be replaced with the same if-chain outside the loop setting a function pointer and then a simple call inside the loop, eliminating the branch. As pseudo-code:
DO i = 0, 10000 IF (condition) function_1() ELSE IF (condition) function_2() ELSE IF (condition) function_3() ... ENDDO
becomes
IF (condition) ptr = function_1 ELSE IF (condition) ptr = function_2 ELSE IF (condition) ptr = function_3 ... DO i = 0, 10000 ptr() ENDDO
No comments
Add a comment
You are not allowed to comment on this entry as it has restricted commenting permissions.