Skip to content

Commit

Permalink
Use mantissa consistently
Browse files Browse the repository at this point in the history
  • Loading branch information
ocelotl committed Oct 31, 2022
1 parent 6871471 commit fda116f
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
MappingUnderflowError,
)
from opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.ieee_754 import (
MANTISSA_WIDTH,
MAX_NORMAL_EXPONENT,
MIN_NORMAL_EXPONENT,
MIN_NORMAL_VALUE,
SIGNIFICAND_WIDTH,
get_ieee_754_exponent,
get_ieee_754_significand,
get_ieee_754_mantissa,
)


Expand Down Expand Up @@ -96,30 +96,30 @@ def map_to_index(self, value: float) -> int:
# == 3. 3 is represented as ...00011. Its compliment is ...11100, the
# binary representation of -4.

# get_ieee_754_significand(value) gets the positive integer made up
# from the rightmost SIGNIFICAND_WIDTH bits (the mantissa) of the IEEE
# get_ieee_754_mantissa(value) gets the positive integer made up
# from the rightmost MANTISSA_WIDTH bits (the mantissa) of the IEEE
# 754 representation of value. If value is an exact power of 2, all
# these SIGNIFICAND_WIDTH bits would be all zeroes, and when 1 is
# these MANTISSA_WIDTH bits would be all zeroes, and when 1 is
# subtracted the resulting value is -1. The binary representation of
# -1 is ...111, so when these bits are right shifted SIGNIFICAND_WIDTH
# -1 is ...111, so when these bits are right shifted MANTISSA_WIDTH
# places, the resulting value for correction is -1. If value is not an
# exact power of 2, at least one of the rightmost SIGNIFICAND_WIDTH
# exact power of 2, at least one of the rightmost MANTISSA_WIDTH
# bits would be 1 (even for values whose decimal part is 0, like 5.0
# since the IEEE 754 of such number is too the product of a power of 2
# (defined in the exponent part of the IEEE 754 representation) and the
# value defined in the mantissa). Having at least one of the rightmost
# SIGNIFICAND_WIDTH bit being 1 means that get_ieee_754(value) will
# MANTISSA_WIDTH bit being 1 means that get_ieee_754(value) will
# always be greater or equal to 1, and when 1 is subtracted, the
# result will be greater or equal to 0, whose representation in binary
# will be of at most SIGNIFICAND_WIDTH ones that have an infinite
# amount of leading zeroes. When those SIGNIFICAND_WIDTH bits are
# shifted to the right SIGNIFICAND_WIDTH places, the resulting value
# will be of at most MANTISSA_WIDTH ones that have an infinite
# amount of leading zeroes. When those MANTISSA_WIDTH bits are
# shifted to the right MANTISSA_WIDTH places, the resulting value
# will be 0.

# In summary, correction will be -1 if value is a power of 2, 0 if not.

# FIXME Document why we can assume value will not be 0, inf, or NaN.
correction = (get_ieee_754_significand(value) - 1) >> SIGNIFICAND_WIDTH
correction = (get_ieee_754_mantissa(value) - 1) >> MANTISSA_WIDTH

return (exponent + correction) >> -self._scale

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ where $v$ is the value of the binary number in the exponent bits and $bias$ is $
Considering the restrictions above, the respective minimum and maximum values for the
exponent are:

1. `00000000001` = $1$, $1 - 1023 = 1022$
1. `00000000001` = $1$, $1 - 1023 = -1022$
2. `11111111110` = $2046$, $2046 - 1023 = 1023$

So, $exponent$ is an integer in the range $\left[-1022, 1023\right]$.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,80 +15,48 @@
from ctypes import c_double, c_uint64
from sys import float_info

# IEEE 754 is a standard that defines a way to represent floating point numbers
# (normalized and denormalized), and also special numbers like zero, infinite
# and NaN using 64-bit binary numbers. First we'll explain how floating point
# numbers are represented and special numbers will be explained later.
#
# IEEE 754 represents floating point numbers using an exponential notation with
# 4 components: sign, mantissa, base and exponent:
#
# floating_point_number = sign * mantissa * (base ** exponent)
#
# Here:
# 1. sign can be 1 or -1.
# 2. mantissa is a positive fractional number whose integer part is 1 for
# normalized floating point numbers and 0 for denormalized floating point
# numbers.
# 3. base is always 2.
# 4. exponent is an integer in the range [-1022, 1023] for normalized floating
# point numbers and -1022 for denormalized floating point numbers.
#
# The smallest value a normalized floating point number can have is
# -1 * 1.0 * (2 ** -1022) == 2.2250738585072014e-308.
# As mentioned before, IEEE 754 defines how floating point numbers are
# represented using a 64-bit binary number, for this number:
#
# 1. The first bit represents the sign.
# 2. The next 11 bits represent the exponent.
# 3. The next 52 bits represent the mantissa.
#
# The sign is positive if the sign bit is 0 and negative if the sign bit is 1.
#
# There are 11 bits for the exponent
# An IEEE 754 double-precision (64 bit) floating point number is represented
# as: 1 bit for sign, 11 bits for exponent and 52 bits for significand. Since
# these numbers are in a normalized form (in scientific notation), the first
# bit of the significand will always be 1. Because of that, that bit is not
# stored but implicit, to make room for one more bit and more precision.

SIGNIFICAND_WIDTH = 52
# IEEE 754 64-bit floating point numbers use 11 bits for the exponent and 52
# bits for the mantissa.
MANTISSA_WIDTH = 52
EXPONENT_WIDTH = 11

# This mask is equivalent to 52 "1" bits (there are 13 hexadecimal 4-bit "f"s
# in the significand mask, 13 * 4 == 52) or 0xfffffffffffff in hexadecimal.
SIGNIFICAND_MASK = (1 << SIGNIFICAND_WIDTH) - 1
# in the mantissa mask, 13 * 4 == 52) or 0xfffffffffffff in hexadecimal.
MANTISSA_MASK = (1 << MANTISSA_WIDTH) - 1

# There are 11 bits for the exponent, but the exponent bias values 0 (11 "0"
# There are 11 bits for the exponent, but the exponent values 0 (11 "0"
# bits) and 2047 (11 "1" bits) have special meanings so the exponent range is
# from 1 to 2046. To calculate the exponent value, 1023 is subtracted from the
# exponent, so the exponent value range is from -1022 to +1023.
# from 1 to 2046. To calculate the exponent value, 1023 (the bias) is
# subtracted from the exponent, so the exponent value range is from -1022 to
# +1023.
EXPONENT_BIAS = (2 ** (EXPONENT_WIDTH - 1)) - 1

# All the exponent mask bits are set to 1 for the 11 exponent bits.
EXPONENT_MASK = ((1 << EXPONENT_WIDTH) - 1) << SIGNIFICAND_WIDTH
EXPONENT_MASK = ((1 << EXPONENT_WIDTH) - 1) << MANTISSA_WIDTH

# The exponent mask bit is to 1 for the sign bit.
SIGN_MASK = 1 << (EXPONENT_WIDTH + SIGNIFICAND_WIDTH)
# The sign mask has the first bit set to 1 and the rest to 0.
SIGN_MASK = 1 << (EXPONENT_WIDTH + MANTISSA_WIDTH)

# For normalized floating point numbers, the exponent can have a value in the
# range [-1022, 1023].
MIN_NORMAL_EXPONENT = -EXPONENT_BIAS + 1
MAX_NORMAL_EXPONENT = EXPONENT_BIAS

# Smallest possible normal value (2.2250738585072014e-308)
# The smallest possible normal value is 2.2250738585072014e-308.
# This value is the result of using the smallest possible number in the
# mantissa, 1.0000000000000000000000000000000000000000000000000000 (52 "0"s in
# the fractional part) = 1.0000000000000000 and a single "1" in the exponent.
# Finally 1.0000000000000000 * 2 ** -1022 = 2.2250738585072014e-308.
# the fractional part) and a single "1" in the exponent.
# Finally 1 * (2 ** -1022) = 2.2250738585072014e-308.
MIN_NORMAL_VALUE = float_info.min

# Greatest possible normal value (1.7976931348623157e+308)
# The binary representation of a float in scientific notation uses (for the
# significand) one bit for the integer part (which is implicit) and 52 bits for
# mantissa) one bit for the integer part (which is implicit) and 52 bits for
# the fractional part. Consider a float binary 1.111. It is equal to 1 + 1/2 +
# 1/4 + 1/8. The greatest possible value in the 52-bit significand would be
# 1/4 + 1/8. The greatest possible value in the 52-bit binary mantissa would be
# then 1.1111111111111111111111111111111111111111111111111111 (52 "1"s in the
# fractional part) = 1.9999999999999998. Finally,
# 1.9999999999999998 * 2 ** 1023 = 1.7976931348623157e+308.
# fractional part) whose decimal value is 1.9999999999999998. Finally,
# 1.9999999999999998 * (2 ** 1023) = 1.7976931348623157e+308.
MAX_NORMAL_VALUE = float_info.max


Expand All @@ -110,10 +78,10 @@ def get_ieee_754_exponent(value: float) -> int:
#
# The first bit of the previous binary number is the sign bit: 1 (1 means negative, 0 means positive)
# The next 11 bits are the exponent bits: 11111111110
# The next 52 bits are the significand bits: 1111111111111111111111111111111111111111111111111111
# The next 52 bits are the mantissa bits: 1111111111111111111111111111111111111111111111111111
#
# This step isolates the exponent bits, turning every bit outside
# of the exponent field (sign and significand bits) to 0.
# of the exponent field (sign and mantissa bits) to 0.
c_uint64.from_buffer(c_double(-MAX_NORMAL_VALUE)).value
& EXPONENT_MASK
# For the example this means:
Expand All @@ -124,11 +92,11 @@ def get_ieee_754_exponent(value: float) -> int:
# zero.
)
# This step moves the exponent bits to the right, removing the
# significand bits that were set to 0 by the previous step. This
# mantissa bits that were set to 0 by the previous step. This
# leaves the IEEE 754 exponent value, ready for the next step.
>> SIGNIFICAND_WIDTH
>> MANTISSA_WIDTH
# For the example this means:
# 9214364837600034816 >> SIGNIFICAND_WIDTH == 2046
# 9214364837600034816 >> MANTISSA_WIDTH == 2046
# bin(2046) == '0b11111111110'
# As shown above, these are the original 11 bits that correspond to the
# exponent.
Expand All @@ -140,11 +108,11 @@ def get_ieee_754_exponent(value: float) -> int:
# As mentioned in a comment above, the largest value for the exponent is


def get_ieee_754_significand(value: float) -> int:
def get_ieee_754_mantissa(value: float) -> int:
return (
c_uint64.from_buffer(c_double(value)).value
# This step isolates the significand bits. There is no need to do any
# bit shifting as the significand bits are already the rightmost field
# This step isolates the mantissa bits. There is no need to do any
# bit shifting as the mantissa bits are already the rightmost field
# in an IEEE 754 representation.
& SIGNIFICAND_MASK
& MANTISSA_MASK
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
MIN_NORMAL_EXPONENT,
MIN_NORMAL_VALUE,
get_ieee_754_exponent,
get_ieee_754_significand,
get_ieee_754_mantissa,
)


Expand Down Expand Up @@ -104,7 +104,7 @@ def map_to_index(self, value: float) -> int:
return self._min_normal_lower_boundary_index - 1

# Exact power-of-two correctness: an optional special case.
if get_ieee_754_significand(value) == 0:
if get_ieee_754_mantissa(value) == 0:
exponent = get_ieee_754_exponent(value)
return (exponent << self._scale) - 1

Expand Down

0 comments on commit fda116f

Please sign in to comment.