2

I am a university lecturer, and I will teach the Numerical Methods course this semester using Fortran 90/95 as the programming language. The beginning of the course starts with the representation of numbers, and I would like to talk about the limits of numbers that can be represented with REAL(4), REAL(8) and REAL(16). I intend to use the following code on OnlineGDB (so that students won't have to install anything on their computers, which may be a pain in times of remote learning):

Program declare_reals

implicit none 

real(kind = 4)  :: a_huge, a_tiny ! single precision ; default if kind not specified

!real(4) :: a ! Equivalent to real(kind = 4) :: a

a_huge = huge(a_huge)
print*, "Max positive for real(4) : ", a_huge
a_tiny = tiny(a_tiny)
print*, "Min positive for real(4) : ", a_tiny 
print*, 

End Program declare_reals

With this code, I get

 Max positive for real(4) :    3.40282347E+38                                                                                                                          
 Min positive for real(4) :    1.17549435E-38

However, if I write a_tiny = tiny(a_tiny)/2.0, the output becomes

Min positive for real(4) :    5.87747175E-39

Looking at the documentation for gfortran (which OnlineGDB uses as the f95 compiler), I had the impression that anything below tiny(x) could result in an underflow and zero would show instead of a non-zero number. Could anyone help me understand what is happening here? If tiny(x) doesn't yield the smallest positive representable number, what is being shown due to the function call?

  • To be sincere, no. Could you please explain? – Marcos Verissimo Alves Jan 10 '21 at 20:01
  • 1
    A few reads: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/800-7895/800-7895.pdf) and Wikipedia: [Denormal number](https://en.wikipedia.org/wiki/Denormal_number), [IEEE_754](https://en.wikipedia.org/wiki/IEEE_754). –  Jan 10 '21 at 20:08
  • 7
    Note, especially if you are teaching this, the use of 4 and 8 and other literal constants for kind values is not best practice, there is no guarantee that the compiler will support those values. As such there is no guarantee that the code above will compile. Look at using the parameters real32 and real64 from iso_fortran_env instead, these will work. – Ian Bush Jan 10 '21 at 20:16
  • Thanks for the tip, but the program did compile - please note that I supplied output in my question, which resulted from my running the code. I used iso_fortran_env and the real32 kind. Same result. It seems weird to me that since tiny(x) should give you the smallest positive number in the model, if you divide it by two it still shows an even smaller number... – Marcos Verissimo Alves Jan 10 '21 at 20:30
  • 4
    The fact that the code did compile now means nothing at all. The practice is simply wrong and the code will not compile, for example, with Simply Fortran or NAG with their default settings. Really, if you are teaching Fortran, do NOT teach using magic numbers like 4 8 or 16, see https://stackoverflow.com/questions/838310/fortran-90-kind-parameter While an independent programmer can do what he likes, if no one else works on that code, the standards and expectations for a teacher are much higher. – Vladimir F Героям слава Jan 10 '21 at 20:51
  • @Jean-ClaudeArbaut , I do not refuse good coding advice. In fact, I have downloaded the reference you recommended, and I was about to open the Wikipedia references you sent, too, but then I saw people were answering. Check my answer to Ian above - I immediately tried doing what he recommended (using iso_fortran env 's real32 constant) . Chill, man, chill. Be more patient with people. This is the first time I'm teaching this discipline, and I don't really do heavy programming. Your attitude really is not encouraging at all. – Marcos Verissimo Alves Jan 10 '21 at 20:57
  • @VladimirF, thanks. I'll check it out. – Marcos Verissimo Alves Jan 10 '21 at 21:00
  • Sorry if it was rude. To be fair, the concept of *model number* is a bit hidden in the Fortran standard (which is [here](https://j3-fortran.org/doc/year/18/18-007r1.pdf), but not an easy read for a beginner). A good way to understand what happens under the hood is to print the hexadecimal representation of floating point numbers and decipher the IEEE 754 representation, especially for a few key numbers (huge/small numbers, infinity, nan, epsilon, 2^k for positive and negative k, etc.), and what happens with 1+eps or 1+eps/2. The kind of stuff I was shown as a student, actually. –  Jan 10 '21 at 21:02
  • @Jean-ClaudeArbaut Ok. I wasn't shown this as a student, unfortunately. Doing my best to learn what I don't know deeply enough so I can teach my students better than I was taught. – Marcos Verissimo Alves Jan 10 '21 at 21:08
  • As a remark: There is a compiler option for ifort which sets all numbers smaller than tiny (measured by their magnitude) to 0. I recall that it was `-ftz` alias "flush to zero". There is probably something similar for gfortran. There are probably other options regarding denormal numbers. – jack Jan 10 '21 at 22:31
  • @Jean-ClaudeArbaut _A good way to understand what happens under the hood is to print the hexadecimal representation of floating point numbers and decipher the IEEE 754 representation_ ... This obviously makes the assumption that your CPU supports IEEE754, it could also be any of [these](http://www.quadibloc.com/comp/cp0201.htm). Also, even if it is IEEE754, it could still be [decimal64](https://en.wikipedia.org/wiki/Decimal64_floating-point_format). The beauty of Fortran is that it makes as little assumptions as possible. – kvantour Jan 11 '21 at 09:27
  • 1
    @kvantour Sure. But students need to begin with something, and preferably something widespread they can even experiment on their own machine. Never telling anything because it might be different on a PDP-4 is not a good way of teaching. The standard is purposedly avoiding to tell what happens in real life. Does not mean real life does not exist. –  Jan 11 '21 at 09:56

1 Answers1

4

The Fortran Standard states the following about a real value:

The model set for real x is defined by

model representation of a real according to Fortran

where b and p are integers exceeding one; each fk is a non-negative integer less than b, with f1 nonzero; s is +1 or −1; and e is an integer that lies between some integer maximum emax and some integer minimum emin inclusively. For x = 0, its exponent e and digits fk are defined to be zero. The integer parameters b, p, emin, and emax determine the set of model floating-point numbers.

Real values which satisfy this definition, are referenced to be model numbers or normal floating point numbers. The floating point numbers your system can represent, i.e. the machine-representable numbers are a superset of the model numbers. They can, but not necessarily must, include the values with f1 zero — also known as subnormal floating point numbers — and are there to fill the underflow gap around zero.

The Fortran functions tiny(x), huge(x), epsilon(x), spacing(x) are all defined for model numbers.

The value of tiny(x) is given by bemin − 1, which for a single-precision floating-point number (binary32) is given by 2−126 and is the smallest model (normal) number. When your system follows IEEE754, the machine representable numbers will also contain the subnormal numbers. The smallest subnormal positive number is given bytiny(x)*epsilon(x) which in binary32 is 2−126 × 2−23. This explains why you can divide tiny(x) by two, i.e. the transition from normal to subnormal.

# smallest normal number
0 00000001 000000000000000000000002 = 0080 000016 = 2−126 ≈ 1.1754943508 × 10−38
# smallest subnormal number
0 00000000 000000000000000000000012 = 0000 000116 = 2−126 × 2−23 ≈ 1.4012984643 × 10−45

Note: when you divide tiny(x)*epsilon(x) by two, gfortran returns an arithmetic underflow error.


Ref: values taken from Wikipedia: Single precision floating-point format

kvantour
  • 25,269
  • 4
  • 47
  • 72
  • The answer probably ought something about model numbers. My understanding is that those do not depend on the IEEE standard - and its definition of normal or denormal, just on those model parameters. – Vladimir F Героям слава Jan 11 '21 at 10:00
  • BTW the gradual underflow is a thing that not need to happen. And will not happen, for example, with -ffast-math with gfortran. Another interesting point is that spacing(0.) returns the same answer as tiny, in my gfortran, even though one defined using model numbers and the other using representable numbers, which are allowed to be denser. – Vladimir F Героям слава Jan 11 '21 at 10:16
  • @VladimirF I've added a bit on model numbers into the answer. – kvantour Jan 11 '21 at 10:33
  • 1
    OK, it was nearest, I confused nearest and spacing, Not sure why. Nearest does change with --ffast-math, even though in a strange way. – Vladimir F Героям слава Jan 11 '21 at 10:38