6. Numeric Classes

Previous chapter

Next chapter

This chapter describes GemStone’s numeric classes. These include Integers, floating point (limited-precision rational numbers), fractions (arbitrary precision rational numbers), and decimal numbers. Most numbers can be specified as literals within your code, and most numbers can be used in expressions with, or converted to, other types of numbers.

Integers
Describes classes that represent whole numbers: SmallInteger and LargeInteger.

Binary Floating Point
Describes classes for binary floating point numbers: SmallDouble and Float.

Other Rational Numbers
Describes classes for other rational numbers with different ranges and precisions, including Fraction, FixedPoint, ScaledDecimal, and DecimalFloat.

Internationalizing Decimal Points using Locale
How to control the display of decimal points.

Random Number Generator
Information on the set of random number generator classes, providing random numbers of various purposes.

6.1 Integers

Integers in GemStone are composed of SmallIntegers and LargeIntegers. Most Integers you are likely to use will be SmallIntegers, in the range of -260 to 260 -1. Integers outside this range are represented by LargeIntegers. Operations that result in a value outside the SmallInteger range transparently result in LargeIntegers, and vice-versa

The literal syntax for Integer will create either a SmallInteger or LargeInteger.

Integers can be specified using radix notation, using the r or # characters.

For example, to specify the hex SmallInteger value FF, the following are all valid:

FFr16
FF#16
Number fromString: 'FFr16'
'ff#16' asNumber

SmallInteger

SmallIntegers are special (immediate) objects, that is, the number itself is encoded in the OOP, making instances of this class both small (since no further storage is required) and fast. They are also unique, so SmallIntegers of the same value are always identical (==) as well as equal (=).

SmallIntegers have a range from -260 to 260 -1. Values outside this range must be represented as LargeIntegers.

LargeInteger

LargeIntegers are not special objects; they require an OOP.

Each instance of LargeInteger is stored as an array of bytes, where every 4 bytes represents a base 4294967296 digit. The first 4 bytes are the sign digit (0 or 1), the next 4 bytes in that array constitute the least significant base 4294967296 digit, and the last 4 bytes are the most significant base 4294967296 digit.

Instances of LargeInteger have a maximum size of 4067 digits plus the sign. The maximum absolute value for a LargeInteger is (2130144 - 1). Attempting to create a LargeInteger that exceeds this maximum will fail with an Integer overflow error.

Printing Integers

Integers are printed by default, using Integer >> asString, in base 10. You may print using other bases by invoking printStringRadix: or printStringRadix:showRadix:.

For example,

1234 printStringRadix: 2 
%
10011010010
 
-1234 printStringRadix: 16 showRadix: true
%
-16r4D2

6.2 Binary Floating Point

Floating point values in GemStone are composed of SmallDoubles and Floats. The most commonly used floating points will be SmallDoubles. While both SmallDouble and Float represents 8-byte binary floating point numbers, as defined in IEEE standard 754, SmallDoubles have a reduced exponent range. Some floating point values therefore can only be represented by instances of Float, rather than SmallDouble. Similarly to SmallInteger and LargeInteger, GemStone operations return one or the other as needed.

The numerical behavior of instances of Float is implemented by the mathematics package of the vendor of the machine on which the Gem process is running. There are slight variations in results with different platform’s implementation of the
IEEE-754 standard.

You can get the components of a floating point value using the methods signBit,
exponent, and mantissa.

SmallDouble

SmallDoubles are special objects; as with SmallIntegers, the number itself is encoded in the OOP, making instances small and fast. They are also unique, so SmallDoubles of the same value are identical (==) as well as equal (=).

Each SmallDouble contains a 61 bit value, in IEEE format but with reduced exponent range. There is 1 sign bit, 8 bits of exponent and 52 bits of fraction. SmallDoubles are always in big-endian format (both on disk and in memory).

SmallDoubles can represent C doubles that have value zero or that have exponent bits in range 0x381 to 0x3ff, which corresponds to about 5.0e-39 to 6.0e38; approximately the range of C 4-byte floats.

Float

Floats are not special objects; they require an OOP.

Each Float contains a 64 bit value in IEEE format, with 1 sign bit, 11 bits of exponent and 52 bits of mantissa. Floats are in cpu-native byte order when in memory, and the byte order of the extent when on disk.

In addition to the finite numbers, the IEEE standard defines floating point formats to include Infinity (positive and negative) and NaNs (not a Number), which can be quiet or signaling. NaNs results from an operations whose result is not a real number, such as:

-23 sqrt
%
PlusQuietNaN

Infinity results from operations that return a value outside the range of representation, such as:

32.0 / 0
%
PlusInfinity

ExceptionalFloats are named, unique instances of Float, not of SmallDouble. Exceptional Floats include:

PlusInfinity
MinusInfinity
PlusQuietNaN
MinusQuietNaN
PlusSignalingNaN
MinusSignalingNaN

Since the sign of NaNs is not defined, GemStone operations return only positive NaNs; they do not return MinusQuietNan or MinusSignalingNan.

An unusual quality of NaNs is that they are not equal to themselves. This means that NaNs can cause problems if used as keys of hashed equality-based collections.

PlusQuietNaN = PlusQuietNaN
%
false

Signalling Exception rather than returning Exceptional Float

When performing operations on Floats, an ExceptionalFloat may not always be an appropriate result.

You can determine if a number is an ExceptionalFloat using the message #isExceptionalFloat.

You can configure your system to signal an exception, rather than return an ExceptionalFloat. The following are the types of Floating point error conditions that may arise:

  • #divideByZero
  • #overflow
  • #underflow
  • #invalidOperation
  • #inexactResult

FloatingPointError has protocol to configure signalling for all or none of these error conditions, or any subset. For example,

FloatingPointError enableAllExceptions.
FloatingPointError enableExceptions: { #divideByZero }

After enabling exceptions, exceptional conditions will signal errors, rather than returning an exceptional Float, for the duration of that session.

Example 6.1 Enabling floating point exceptions

topaz 1> run
3 / 0.0
%
PlusInfinity
topaz 1> run
FloatingPointError enableAllExceptions.
%
0
topaz 1>  run
3 / 0.0
%
ERROR 2724 , a FloatingPointError divideByZero (FloatingPointError)
 

Literal Floats

Literal numbers in evaluated code that include a decimal point by default create a SmallDouble or Float. If the value is in the SmallDouble range, a SmallDouble will be created, otherwise a Float will be created.

Literal floats may be specified using exponential notation. For example, 5.1e3 and 5.1e-3 are valid SmallDouble literals.

ANSI specifies that float values may have exponents e, d, or q. These exponents, as well as E and D, are legal in GemStone, but have the same result: a SmallDouble or Float. Likewise, the ANSI class names FloatE, FloatD, and FloatQ can be used in code, but all resolve to Float class.

Note that using a plus sign before the exponent is not allowed in literal floats, although it can be used to create floating points from strings (using Float fromString:). This avoids ambiguity with Smalltalk dialects that would interpret this as the addition operator. For example, 5.1E+3, which historically GemStone would interpret as the same as 5.1E3, is disallowed; code must either omit the +, or include white space to clarify the addition operator.

Printing Binary Floating Points

SmallDoubles and Floats are printed by default using asString or printString, in the notation equivalent to the C printf expression %.16g. This provides a maximum of 16 significant digits, rounding the fractional portion and changing to exponent notation if the whole number portion has more than 16 digits.

You can use asStringUsingFormat: to control the details of how floating point numbers are formatted when printing. asStringUsingFormat: accepts an Array of three elements:

  • an Integer between -1000 and 1000, specifying a minimum number of Characters in the result String. Negative arguments pad with blanks to the left, positive arguments pad to the right. Note that if the value of this element is not large enough to completely represent the Float, a longer String will be generated.
  • an Integer between 0 and 1000, specifying the number of digits to display to the right of the decimal point. If the printed representation of the float requires fewer characters, the result is padded with blanks on the right. If the value is insufficient to completely specify the float, the value is rounded to fit.
  • A Boolean indicating whether or not to display the magnitude using exponential notation. If true, exponential notation is used; if false, decimal notation.

For example:

12.3456 asString
%
12.3456
 
12.3456 asStringUsingFormat: #(-8 2 false) 
%
12.35
 
12.3456 asStringUsingFormat: #(4 10 true) 
%
1.2345600000e01

6.3 Other Rational Numbers

For some application, binary floating points are problematic, since there are common decimal values that cannot be expressed exactly in binary floating point; for example, 5.1 does not have a precise binary floating point representation. This can make computation results incorrect. For example:

5.1 * 100000
%
509999.9999999999

There are several options to avoid this: Fraction, FixedPoint, ScaledDecimal, and DecimalFloat. These classes are independent of each other, and each provides different qualities of precision and range.

Fractions

Fractions precisely represent rational numbers. Fractions are composed of an integer numerator and an integer denominator. As the ratio of two Integers, fractions can represent any rational number to an unbounded level of precision.

The display of fractions is as the numerator and denominator separated by the $/ character, which is also the division binary method. Fractions have no literal representation. An expression such as 1/3, which performs a division of two Integers, will return a fraction if the result is not an Integer.

(1/3) printString
%
1/3

Any expression, not just division expressions, that could result in fractions will be reduced automatically, to the lowest fraction or to an Integer.

(5/6) + (1/6)
%
1

SmallFraction

SmallFractions are special objects, in which the OOP itself encodes the value. As with SmallDouble and Float, creating a fraction will result in either an instance of SmallFraction or Fraction, depending on the specific value.

SmallFractions can hold objects with numerators between -536870912 and 536870911, and denominators from 1 to 134217727.

Fraction

If the numerator or denominator is outside the SmallFraction range, an instance of Fraction is created. These are not special objects.

FixedPoint

FixedPoints, like Fractions, represents rational numbers, but also include information on how they should be displayed. A FixedPoint is composed of an integer numerator, integer denominator, and an integer scale. Like Fraction, this allows rational numbers to be represented with unbounded precision, and since fractional arithmetic is used in calculations, numerical results do not lose precision.

The scale provides automatic rounding when representing the FixedPoint as a String.

FixedPoint uses a literal notation using p, such as 1.23p2. This is not an exponential notation; the 2 here specifies scale. The values 1.23p2, 1.23p3, and 1.23p4 are all equal.

ScaledDecimal

ScaledDecimals represent a decimal number to the precision of a fixed number of fractional digits. ScaledDecimals are composed of an integer mantissa and a power-of-10 scale. While ScaledDecimals represent decimal fractions to the precision specified, not all values can be represented exactly by ScaledDecimals. The maximum scale is 30000.

Literal ScaledDecimals can be created using the s notation; for example, 1.23s2. This is not an exponential notation; the 2 here is the scale, and mantissa is resized appropriately. The values 1.23s2, 1.23s3, and 1.23s4 are all equal.

The number of fractional digits must not be greater than the scale.

For returned values from mathematical operations, ANSI does not precisely specify the scale of a returned ScaledDecimal. The following rules are used:

  • For unary messages, the scale of the result equals the scale of the receiver.
  • For a one-argument message, the scale of the result is the greater of the scale of the receiver and argument. An integer receiver or argument coerced to a ScaledDecimal should effectively have a scale of zero, meaning the result will have the scale of the non-coerced ScaledDecimal argument or receiver.

For some mathematical operations, the returned value type is a ScaledDecimal, but the returned value cannot always be exactly represented as a ScaledDecimal with the correct scale. In these cases, the results are rounded using the following rules:

  • Following the example of IEEE754 float rounding, the ScaledDecimal that is answered is selected as though we computed the numerically exact value and then chose the closest representable ScaledDecimal of the scale specified by the rules. If the numerically exact value falls exactly halfway between two adjacent representable ScaledDecimal values of the scale specified by the rules, the ScaledDecimal with an even least significant digit is answered.

DecimalFloat

DecimalFloats represent base 10 floating point numbers, per IEEE standard 854-1987.

Literal DecimalFloats can be specified in exponential notation using the f or F character; for example, 5.432F2 creates a DecimalFloat equivalent to 543.2.

Objects of class DecimalFloat have 20 digits of precision, with an exponent in the range -15000 to +15000. The first byte encodes the sign and kind of the floating-point number. Bit 0 is the sign bit. The values in bits 1 through 3 indicate the kind of DecimalFloat:

001x = normal
010x = subnormal
011x = infinity
100x = zero
101x = quiet NaN
110x = signaling NaN

Bytes 2 and 3 encode the exponent as a biased 16-bit number (byte 2 is more significant). The actual exponent is calculated by subtracting 15000. Bytes 4 through 13 form the mantissa of the number. Each byte holds two BCD digits, with bits 4 through 7 of byte 4 containing the most significant digit.

Similarly to Float, operations that would not result in a real number, or that produce a result outside the representable range, result in Exceptional numbers:

DecimalPlusInfinity
DecimalMinusInfinity
DecimalPlusQuietNaN
DecimalMinusQuietNaN
DecimalPlusSignalingNaN
DecimalMinusSignalingNaN

You can determine if a number is an ExceptionalFloat using the message #isExceptionalFloat.

Summary of literal syntax

The following table lists the notations that may appear in a literal number.

#

radix notation

d, D

SmallDouble/Float exponential notation

e, E

SmallDouble/Float exponential notation

f, F

DecimalFloat exponential notation

p

FixedPoint notation

q

SmallDouble/Float exponential notation

s

ScaledDecimal notation

r

radix notation

Custom numeric literals

You can instruct the compiler to understand a new numerical literal format by sending a message to your customized subclass of Number to register that format.

The following method provides this registration:

Number >> parseLiterals: aCharacter exponentRequired: aBoolean

Once this is sent to an instance of a subclass of Number, when the compiler encounters a numeric value using aCharacter, it will send fromString: to that class.

  • The subclass of Number must implement fromString: in such a way as to be able to read the new literal format, and create the new instance.
  • aCharacter must an alphabetic Character with codePoint <= 127, and may not be an existing numeric literal character as listed in the table here.
  • aBoolean indicates if digits following the exponent are required or not.

For example, say you have defined a class ComplexNumber. For the literal format, you wish to use NiM, where N represent the real part and M represents the imaginary part. So for example, 4.5+5i would be specified using the literal form 4.5i5.

First, you would define the ComplexNumber>>fromString: method, which will parse a string of the form NiM and return the new instance of ComplexNumber.

Then, to allow the literals to be included in code, send the following message.

ComplexNumber parseLiterals: $i exponentRequired: true

Now, assuming you have implemented the behavior appropriately, the compiler can evaluate expressions of the form:

(3.5i5 + 7.1i3) asString
%
10.6i8.0

Once invoked, the new literal format will be recognized until the session logs out.

Note that for subsequent logins, compiled references to that literal will continue to be valid, but unless the method is invoked again, methods with that literal cannot be recompiled. Including the invocation of parseLiterals:exponentRequired: in session initialization code (such as using loginHook:) is recommended.

To uninstall a custom literal without logging out, use the same method, passing in for aBoolean. For example,

ComplexNumber parseLiterals: $i exponentRequired: nil

6.4 Internationalizing Decimal Points using Locale

The class Locale allows you to obtain operating system locale information and use or override it in GemStone. GemStone currently only uses the decimalPoint setting, to provide localized reading and writing of numbers involving decimal points. Updates to Locale are stored in session state, and only persist for the lifetime of the session. They are not affected by commit or abort.

Note that Smalltalk syntax requires the use of “.” as the decimal point separator, so expressions involving literal floating point numbers within Smalltalk code will still require use of the period, regardless of Locale.

To override the operating system locale information, use the following message:

Locale class >> setCategory: categorySymbol locale: LocaleString

Note that the LocaleString passed to setCategory:locale: must be defined on the host machine. If the given locale is not found, this method will return nil. You can use the UNIX command locale -a to get a list of all available LocaleStrings. To check the decimal point, the following method returns the decimalPoint setting for the current Locale:

Locale decimalPoint
%
,

While there are a number of Locale category symbols, the only ones that are of use in this release are #LC_NUMERIC and #LC_ALL, either of which will set the category that affects the decimal point.

For example, To use decimal localization appropriate for Germany:

Locale setCategory: #LC_NUMERIC locale: 'de_DE'.

To reset to UNIX default value, using period:

Locale setCategory: #LC_ALL locale: 'C'.

In order to be able to export and input numerical values regardless of the Locale of a particular session, methods whose printed form includes the decimal point provide the following set of methods:

(instance method) asStringLocaleC
(class method) fromStringLocaleC:

These methods use a period as a decimal separator, regardless of Locale.

6.5 Random Number Generator

The class Random and its subclasses provide random number generation.

There are two types of random number generation, which correspond to separate subclass hierarchies. The SeededRandom subclasses provide random numbers generated within GemStone code, using a starting seed value. The HostRandom subclass provides access to the host operating system’s /dev/urandom random number generator.

The class hierarchy of the Random classes are:

Object
	Random (abstract)
		HostRandom
		SeededRandom (abstract)
			Lag1MwcRandom
			Lag25000CmwcRandom
Random

The Random class is an abstract superclass for the random number generators. It also can be used to create an instance of a default random number generator class.

Random new will return an instance of HostRandom, the most basic kind of generator based on host OS /dev/urandom.

Random seed: will return an instance of Lag1MwcRandom. HostRandom does not support seeds.

While an instance of Lag25000CmwcRandom takes some time to create, it can produce in a more fair and longer-period series of random numbers that are generated much more quickly than is done by the other Random subclasses.

Once you have an instance of a concrete subclass of Random, you can generate random numbers or collections of random numbers with the following range and type specifications:

float - a random Float in the range [0,1)

floats: n - a collection of n random floats in the range [0,1)

integer - a random non-negative 32-bit integer, in the range [0,232-1]

integers: n - a collection of n random non-negative integers in the range [0,232-1]

integerBetween: l and: h - a random integer in the range [l,h]. l and h should be less than approximately 231.

integers: n between: l and: h - a collection of n random integers in the range [l,h]. l and h should be less than approximately 231.

smallInteger - Answer a random integer in the SmallInteger range,
[-260,260-1]

Subsequent calls to the same instance will generate new random numbers.

You should create an instance of a Random subclass and retain that to generate many random numbers, rather than creating new instances of a Random subclass.

HostRandom

HostRandom allows access to the host operating system's /dev/urandom random number generator.

HostRandom is much slower to generate numbers than the other subclasses of Random, but does not have the overhead of creating an instance. On some platforms, /dev/urandom may be intended to be a cryptographically secure random number generator, which none of the other subclasses are. It also has the advantage of not needing an initial seed, and so is good for generating random seeds for other Random subclasses.

HostRandom uses a shared singleton instance, which is accessed by sending #new to the class HostRandom. Sending #new has the side effect of opening the underlying file /dev/urandom. This file normally remains open for the life of the session, but if you wish to close it you can send #close to the instance, and later send #open to reopen it. If you store a persistent reference to the singleton instance the underlying file will not be open in a new session and you must send #open to the instance before asking for a random number.

Since HostRandom is a service from the operating system, it cannot be seeded, and should not be used when a repeatable random sequence of numbers is needed.

SeededRandom

SeededRandom is an abstract superclass for classes that generate sequences of random numbers that can be generated repeatedly by giving the same initial seed to the generator.

In addition to creating new instances using the class methods new and seed:, the following instance methods allow repeatable sequences to be generated:

seed: aSmallInteger
Sets the seed of the receiver from the given seed, which can be any SmallInteger. The subsequent random number sequence generated will be the same as if this generator had been created with this seed.

fullState, fullState: stateArray
The internal state of a generator is more than can be represented by a single SmallInteger. These messages allow you to retrieve the full state of a generator at any time, and to restore that state later. The random number sequence generated after the restoration of the state will be the same as that generated after the retrieval of the state. You might, for instance, allow a generator to get its initial state from /dev/urandom, then save this state so the random sequence can be repeated later.

Lag1MwcRandom

Lag1MwcRandom is faster to create than Lag25000CmwcRandom, since it can be seeded by a single 61-bit SmallInteger, rather than a seed of more than 800000 bits as required by Lag25000CmwcRandom. After creation, however, it is slower, and it is not perfectly fair, and has a shorter period. It can be used when a small number of seeded random numbers are needed.

Lag25000CmwcRandom

Lag25000CmwcRandom is a seedable random generator with a period of over 10240833. It is a lag-25000 generator using the complementary multiply-with-carry algorithm to generate random numbers. Its period is so long that every possible sequence of 24994 successive 32-bit integers appears somewhere in its output, making it suitable for generating random n-tuples where n<24994. Its output is fair in that the number of 0 bits and 1 bits in the full sequence are equal.

While this generator is recommended for most uses, it is not cryptographically secure, so for applications such as key generation you should consider using HostRandom, once you satisfy yourself that HostRandom is secure enough on your operating system.

You can also allow the seed bits to be initialized from the HostRandom, then retrieve that state by sending #fullState. That state can later be restored by sending the retrieved state as an argument to #fullState:.

 

Previous chapter

Next chapter