Degenerate Conic

Algorithms • Modern Fortran Programming • Orbital Mechanics

Jun 13, 2026

Sign, Sign, Everywhere a Sign

fortran-ai-2

Reference [1] describes a numerical method for computing the derivative of a 1D function, using Neville's algorithm to extrapolate from a sequence of simple polynomial approximations based on interpolating points within specific bounds of a given point. The original code in the published paper in 1980 was written in ALGOL 60. It seems as if a Fortran 77 translation was produced by David Kahaner at NIST circa 1989. I found the file on this NIST server where it has been sitting quietly since 1992 (originally this was an ftp server when I first found it). It's an interesting algorithm, and I included a modernized version in my NumDiff numerical differentiation library. I also note that a version of the code is embedded within the Dataplot package.

But, there is a problem!

Here is a line of code in the original faccuracy function:

IF 16*ABS(H1)>ABS(H0) THEN H1:=SIGN(H1)*ABS(H0)/16;

Now, I know nothing about ALGOL 60, except that apparently John Backus (the creator of Fortran) was one of the committee members that developed it. Notice the SIGN function in this code. This line was translated into Fortran 77 (where the function is renamed as FACCUR) as:

IF(16.*ABS(H1) .GT. ABS(H0)) H1 = SIGN(H1,1.)*ABS(H0)/16.

They are very similar, but note the SIGN function again. In Fortran, the SIGN function (added in Fortran 77) is a little different since it has two arguments. While more flexible, it is a never-ending source of confusion, and I always have to look up the meaning of the two arguments. The following table summarizes the difference between the two:

Language Syntax Description Reference
ALGOL 60 sign(E) the sign of the value of E (+1 for E>0, 0 for E=0, -1 for E<0) [2]
Fortran sign(A, B) the value of A with the sign of B [3]

So, do you see the bug? SIGN(H1,1.) doesn't return the same thing that SIGN(H1) did in the original code. SIGN(H1,1.) returns the value of H1 with the sign of 1.0, which is always +H1, not the original intent at all! It really should have been translated to SIGN(1.0,H1), to give the +1/-1 of the original code. So, this bug is over 30 years old. Technically, the 0 case is still not handled the same, but that's a degenerate case that would never happen or give meaningful results in this context. But to be totally faithful we would need to use a function like this:

elemental real(wp) function sign_algol(x)
real(wp),intent(in) :: x
sign_algol = merge(0.0_wp, sign(1.0_wp,x), x==0.0_wp)
end function sign_algol

Other Languages

Python doesn't have a built-in sign function, but the numpy one behaves exactly like the ALGOL one. The advantage of the Fortran one is that it does support signed zeros, which the Numpy one does not appear to support (for that you need to use copysign). C++ also has a copysign function that does the same thing.

References

  1. J. Oliver, "Algorithm 017: An algorithm for numerical differentiation of a function of one real variable", Journal of Computational and Applied Mathematics 6 (2) (1980) 145-160. Fortran 77 code from NIST.
  2. J. W. Backus, F. L. Bauer, J. Green, C. Katz, J. McCarthy, A. J. Perlis, H. Rutishauser, K. Samelson, B. Vauquois, J. H. Wegstein, A. van Wijngaarden, M. Woodger Editor: P. Naur, "Revised Report on the Algorithmic Language ALGOL 60", Communications of the ACM, Volume 6, Issue 1 Pages 1 - 17, 01 January 1963.
  3. SIGN — Sign copying function [gcc.gnu.org]
  4. Numerical Differentiation, degenerateconic.com, Dec 04, 2016

Jul 14, 2018

CMLIB

nbs

The National Bureau of Standards (NBS) Core Math Library (CMLIB) is a "collection of high-quality, easily transportable Fortran subroutine sublibraries solving standard problems in many areas of mathematics and statistics". It was written in FORTRAN 77 and is available from a NIST FTP link. The first version (1.0) was released in March 1986, and the last update (3.0) was in 1988. The software is no longer maintained, and I assume one day the link will probably disappear. It was compiled mostly from other externally-available libraries available at the time (including BLAS, EISPACK, FISHPAK, FNLIB, FFTPACK, LINPACK, and QUADPACK) but there are also some original codes. My Bspline-Fortran package is a modernized update and extension of the DTENSBS routines from CMLIB. The core spline interpolation routines (slightly updated) still work great after more than 30 years.

The National Bureau of Standards (NBS) was renamed the National Institute of Standards and Technology (NIST) in August 1988 (a few months after CMLIB was last updated). The nice thing about these old government-produced codes is that they are public domain in the United States. Indeed any software written by a U.S. government employee (but not a government contractor) as part of their official duties is automatically public domain. I generally prefer permissive software licenses, and you can't get more permissive than that.

See also

Mar 21, 2015

Conversion Factors

Conversion Factors

  • 1 lbm = 0.45359237 kg
  • 1 lbf = 4.4482216152605 N
  • 1 ft = 0.3048 m
  • 1 mile = 1.609344 km
  • 1 nmi = 1.852 km
  • 1 slug = 1 \(\mathrm{lbf} ~ \mathrm{s}^2 / \mathrm{ft} \approx\) 14.5939029 kg

References

Fortran Code

module conversion_factors

use, intrinsic :: iso_fortran_env, only: wp => real64 !double precision

implicit none

real(wp),parameter :: one = 1.0_wp

real(wp),parameter :: lbm2kg = 0.45359237_wp     ! exact
real(wp),parameter :: lbf2N = 4.4482216152605_wp !
real(wp),parameter :: ft2m = 0.3048_wp           !
real(wp),parameter :: mile2km = 1.609344_wp      !
real(wp),parameter :: nmi2km = 1.852_wp          !
real(wp),parameter :: slug2kg = lbf2N/ft2m       ! ~ 14.593902937206362

real(wp),parameter :: kg2lbm = one/lbm2kg        ! ~ 2.2046226218487757
real(wp),parameter :: N2lbf = one/lbf2N          ! ~ 0.2248089430997105
real(wp),parameter :: m2ft = one/ft2m            ! ~ 3.280839895013123
real(wp),parameter :: km2mile = one/mile2km      ! ~ 0.621371192237334
real(wp),parameter :: km2nmi = one/nmi2km        ! ~ 0.5399568034557235
real(wp),parameter :: kg2slug = one/slug2kg      ! ~ 0.06852176585679176

end module conversion_factors