The shocking truth about floating point

Got a bit sidetracked again while writing some code to convert SVG paths to 6809 assembler suitable for a Vectrex (don’t ask), and ended up writing some code to convert numbers to words, with a view for adapting it at some point for TTS/PAF->grammar use.

Entertainingly, as I haven’t introduced any precision or rounding yet, entering – for instance  –  0.03f gives us what is actually stored in the float. You tend to forget that “0.03f” (at least on my PC) is actually more like “zero point zero two nine nine nine nine nine nine eight zero nine two six five one three six seven one eight seven five” …

Here’s the current, unoptimised, code. It requires a fairly recent version of Boost.

Snippet</pre>
<pre>#ifndef NUM_TO_WORDS_HPP_INCLUDED
#define NUM_TO_WORDS_HPP_INCLUDED

static std::string SpeakUnderOneHundred (uint16_t);
static std::string SpeakUnderOneThousand (uint16_t);

enum PrintScale
{
    Short,
    Long,
    LongWithMilliards
};

template <typename T> std::string numeric_to_words (T const & Input, PrintScale DisplayScale = Short, bool UseCommas=true)
{
    #if BOOST_VERSION >= 105500 
        BOOST_STATIC_ASSERT_MSG (boost::is_arithmetic<T>::value, "numeric_to_words can be only instantiated with arithmetic types");
    #else
        BOOST_STATIC_ASSERT (boost::is_arithmetic<T>::value);
    #endif

    const std::string ThousandsShortScale [] = {"", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion"};
    const std::string ThousandsLongScale [] = {"", "thousand", "million", "million", "billion", "billion", "trillion"};
    const std::string ThousandsLongScaleMB [] = {"", "thousand", "million", "milliard", "billion", "billiard", "trillion"};
    const uint8_t IsThousand = 40;  // 8 | 32
    
    const std::string * Thousands;

    const std::string single_digits [] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};

        const uint8_t MINIMUM_NOT_SET=0xFF;

    uint16_t PowerOf3Boxes [7];

    uint8_t MaxBox = 0, MinBox = MINIMUM_NOT_SET;

    std::string WriteAsString;

    uint8_t MagnitudeBlock = 7;

    uint64_t ValueAsInteger;

    T WorkingCopy = Input;
    
    switch (DisplayScale)
    {
        case Long : Thousands = &ThousandsLongScale [0]; break;
        case LongWithMilliards : Thousands = &ThousandsLongScaleMB [0]; break;
        default : Thousands = &ThousandsShortScale [0]; break;
    };

    #if BOOST_VERSION >= 104800
    if (boost::is_signed<T>::value)
    {
    #endif

        if  (Input < 0)
        {
            WriteAsString += "minus ";

            ValueAsInteger = (uint64_t) (0 - ((int64_t) Input));
        }
        else
            ValueAsInteger = (uint64_t) Input;
    #if BOOST_VERSION >= 104800
    }
    else
        ValueAsInteger = (uint64_t) Input;
    #endif

    if (ValueAsInteger)
    {
        WorkingCopy -= (T) ValueAsInteger;

        // Run forward to populate boxes and determine max/min.

        for (MaxBox = 0; MaxBox < 7; MaxBox++)
        {
        
            if ((PowerOf3Boxes [MaxBox] = (uint16_t) (ValueAsInteger - ((ValueAsInteger / 1000) * 1000))))
                if (MinBox == MINIMUM_NOT_SET) 
                    MinBox = MaxBox;

            ValueAsInteger -= PowerOf3Boxes [MaxBox];

            ValueAsInteger /= 1000;

            if (!ValueAsInteger) break;
        }

        MagnitudeBlock = MaxBox + 1;

        do
        {
            --MagnitudeBlock;

            if ((DisplayScale == Long) && ((1 << MagnitudeBlock) & IsThousand))
            {
                WriteAsString += numeric_to_words ((PowerOf3Boxes [MagnitudeBlock] * 1000) + PowerOf3Boxes [MagnitudeBlock - 1], Long, false);
                
                WriteAsString += " ";
                
                WriteAsString += Thousands [MagnitudeBlock];
                
                MagnitudeBlock --;
            }
            else
            {
                WriteAsString += SpeakUnderOneThousand (PowerOf3Boxes [MagnitudeBlock]);

                if (MagnitudeBlock) WriteAsString += " ";
                
                WriteAsString += Thousands [MagnitudeBlock];

            }

            
            if (MagnitudeBlock > MinBox)
            {
                if ((MagnitudeBlock == 1) && (PowerOf3Boxes [0] <= 99))
                    WriteAsString += " and";
                else if (UseCommas) WriteAsString += ",";
                
                WriteAsString += " ";
            }

        }
        while (MagnitudeBlock > MinBox);
    }
    else
    {
        // Zero

        WriteAsString += single_digits [0];
    }

    if (boost::is_float<T>::value)
    {
        if (WorkingCopy)
        {
            WriteAsString += " point";

            // Deal with decimal point.

            while (WorkingCopy)
            {
                WorkingCopy *= 10;

                WriteAsString += " " + single_digits [(int) WorkingCopy];

                WorkingCopy -= (int) WorkingCopy;
            }
        }
    }
    
    return WriteAsString;
}


static std::string SpeakUnderOneHundred (uint16_t DigitValue)
{
    const std::string Tens [] = {"", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"};

    const std::string Teens [] = {"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"};

    const std::string SingleDigits [] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};

    std::string WriteAsString;

    if (DigitValue < 10)
        WriteAsString += SingleDigits [DigitValue];
    else if (DigitValue < 20)
        WriteAsString += Teens [DigitValue - 10];
    else
    {
        if (DigitValue / 10) WriteAsString += Tens [DigitValue / 10];
        
        if ((DigitValue / 10) && (DigitValue % 10)) WriteAsString += " ";
        
        if (DigitValue % 10) WriteAsString += SingleDigits [DigitValue % 10];
    }

    return WriteAsString;
}


static std::string SpeakUnderOneThousand (uint16_t DigitValue)
{
    int Hundreds;

    std::string WriteAsString;

    Hundreds = (int) DigitValue / 100;

    if (Hundreds)
    {
        WriteAsString += SpeakUnderOneHundred (Hundreds) + " hundred";

        DigitValue %= (Hundreds * 100);

        if (DigitValue) WriteAsString += " and ";				
    }

    if (DigitValue)	WriteAsString += SpeakUnderOneHundred (DigitValue);

    return WriteAsString;

}

#endif

</pre>
<pre>

And some unit tests, should that sort of thing make your boat floaty. You’ll need UnitTest++.

Snippet</pre>
<pre>#include <cstdlib>
#include <cmath>
#include <limits>
#include <string>
#include <iostream>
#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <boost/version.hpp>
 
#include <UnitTest++.h>
 
#if (__cplusplus >= 201103L)
    #include <cstdint>
#else
    #include <stdint.h>
#endif
 
using namespace std;
using namespace UnitTest;
 
#include "../numtowords.hpp"
 
int main (void);
 
TEST (ShortScale)
{
    CHECK_EQUAL ("one quintillion, two hundred and thirty four quadrillion, five hundred and sixty seven trillion, eight hundred and ninety billion, one hundred and twenty three million, four hundred and fifty six thousand, seven hundred and eighty nine", numeric_to_words (1234567890123456789LL));
    CHECK_EQUAL ("zero", numeric_to_words (0));
    CHECK_EQUAL ("one", numeric_to_words (1));
    CHECK_EQUAL ("twenty", numeric_to_words (20));
    CHECK_EQUAL ("one hundred and one", numeric_to_words (101));
    CHECK_EQUAL ("one hundred and twenty", numeric_to_words (120));
    CHECK_EQUAL ("zero", numeric_to_words (0.0));
    CHECK_EQUAL ("zero point one", numeric_to_words (0.1));
    CHECK_EQUAL ("minus ten", numeric_to_words (-10));
    CHECK_EQUAL ("one thousand and one", numeric_to_words (1001));
    CHECK_EQUAL ("one thousand, one hundred and one", numeric_to_words (1101));
    CHECK_EQUAL ("one thousand", numeric_to_words (1000.0f));
    CHECK_EQUAL ("one billion, two hundred and thirty four million, five hundred and sixty seven thousand, eight hundred and ninety", numeric_to_words (1234567890));
}
 
TEST (LongScale)
{
    CHECK_EQUAL ("zero", numeric_to_words (0, Long));
    CHECK_EQUAL ("one", numeric_to_words (1, Long));
    CHECK_EQUAL ("twenty", numeric_to_words (20, Long));
    CHECK_EQUAL ("one hundred and one", numeric_to_words (101, Long));
    CHECK_EQUAL ("one hundred and twenty", numeric_to_words (120, Long));
    CHECK_EQUAL ("zero", numeric_to_words (0.0, Long));
    CHECK_EQUAL ("zero point one", numeric_to_words (0.1, Long));
    CHECK_EQUAL ("minus ten", numeric_to_words (-10, Long));
    CHECK_EQUAL ("one thousand and one", numeric_to_words (1001, Long));
    CHECK_EQUAL ("one thousand", numeric_to_words (1000.0f, Long));
    CHECK_EQUAL ("one thousand two hundred and thirty four million, five hundred and sixty seven thousand, eight hundred and ninety", numeric_to_words (1234567890, Long));
    CHECK_EQUAL ("one trillion, two hundred and thirty four thousand five hundred and sixty seven billion, eight hundred and ninety thousand one hundred and twenty three million, four hundred and fifty six thousand, seven hundred and eighty nine", numeric_to_words (1234567890123456789LL, Long));
}
 
TEST (LongScaleWithMilliards)
{
    CHECK_EQUAL ("zero", numeric_to_words (0, LongWithMilliards));
    CHECK_EQUAL ("one", numeric_to_words (1, LongWithMilliards));
    CHECK_EQUAL ("twenty", numeric_to_words (20, LongWithMilliards));
    CHECK_EQUAL ("one hundred and one", numeric_to_words (101, LongWithMilliards));
    CHECK_EQUAL ("one hundred and twenty", numeric_to_words (120, LongWithMilliards));
    CHECK_EQUAL ("zero", numeric_to_words (0.0, LongWithMilliards));
    CHECK_EQUAL ("zero point one", numeric_to_words (0.1, LongWithMilliards));
    CHECK_EQUAL ("minus ten", numeric_to_words (-10, LongWithMilliards));
    CHECK_EQUAL ("one thousand and one", numeric_to_words (1001, LongWithMilliards));
    CHECK_EQUAL ("one thousand", numeric_to_words (1000.0f, LongWithMilliards));
    CHECK_EQUAL ("one milliard, two hundred and thirty four million, five hundred and sixty seven thousand, eight hundred and ninety", numeric_to_words (1234567890, LongWithMilliards));
    CHECK_EQUAL ("one trillion, two hundred and thirty four billiard, five hundred and sixty seven billion, eight hundred and ninety milliard, one hundred and twenty three million, four hundred and fifty six thousand, seven hundred and eighty nine", numeric_to_words (1234567890123456789LL, LongWithMilliards));
}
 
TEST (ShortScaleNoCommas)
{
    CHECK_EQUAL ("one billion two hundred and thirty four million five hundred and sixty seven thousand eight hundred and ninety", numeric_to_words (1234567890, Short, false));
    CHECK_EQUAL ("one quintillion two hundred and thirty four quadrillion five hundred and sixty seven trillion eight hundred and ninety billion one hundred and twenty three million four hundred and fifty six thousand seven hundred and eighty nine", numeric_to_words (1234567890123456789LL, Short, false));
}
 
TEST (LongScaleNoCommas)
{
    CHECK_EQUAL ("one thousand two hundred and thirty four million five hundred and sixty seven thousand eight hundred and ninety", numeric_to_words (1234567890, Long, false));
    CHECK_EQUAL ("one trillion two hundred and thirty four thousand five hundred and sixty seven billion eight hundred and ninety thousand one hundred and twenty three million four hundred and fifty six thousand seven hundred and eighty nine", numeric_to_words (1234567890123456789LL, Long, false));
}
 
TEST (LongScaleWithMilliardsNoCommas)
{
    CHECK_EQUAL ("one milliard two hundred and thirty four million five hundred and sixty seven thousand eight hundred and ninety", numeric_to_words (1234567890, LongWithMilliards, false));
    CHECK_EQUAL ("one trillion two hundred and thirty four billiard five hundred and sixty seven billion eight hundred and ninety milliard one hundred and twenty three million four hundred and fifty six thousand seven hundred and eighty nine", numeric_to_words (1234567890123456789LL, LongWithMilliards, false));
}
 
 
int main (void)
{	
    int count, ret = UnitTest::RunAllTests ();
 
    for (count = -9999; count < 9999; count++) numeric_to_words (count);
 
    return ret;
}
</pre>
<pre>

EDIT : Fixed code tags. Again.

Beware the cloud

So I happened to randomly pick up Olaf Stapldon’s “Last And First Men” and chanced across this :

To this extent they were similar to the other living things, but they had also certain capacities which the other stock had lost at the very outset of its evolutionary career. Terrestrial organisms, and Martian organisms of the terrestrial type, maintained themselves as vital unities by means of nervous systems, or other forms of material contact between parts. In the most developed forms, an immensely complicated neural “telephone” system connected every part of the body with a vast central exchange, the brain. Thus on the earth a single organism was without exception a continuous system of matter, which maintained a certain constancy of form. But from the distinctively Martian subvital unit there evolved at length a very different kind of complex organism, in which material contact of parts was not necessary either to coordination of behaviour or unity of consciousness. These ends were achieved upon a very different physical basis. The ultra-microscopic subvital members were sensitive to all kinds of etherial vibrations, directly sensitive, in a manner impossible to terrestrial life; and they could also initiate vibrations. Upon this basis Martian life developed at length the capacity of maintaining vital organization as a single conscious individual without continuity of living matter. Thus the typical Martian organism was a cloudlet, a group of free-moving members dominated by a “group-mind.” But in one species individuality came to inhere, for certain purposes, not in distinct cloudlets only, but in a great fluid system of cloudlets. Such was the single-minded Martian host which invaded the Earth.

The Martian organism depended, so to speak, not on “telephone” wires, but on an immense crowd of mobile “wireless stations,” transmitting and receiving different wave-lengths according to their function. The radiation of a single unit was of course very feeble; but a great system of units could maintain contact with its wandering parts over a considerable distance.

One other important characteristic distinguished the dominant form of life on Mars. Just as a cell, in the terrestrial form of life, has often the power of altering its shape (whence the whole mechanism of muscular activity), so in the Martian form the free-floating ultra-microscopic unit might be specialized for generating around itself a magnetic field, and so either repelling or attracting its neighbours. Thus a system of materially disconnected units had a certain cohesion. Its consistency was something between a smoke-cloud and a very tenuous jelly. It had a definite, though ever-changing contour and resistant surface. By massed mutual repulsions of its constituent units it could exercise pressure on surrounding objects; and in its most concentrated form the Martian cloud-jelly could bring to bear immense forces which could also be controlled for very delicate manipulation. Magnetic forces were also responsible for the mollusc-like motion of the cloud as a whole over the ground, and again for the transport of lifeless material and living units from region to region within the cloud.

The magnetic field of repulsion and attraction generated by a subvital unit was much more restricted than its field of “wireless” communication. Similarly with organized systems of units. Thus each of the cloudlets which the Second Men saw in their sky was an independent motor unit; but also it was in a kind of “telepathic” communication with all its fellows. Indeed in every public enterprise, such as the terrestrial campaigns, almost perfect unity of consciousness was maintained within the limits of a huge field of radiation. Yet only when the whole population concentrated itself into a small and relatively dense cloud-jelly, did it become a single magnetic motor unit. The Martians, it should be noted, had three possible forms, or formations, namely: first, an “open order” of independent and very tenuous cloudlets in “telepathic” communication, and often in strict unity as a group mind; second, a more concentrated and less vulnerable corporate cloud; and third, an extremely concentrated and formidable cloud-jelly.”

So, The Cloud and nanotechnology haven’t actually eaten anyone yet (early days, possibly) but still – uncannily prescient for 1930.

 

First post!

So after procrastinating for a couple of decades, and on the morning of my 39th birthday, I’ve decided to get myself one of these “home page” thingies. Will be posting stuff about game remakes, Vectrex development, music, scotch eggs and anything else that comes to mind …