NAME
Data::IEEE754::Tools - Various tools for understanding and manipulating the underlying IEEE-754 representation of floating point values
SYNOPSIS
use Data::IEEE754::Tools qw/:convertToString :ulp/;
# return -12.875 as strings of decimal or hexadecimal floating point numbers ("convertTo*Character" in IEEE-754 parlance)
convertToDecimalString(-12.875); # -0d1.6093750000000000p+0003
convertToHexString(-12.875); # -0x1.9c00000000000p+0003
# shows the smallest value you can add or subtract to 16.16 (ulp = "Unit in the Last Place")
print ulp( 16.16 ); # 3.5527136788005e-015
# toggles the ulp: returns a float that has the ULP of 16.16 toggled
# (if it was a 1, it will be 0, and vice versa);
# running it twice should give the original value
print $t16 = toggle_ulp( 16.16 ); # 16.159999999999997
print $v16 = toggle_ulp( $t16 ); # 16.160000000000000
DESCRIPTION
These tools give access to the underlying IEEE 754 floating-point 64bit representation used by many instances of Perl (see perlguts). They include functions for converting from the 64bit internal representation to a string that shows those bits (either as hexadecimal or binary) and back, functions for converting that encoded value into a more human-readable format to give insight into the meaning of the encoded values, and functions to manipulate the smallest possible change for a given floating-point value (which is the ULP or "Unit in the Last Place").
IEEE 754 Encoding
The IEEE 754 standard describes various floating-point encodings. The double format (`binary64') is a 64-bit base-2 encoding, and correpsonds to the usual Perl floating value (NV). The format includes the sign (s), the power of 2 (q), and a significand (aka, mantissa; the coefficient, c): value = ((-1)**s) * (c) * (2**q)
. The (-1)**s
term evaluates to the sign of the number, where s=0 means the sign is +1 and s=1 means the sign is -1.
For most numbers, the coefficient is an implied 1 plus an encoded fraction, which is itself encoded as a 52-bit integer divided by an implied 2**52. The range of valid exponents is from -1022 to +1023, which are encoded as an 11bit integer from 1 to 2046 (where exponent_value = exponent_integer - 1023
). With an 11bit integer, there are two exponent values (0b000_0000_0000 = 0 - 1023 = -1023
and 0b111_1111_1111 = 2047 - 1023 = +1024
), which are used to indicate conditions outside the normal range: The first special encoded-exponent, 0b000_0000_0000
, indicates that the coefficient is 0 plus the encoded fraction, at an exponent of -1022; thus, the floating-point zero is encoded using an encoded-exponent of 0 and an encoded-fraction of 0 ([0 + 0/(2**52)] * [2**-1022] = 0*(2**-1022) = 0
); other numbers smaller than can normally be encoded (so-called "denormals" or "subnormals"), lying between 0 and 1 (non-inclusive) are encoded with the same exponent, but have a non-zero encoded-fraction. The second special encoded-exponent, 0b111_1111_1111
, indicates a number that is infinite (too big to represent), or something that is not a number (NAN); infinities are indicated by that special exponent and an encoded-fraction of 0; NAN is indicated by that special exponent and a non-zero encoded-fraction.
Justification for the existence of Data::IEEE754::Tools
Data::IEEE754, or the equivalent "pack" in perlfunc recipe d>, do a good job of converting a perl floating value (NV) into the big-endian bytes that encode that value, but they don't help you interpret the value.
Data::Float has a similar suite of tools to Data::IEEE754::Tools, but uses numerical methods rather than accessing the underlying bits. It has been shown that its interpretation function can take an order of magnitude longer than a routine that manipulates the underlying bits to gather the information.
This Data::IEEE754::Tools module combines the two sets of functions, giving access to the raw IEEE 754 encoding, or a stringification of the encoding which interprets the encoding as a sign and a coefficient and a power of 2, or access to the ULP and ULP-manipulating features, all using direct bit manipulation when appropriate.
Compatibility
Data::IEEE754::Tools works with 64bit floating-point representations.
If you have a Perl setup which uses a larger representation (for example, use Config; print $Config{nvsize}; # 16 => 128bit
), values reported by this module will be reduced in precision to fit the 64bit representation.
If you have a Perl setup which uses a smaller representation (for example, use Config; print $Config{nvsize}; # 4 => 32bit
), the installation will likely fail, because the unit tests were not set up for lower precision inputs. However, forcing the installation might still allow coercion from the smaller Perl NV into a true IEEE 754 double (64bit) floating-point, but there is no guarantee it will work.
INTERFACE NOT YET STABLE
Please note: the interface to this module is not yet stable. There may be changes to function naming conventions (under_scores vs. camelCase, argument order, etc). Once Data::IEEE754::Tools hits v1.000, the interface should be stable for all sub-versions of v1: existing functions should keep the same calling conventions, though new functions may be added; significant changes to the interface will cause a transition to v2.
v0.013_003
nextup()
renamed tonextUp()
nextdown()
renamed tonextDown()
nextafter()
renamed tonextAfter()
v0.013_008
absolute()
renamed toabs()
, and noted that perl's builtin can be accessed viaCORE::abs()
v0.14001
messed up version numbering convention. Fixed at v0.016.
v0.017_002
:floatingpoint
renamed to:convertToString
to_hex_floatingpoint()
renamed toconvertToHexString()
(orData::Tools::IEEE754::binary64_convertToHexCharacter()
)to_dec_floatingpoint()
renamed toconvertToDecimalString()
(orData::Tools::IEEE754::binary64_convertToDecimalCharacter()
):raw754
renamed to:internalString
hexstr754_from_double()
renamed toconvertToInternalHexString()
binstr754_from_double()
renamed toconvertToInternalBinaryString()
hexstr754_to_double()
renamed toconvertFromInternalHexString()
binstr754_to_double()
renamed toconvertFromInternalBinaryString()
For backward compatibility, the old names are available, but the new names are recommended.
EXPORTABLE FUNCTIONS AND VARIABLES
:internalString
These are the functions to do raw conversion from a floating-point value to a hexadecimal or binary string of the underlying IEEE754 encoded value, and back.
convertToInternalHexString( value )
Converts the floating-point value into a big-endian hexadecimal representation of the underlying IEEE754 encoding.
convertToInternalHexString(12.875); # 4029C00000000000
# ^^^
# : ^^^^^^^^^^^^^
# : :
# : `- fraction
# :
# `- sign+exponent
The first three nibbles (hexadecimal digits) encode the sign and the exponent. The sign is the most significant bit of the three nibbles (so AND the first nibble with 8; if it's true, the number is negative, else it's positive). The remaining 11 bits of the nibbles encode the exponent: convert the 11bits to decimal, then subtract 1023. If the resulting exponent is -1023, it indicates a zero or denormal value; if the exponent is +1024, it indicates an infinite (Inf) or not-a-number (NaN) value, which are generally used to indicate the calculation has grown to large to fit in an IEEE754 double (Inf) or has tried an performed some other undefined operation (divide by zero or the logarithm of a zero or negative value) (NaN).
The final thirteen nibbles are the encoding of the fractional value (usually 1 + thirteennibbles / 16**13
, unless it's zero, denormal, infinite, or not a number).
Of course, this is easier to decode using the "convertToDecimalString()" function, which interprets the sign, fraction, and exponent for you. (See below for more details.)
convertToDecimalString(12.875); # +0d1.6093750000000000p+0003
# ^ ^^^^^^^^^^^^^^^^^^ ^^^^
# : : :
# : `- coefficient `- exponent (power of 2)
# :
# `- sign
convertToInternalBinaryString( value )
Converts the floating-point value into a big-endian binary representation of the underlying IEEE754 encoding.
convertToInternalBinaryString(12.875); # 0100000000101001110000000000000000000000000000000000000000000000
# ^
# `- sign
# ^^^^^^^^^^^
# `- exponent
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# `- fraction
The first bit is the sign, the next 11 are the exponent's encoding
convertFromInternalHexString( str )
The inverse of convertToInternalHexString()
: it takes a string representing the 16 nibbles of the IEEE754 double value, and converts it back to a perl floating-point value.
print convertFromInternalHexString('4029C00000000000');
12.875
convertFromInternalBinaryString( str )
The inverse of convertToInternalBinaryString()
: it takes a string representing the 64 bits of the IEEE754 double value, and converts it back to a perl floating-point value.
print convertFromInternalBinaryString('0100000000101001110000000000000000000000000000000000000000000000');
12.875
:convertToString
convertToHexString( value [, conversionSpecification] )
convertToDecimalString( value [, conversionSpecification] )
Converts value to a hexadecimal or decimal floating-point notation that indicates the sign and the coefficient and the power of two, with the coefficient either in hexadecimal or decimal notation.
convertToHexString(-3.9999999999999996) # -0x1.fffffffffffffp+0001
convertToDecimalString(-3.9999999999999996) # -0d1.9999999999999998p+0001
The optional conversionSpecification argument is an integer specifying the number of digits after the fractional-point. By default, convertToHexString()
uses 13 hex-digits and convertToDecimalString()
uses 16 decimal-digits, because those are the minimum number of digits to always distinguish one ULP; if you choose the conversionSpecification below default, it will round your results; if you choose the conversionSpecification above default, the correctness of digits beyond the default is not guaranteed (it may even round back to 13 hex-digits or 16 decimal-digits).
convertToHexString(-3.9999999999999996, 16) # -0x1.fffffffffffff000p+0001
convertToHexString(-3.9999999999999996, 10) # -0x2.0000000000p+0001
convertToDecimalString(-3.9999999999999996, 18) # -0d1.999999999999999778p+0001 (the last three digits may be different on your system, or may round to -0d1.9999999999999998p+0001)
convertToDecimalString(-3.9999999999999996, 10) # -0d2.0000000000p+0001
interpretation
It displays the value as (sign)(0base)(implied).(fraction)p(exponent):
- sign
-
The sign will be + or -
- 0base
-
The 0base will be
0x
for hexadecimal,0d
for decimal - implied.fraction
-
The implied.fraction indicates the hexadecimal or decimal equivalent for the coefficient
implied will be 0 for zero or denormal numbers, 1 for everything else
fraction will indicate infinities (#INF), signaling not-a-numbers (#SNAN), and quiet not-a-numbers (#QNAN).
implied.fraction will range from decimal 0.0000000000000000 to 0.9999999999999998 for zero thru all the denormals, and from 1.0000000000000000 to 1.9999999999999998 for normal values.
- p
-
The p introduces the "power" of 2. (It is analogous to the
e
in1.0e3
introducing the power of 10 in a standard decimal floating-point notation, but indicates that the exponent is 2**exp instead of 10**exp.) - exponent
-
The exponent is the power of 2. Is is always a decimal number, whether the coefficient's base is hexadecimal or decimal.
+0d1.500000000000000p+0010 = 1.5 * (2**10) = 1.5 * 1024.0 = 1536.0.
The exponent can range from -1022 to +1023.
Internally, the IEEE 754 representation uses the encoding of -1023 for zero and denormals; to aid in understanding the actual number, the
:convertToCharacter
conversions represent them as +0000 for zero, and -1022 for denormals: since denormals are(0+fraction)*(2**min_exp)
, they are really multiples of 2**-1022, not 2**-1023.
For backward compatibility, if you use the older tag :floatingpoint
, you can refer to these functions as to_hex_floatingpoint()
and to_dec_floatingpoint()
.
Data::IEEE754::Tools::binary64_convertToHexCharacter( value )
Data::IEEE754::Tools::binary64_convertToDecimalCharacter( value )
These are the same functions, but under the official IEEE 754 nomenclature of <type>_convertTo*Character()
. These are included for the "canonical" naming convention, but are not exportable.
:constants
These can be useful as references for the specialty values, and include the positive and negative zeroes, infinities, a variety of signaling and quiet NAN values.
POS_ZERO # +0x0.0000000000000p+0000 # signed zero (positive)
POS_DENORM_SMALLEST # +0x0.0000000000001p-1022 # smallest positive value that requires denormal representation in 64bit floating-point
POS_DENORM_BIGGEST # +0x0.fffffffffffffp-1022 # largest positive value that requires denormal representation in 64bit floating-point
POS_NORM_SMALLEST # +0x1.0000000000000p-1022 # smallest positive value that allows for normal representation in 64bit floating-point
POS_NORM_BIGGEST # +0x1.fffffffffffffp+1023 # largest positive value that allows for normal representation in 64bit floating-point
POS_INF # +0x1.#INF000000000p+0000 # positive infinity: indicates that the answer is out of the range of a 64bit floating-point
POS_SNAN_FIRST # +0x1.#SNAN00000000p+0000 # positive signaling NAN with "0x0000000000001" as the system-dependent information [*]
POS_SNAN_LAST # +0x1.#SNAN00000000p+0000 # positive signaling NAN with "0x7FFFFFFFFFFFF" as the system-dependent information [*]
POS_IND # +0x1.#QNAN00000000p+0000 # positive quiet NAN with "0x8000000000000" as the system-dependent information [%]
POS_QNAN_FIRST # +0x1.#QNAN00000000p+0000 # positive quiet NAN with "0x8000000000001" as the system-dependent information
POS_QNAN_LAST # +0x1.#QNAN00000000p+0000 # positive quiet NAN with "0xFFFFFFFFFFFFF" as the system-dependent information
NEG_ZERO # -0x0.0000000000000p+0000 # signed zero (negative)
NEG_DENORM_SMALLEST # -0x0.0000000000001p-1022 # smallest negative value that requires denormal representation in 64bit floating-point
NEG_DENORM_BIGGEST # -0x0.fffffffffffffp-1022 # largest negative value that requires denormal representation in 64bit floating-point
NEG_NORM_SMALLEST # -0x1.0000000000000p-1022 # smallest negative value that allows for normal representation in 64bit floating-point
NEG_NORM_BIGGEST # -0x1.fffffffffffffp+1023 # largest negative value that allows for normal representation in 64bit floating-point
NEG_INF # -0x1.#INF000000000p+0000 # negative infinity: indicates that the answer is out of the range of a 64bit floating-point
NEG_SNAN_FIRST # -0x1.#SNAN00000000p+0000 # negative signaling NAN with "0x0000000000001" as the system-dependent information [*]
NEG_SNAN_LAST # -0x1.#SNAN00000000p+0000 # negative signaling NAN with "0x7FFFFFFFFFFFF" as the system-dependent information [*]
NEG_IND # -0x1.#IND000000000p+0000 # negative quiet NAN with "0x8000000000000" as the system-dependent information [%]
NEG_QNAN_FIRST # -0x1.#QNAN00000000p+0000 # negative quiet NAN with "0x8000000000001" as the system-dependent information
NEG_QNAN_LAST # -0x1.#QNAN00000000p+0000 # negative quiet NAN with "0xFFFFFFFFFFFFF" as the system-dependent information
[*] note that many perl interpreters will internally convert Signalling NaN (SNAN) to Quiet NaN (QNAN)
[%] some perl interpreters define the zeroeth negative Quiet NaN, NEG_IND, as an "indeterminate" value (IND);
in a symmetrical world, they would also define the zeroeth positive Quiet NaN, POS_IND, as an "indeterminate" value (IND)
:ulp
ulp( value )
Returns the ULP ("Unit in the Last Place") for the given value, which is the smallest number that you can add to or subtract from value and still be able to discern a difference between the original and modified. Under normal (or denormal) circumstances, ulp($val) + $val > $val
is true.
If the value is a zero or a denormal, ulp()
will return the smallest possible denormal.
Since INF and NAN are not really numbers, ulp()
will just return the same value. Because of the way they are handled, ulp($val) + $val > $val
no longer makes sense (infinity plus anything is still infinity, and adding NAN to NAN is not numerically defined, so a numerical comparison is meaningless on both).
toggle_ulp( value )
Returns the orginal value, but with the ULP toggled. In other words, if the ULP bit was a 0, it will return a value with the ULP of 1 (equivalent to adding one ULP to a positive value); if the ULP bit was a 1, it will return a value with the ULP of 0 (equivalent to subtracting one ULP from a positive value). Under normal (or denormal) circumstances, toggle_ulp($val) != $val
is true.
Since INF and NAN are not really numbers, ulp()
will just return the same value. Because of the way they are handled, toggle_ulp($val) != $val
no longer makes sense.
nextUp( value )
Returns the next floating point value numerically greater than value; that is, it adds one ULP. Returns infinite when value is the highest normal floating-point value. Returns value when value is positive-infinite or NAN; returns the largest negative normal floating-point value when value is negative-infinite.
nextUp
is an IEEE 754r standard function (754-2008 #5.3.1).
nextDown( value )
Returns the next floating point value numerically lower than value; that is, it subtracts one ULP. Returns -infinity when value is the largest negative normal floating-point value. Returns value when value is negative-infinite or NAN; returns the largest positive normal floating-point value when value is positive-infinite.
nextDown
is an IEEE 754r standard function (754-2008 #5.3.1).
nextAfter( value, direction )
Returns the next floating point value after value in the direction of direction. If the two are identical, return direction; if direction is numerically above float, return nextUp(value)
; if direction is numerically below float, return nextDown(value)
.
:info
The informational functions include various operations (defined in 754-2008 #5.7.2) that provide general information about the floating-point value: most define whether a value is a special condition of floating-point or not (such as normal, finite, zero, ...).
isSignMinus( value )
Returns 1 if value has negative sign (even applies to zeroes and NaNs); otherwise, returns 0.
isNormal( value )
Returns 1 if value is a normal number (not zero, subnormal, infinite, or NaN); otherwise, returns 0.
isFinite( value )
Returns 1 if value is a finite number (zero, subnormal, or normal; not infinite or NaN); otherwise, returns 0.
isZero( value )
Returns 1 if value is positive or negative zero; otherwise, returns 0.
isSubnormal( value )
Returns 1 if value is subnormal (not zero, normal, infinite, nor NaN); otherwise, returns 0.
isInfinite( value )
Returns 1 if value is positive or negative infinity (not zero, subnormal, normal, nor NaN); otherwise, returns 0.
isNaN( value )
Returns 1 if value is NaN (not zero, subnormal, normal, nor infinite); otherwise, returns 0.
isSignaling( value )
Returns 1 if value is a signaling NaN (not zero, subnormal, normal, nor infinite), otherwise, returns 0.
Note that some perl implementations convert some or all signaling NaNs to quiet NaNs, in which case, isSignaling
might return only 0.
isSignalingConvertedToQuiet()
Returns 1 if your implementation of perl converts a SignalingNaN to a QuietNaN, otherwise returns 0.
This is not a standard IEEE 754 function; but this is used to determine if the isSignaling()
function is meaningful in your implementation of perl.
isCanonical( value )
Returns 1 to indicate that value is Canonical.
Per IEEE Std 754-2008, "Canonical" is the "preferred" encoding. Based on the Data::IEEE754::Tools's author's reading of the standard, non-canonical applies to decimal floating-point encodings, not the binary floating-point encodings that Data::IEEE754::Tools handles. Since there are not multiple choicesfor the representation of a binary-encoded floating-point, all values seem canonical, and thus return 1.
class( value )
Returns the "class" of the value:
signalingNaN
quietNaN
negativeInfinity
negativeNormal
negativeSubnormal
negativeZero
positiveZero
positiveSubnormal
positiveNormal
positiveInfinity
radix( value )
Returns the base (radix) of the internal floating point representation. This module works with the binary floating-point representations, so will always return 2.
totalOrder( x, y )
Returns TRUE if x ≤ y, FALSE if x > y.
Special cases are ordered as below:
-quietNaN < -signalingNaN < -infinity < ...
... < -normal < -subnormal < -zero < ...
... < +zero < +subnormal < +normal < ...
... < +infinity < +signalingNaN < +quietNaN
totalOrderMag( x, y )
Returns TRUE if abs(x) ≤ abs(y), otherwise FALSE. Equivalent to
totalOrder( abs(x), abs(y) )
Special cases are ordered as below:
zero < subnormal < normal < infinity < signalingNaN < quietNaN
compareFloatingValue( x, y )
compareFloatingMag( x, y )
These are similar to totalOrder()
and totalOrderMag()
, except they return -1 for x < y
, 0 for x == y
, and +1 for x > y
.
These are not in IEEE 754-2008, but are included as functions to replace the perl spaceship (<=>
) when comparing floating-point values that might be NaN.
:signbit
These functions, from IEEE Std 754-2008, manipulate the sign bits of the argument(s)set P.
See IEEE Std 754-2008 #5.5.1 "Sign bit operations": This section asserts that the sign bit operations (including negate
, abs
, and copySign
) should only affect the sign bit, and should treat numbers and NaNs alike.
copy( value )
Copies the value to the output, leaving the sign bit unchanged, for all numbers and NaNs.
negate( value )
Reverses the sign bit of value. (If the sign bit is set on value, it will not be set on the output, and vice versa; this will work on signed zeroes, on infinities, and on NaNs.)
abs( value )
Similar to the CORE::abs()
builtin function, abs()
is provided as a module-based function to get the absolute value (magnitude) of a 64bit floating-point number.
The CORE::abs()
function behaves properly (per the IEEE 754 description) for all classes of value, except that many implementations do not correctly handle -NaN properly, outputting -NaN, which is in violation of the standard. The Data::IEEE754::Tools::abs()
function correctly treats NaNs in the same way it treats numerical values, and clears the sign bit on the output.
Please note that exporting abs()
or :signbit
from this module will "hide" the builtin abs()
function. If you really need to use the builtin version (for example, you care more about execution speed than its ability to find the absolute value of a signed NaN), then you may call it as CORE::abs
.
copySign( x, y )
Copies the sign from y, but uses the value from x. For example,
$new = copySign( 1.25, -5.5); # $new is -1.25: the value of x, but the sign of y
also exports isSignMinus(
value )
(see :info)
(:signbit also exports the isSignMinus()
function, described in :info, above)
:all
Include all of the above.
INSTALLATION
To install this module, use your favorite CPAN client.
For a manual install, type the following:
perl Makefile.PL
make
make test
make install
(On Windows machines, you may need to use "dmake" or "gmake" instead of "make", depending on your setup.)
SEE ALSO
What Every Compute Scientist Should Know About Floating-Point Arithmetic
Perlmonks: Integers sometimes turn into Reals after substraction for inspiring me to go down the IEEE754-expansion trail in perl.
Perlmonks: Exploring IEEE754 floating point bit patterns as a resource for how perl interacts with the various "edge cases" (+/-infinity, denormalized numbers, signaling and quiet NaNs (Not-A-Number).
Data::IEEE754: I really wanted to use this module, but it didn't get me very far down the "Tools" track, and included a lot of overhead modules for its install/test that I didn't want to require for Data::IEEE754::Tools. However, I was inspired by its byteorder-dependent anonymous subs (which were in turn derived from Data::MessagePack::PP); they were more efficient, on a per-call-to-subroutine basis, than my original inclusion of the if(byteorder) in every call to the sub.
Data::Float: Similar to this module, but uses numeric manipulation.
AUTHOR
Peter C. Jones <petercj AT cpan DOT org>
Please report any bugs or feature requests emailing <bug-Data-IEEE754-Tools AT rt.cpan.org>
or thru the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Data-IEEE754-Tools, or thru the repository's interface at https://github.com/pryrt/Data-IEEE754-Tools/issues.
COPYRIGHT
Copyright (C) 2016-2017 Peter C. Jones
LICENSE
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.