Home | Contact | Pricing | News | Partners | Mailing List | Site Map
Gnat Pro. Powerful tools. Frontline Support. Ada expertise.

Gem #7: The Beauty of Numeric Literals in Ada

Author: Franco Gasperoni (AdaCore)

Abstract: Ada Gem #7 — Did you know that the marvels of Ada extend to integer and real numbers (numeric literals really). Read on to learn about this Ada Gem.

« Previous Gem | Next Gem » | Gems Menu

Let’s get started…

On an unusually hot end-of-summer day some years ago, I stepped into the Courant Institute of Mathematical Sciences. It was my first day of class at New York University and my first day with Ada. The heat was pounding and the classroom air-conditioning unit was being repaired.

The breeze that day came from something simple and elegant: numeric literals in Ada. I was fortunate that my programming languages class started with something so cool. The first thing that struck me is the ability to use underscores to separate groups of digits. I always found

  3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37510

more readable and less error prone to type than

     3.14159265358979323846264338327950288419716939937510

what do you think? (I wish underscores were allowed when typing one’s credit card number on the internet.)

This is just a cool beginning. Let’s talk about based literals. I never understood the rationale behind the strange and unintuitive method to specify based literals in the C programming language where you have only 3 possible bases: 8, 10, and 16 (why no base 2?). Furthermore, requiring that numbers in base 8 be preceded by a zero feels like a bad joke on us programmers (what values do 0210 and 210 represent in C?)

To my pleasant surprise that humid end-of-summer day, I learnt that Ada allows any base from 2 to 16 and that we can write the decimal number 136 in any one of the following notations

        2#1000_1000#     8#210#     10#136#     16#88#

Coming from a microcontroller background where my I/O devices were memory mapped I liked the ability to write:

        Lights_On  : constant := 2#1000_1000#;
        Lights_Off : constant := 2#0111_0111#;

and have the ability to turn on/off the lights as follows:

   Output_Devices := Output_Devices  or   Lights_On;
   Output_Devices := Output_Devices  and  Lights_Off;

Of course we can also use records with representation clauses to do the above, which is even more elegant, and I leave that to a future gem (any volunteers out there?).

Back to based literals. The notion of base in Ada allows for exponents. That is particularly pleasant. For instance we can write:

     Kilobinary  : constant := 2#1#e+10;
     Megabinary  : constant := 2#1#e+20;
     Gigabinary  : constant := 2#1#e+30;
     Terabinary  : constant := 2#1#e+40;
     Petabinary  : constant := 2#1#e+50;
     Exabinary   : constant := 2#1#e+60;
     Zettabinary : constant := 2#1#e+70;
     Yottabinary : constant := 2#1#e+80;

In based literals the exponent, like the base, uses the regular decimal notation and specifies the power of the base that the based literal should be multiplied with to obtain the final value. For instance 2#1#e+10 = 1 x 210 = 1_024 (in base 10), whereas 16#F#e+2 = 15 x 162= 15 x 256 = 3_840 (in base 10).

Based numbers apply equally well to real literals. We can for instance write:

    One_Third : constant := 3#0.1#;  --  same as 1.0/3

Whether we write 3#0.1# or 1.0/3, or even 3#1.0#e-1, Ada allows us to specify exactly rational numbers for which decimal literals cannot be written.

This brings us to the last nice feature of Ada for this gem. As Bob Duff would put it: Ada has an open-ended set of integer and real types.

As a result, numeric literals in Ada do not carry with them their type as in C. The actual type of the literal is determined from the context. This is particularly helpful in avoiding overflows, underflows, and loss of precision (think about 32l in C, which is very different from 321).

And this is not all: all constant computations done at compile time are done in infinite precision be they integer or real. This allows us to write constants with whatever size and precision without having to worry about overflow or underflow. We can for instance write:

           Zero : constant := 1.0 - 3.0 * One_Third;

and be guaranteed that constant Zero has indeed value zero. This is very different from writing:

One_Third_Approx : constant := 0.33333333333333333333333333333;
Zero_Approx      : constant := 1.0 - 3.0 * One_Third_Approx;

where Zero_Approx is really 1.0e-29 (and that will show up in your numerical computations.) The above is quite handy when we want to write fractions without any loss of precision. Along these same lines we can write:

   Big_Sum : constant := 1           +
                         Kilobinary  +
                         Megabinary  +
                         Gigabinary  +
                         Terabinary  +
                         Petabinary  +
                         Exabinary   +
                         Zettabinary;

   Result : constant := (Yottabinary - 1) / (Kilobinary - 1);
   Nil : constant := Result - Big_Sum;

and be guaranteed that Nil is equal to zero.

But I am getting carried away by the elegance of Ada numeric literals and almost forgot about the date of our next gem which will be on the 2#10_10# of September (sorry I couldn’t resist :) .

Have a happy summer fellow developers.

Related Source Code

Ada Gems example files are distributed by AdaCore and may be used or modified for any purpose without restrictions.

application/x-ada-source
3.0Kb
 

Posted by Posted in Ada / Ada 2005, Development Log, Devt log - Gem of the Week

Have your own idea for a Gem?

If you have an idea for a Gem you would like to contribute please feel free to contact us at: gems@adacore.com

Discussion

8 responses to “Gem #7: The Beauty of Numeric Literals in Ada”


  1. Christoph Grein said:

    Franco,

    today, on the day of the Lord, the 7#40.6.5565#, I have to tell you the sad truth that your numbers like Kilobyte are wrong.

    Kilobyte = 1 kB = 10#1#e3 B, always,

    see .

    What you mean is Kibibyte = 1 KiB = 2#1#e10 B.

    Please note the capitalisation: k kilo, Ki kilobinary.

    It really makes a difference if you have 1 GiB or 1 GB. The difference amounts to 7#1_553_536_512# B.

    Christoph ;-)


  2. Christoph Grein said:

    Oops, your program ate the NIST address http://physics.nist.gov/cuu/Units/prefixes.html
    because I encluded it in “smaller” - “greater” brackets. Please see there for binary and decimal prefixes.


  3. Franco Gasperoni said:

    When I learnt all of this - in the late 70s - a kilobyte was 1_024 bytes. I found this kind of cute at the time and also kind of confusing.

    Thank you Christoph for bringing me up to date on this.

    I wonder whether it is common practice these days
    to talk about kilobinary bytes (or kibibytes :)

    By the way for everyone’s benefit on page:

    http://physics.nist.gov/cuu/Units/prefixes.html

    NIST writes:

    Because the SI prefixes strictly represent powers
    of 10, they should not be used to represent powers
    of 2. Thus, one kilobit, or 1 kbit, is 1_000 bit and
    not 210 bit = 1_024 bit. To alleviate this ambiguity,
    prefixes for binary multiples have been adopted by
    the International Electrotechnical Commission (IEC)
    for use in information technology.

    See

    http://physics.nist.gov/cuu/Units/binary.html

    for the details.

    I have changed the variable names so now instead of Kilobyte I use Kilobinary :)


  4. Christoph Grein said:

    “When I learnt all of this - in the late 70s - a kilobyte was 1_024 bytes. I found this kind of cute at the time and also kind of confusing.

    I wonder whether it is common practice these days
    to talk about kilobinary bytes (or kibibytes :)”

    No, I haven’t ever heard someone use these prefixes, which is kind of sad because of the confusion you mention.

    So I begin to spread the (old) news :-)


  5. Matthew Heaney said:

    The convention we use in the US is:

    kb = 1000
    Kb = 1024

    mb = 1000^2
    Mb = 1024^2

    etc


  6. Christoph Grein said:

    Not so good if you consider that capitalisation is generally looked upon as something optional.

    4 mS is Millisiemens, not milliseconds (which is meant and I see often).

    And on some devices, there are (still) no lower case characters.

    So what would 10 MB mean?


  7. Francisco J. Montoya said:

    Hello, Franco.

    I’m not sure whether I understood correctly that of “infinite precision” in compilation-time computations related to literals.

    In the line:

    Zero : constant := 1.0 - 3.0 * One_Third;

    , Zero is guaranteed to be 0.0 due only to the nature of the right-side operands of “:=”, or to the nature of the left-side argument (because Zero is a named number, and not a typed constant), or for both reasons?

    I mean, would it be 0.0 in the same way if Zero were a floating-point typed constant, instead of a named number?

    Thank you for your gem :-)


    Francisco J. Montoya


  8. Ville Witt said:

    Dear Mr. Francisco J. Montoya:
    Zero is guaranteed to be 0, because 3.0 * One_Third is 1 exactly. This is to show that One_Third is really 1/3, and not (with finite decimals of 3) 0.333

    What is 0.333 + 0.333 + 0.333 ? It’s of cause 0.999.
    But if you mean 1/3 + 1/3 + 1/3 you suspect the result to be 1. Ada let you stop wondering about the amounts of bit of the target architecture, and instead explain to the compile what you really mean.

    Sincerly Ville Witt (.net)

    P.S.: I’m sorry if I was wrong about the above - I myself is quite new to Ada and is fascinated by it. We need people to update their Ada-on-Linux tutorials!

Leave a Reply