Data::IEEE754::Tools - Various tools for understanding and manipulating the underlying IEEE-754 representation of floating point values
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
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").
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.
value = ((-1)**s) * (c) * (2**q)
(-1)**s
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.
exponent_value = exponent_integer - 1023
0b000_0000_0000 = 0 - 1023 = -1023
0b111_1111_1111 = 2047 - 1023 = +1024
0b000_0000_0000
[0 + 0/(2**52)] * [2**-1022] = 0*(2**-1022) = 0
0b111_1111_1111
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.
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.
use Config; print $Config{nvsize}; # 16 => 128bit
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.
use Config; print $Config{nvsize}; # 4 => 32bit
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 to nextUp()
nextup()
nextUp()
nextdown() renamed to nextDown()
nextdown()
nextDown()
nextafter() renamed to nextAfter()
nextafter()
nextAfter()
v0.013_008
absolute() renamed to abs(), and noted that perl's builtin can be accessed via CORE::abs()
absolute()
abs()
CORE::abs()
v0.14001
messed up version numbering convention. Fixed at v0.016.
v0.017_002
:floatingpoint renamed to :convertToString
:floatingpoint
:convertToString
to_hex_floatingpoint() renamed to convertToHexString() (or Data::Tools::IEEE754::binary64_convertToHexCharacter())
to_hex_floatingpoint()
convertToHexString()
Data::Tools::IEEE754::binary64_convertToHexCharacter()
to_dec_floatingpoint() renamed to convertToDecimalString() (or Data::Tools::IEEE754::binary64_convertToDecimalCharacter())
to_dec_floatingpoint()
convertToDecimalString()
Data::Tools::IEEE754::binary64_convertToDecimalCharacter()
:raw754 renamed to :internalString
:raw754
:internalString
hexstr754_from_double() renamed to convertToInternalHexString()
hexstr754_from_double()
convertToInternalHexString()
binstr754_from_double() renamed to convertToInternalBinaryString()
binstr754_from_double()
convertToInternalBinaryString()
hexstr754_to_double() renamed to convertFromInternalHexString()
hexstr754_to_double()
convertFromInternalHexString()
binstr754_to_double() renamed to convertFromInternalBinaryString()
binstr754_to_double()
convertFromInternalBinaryString()
For backward compatibility, the old names are available, but the new names are recommended.
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.
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).
1 + thirteennibbles / 16**13
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
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
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
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
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
It displays the value as (sign)(0base)(implied).(fraction)p(exponent):
The sign will be + or -
The 0base will be 0x for hexadecimal, 0d for decimal
0x
0d
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.
The p introduces the "power" of 2. (It is analogous to the e in 1.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.)
e
1.0e3
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.
:convertToCharacter
(0+fraction)*(2**min_exp)
For backward compatibility, if you use the older tag :floatingpoint, you can refer to these functions as to_hex_floatingpoint() and to_dec_floatingpoint().
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.
<type>_convertTo*Character()
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)
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.
ulp($val) + $val > $val
If the value is a zero or a denormal, ulp() will return the smallest possible denormal.
ulp()
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).
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.
toggle_ulp($val) != $val
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.
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).
nextUp
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).
nextDown
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).
nextUp(value)
nextDown(value)
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, ...).
Returns 1 if value has negative sign (even applies to zeroes and NaNs); otherwise, returns 0.
Returns 1 if value is a normal number (not zero, subnormal, infinite, or NaN); otherwise, returns 0.
Returns 1 if value is a finite number (zero, subnormal, or normal; not infinite or NaN); otherwise, returns 0.
Returns 1 if value is positive or negative zero; otherwise, returns 0.
Returns 1 if value is subnormal (not zero, normal, infinite, nor NaN); otherwise, returns 0.
Returns 1 if value is positive or negative infinity (not zero, subnormal, normal, nor NaN); otherwise, returns 0.
Returns 1 if value is NaN (not zero, subnormal, normal, nor infinite); otherwise, returns 0.
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.
isSignaling
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.
isSignaling()
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.
Returns the "class" of the value:
signalingNaN quietNaN negativeInfinity negativeNormal negativeSubnormal negativeZero positiveZero positiveSubnormal positiveNormal positiveInfinity
Returns the base (radix) of the internal floating point representation. This module works with the binary floating-point representations, so will always return 2.
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
Returns TRUE if abs(x) ≤ abs(y), otherwise FALSE. Equivalent to
totalOrder( abs(x), abs(y) )
zero < subnormal < normal < infinity < signalingNaN < quietNaN
These are similar to totalOrder() and totalOrderMag(), except they return -1 for x < y, 0 for x == y, and +1 for x > y.
totalOrder()
totalOrderMag()
x < y
x == y
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.
<=>
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.
negate
abs
copySign
Copies the value to the output, leaving the sign bit unchanged, for all numbers and NaNs.
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.)
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.
Data::IEEE754::Tools::abs()
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.
:signbit
CORE::abs
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
isSignMinus(
)
(:signbit also exports the isSignMinus() function, described in :info, above)
isSignMinus()
Include all of the above.
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.)
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.
Peter C. Jones <petercj AT cpan DOT org>
<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.
<bug-Data-IEEE754-Tools AT rt.cpan.org>
Copyright (C) 2016-2017 Peter C. Jones
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.
To install Data::IEEE754::Tools, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Data::IEEE754::Tools
CPAN shell
perl -MCPAN -e shell install Data::IEEE754::Tools
For more information on module installation, please visit the detailed CPAN module installation guide.