Performing a calculation to show Python has 53-bit precision - python

May anyone provide guidance on how to perform a simple calculation in Python to roughly prove that the language implements 53 bit precision, as per IEEE 754? I don't have much to go on here other than this. I have tried to work off of the canonical example of 0.1 + 0.2, but no luck.

>>> for i in range(100):
x = 2**i
if float(x) == float(x + 1):
print(i)
break
53
This works because x is an integer, and integers are unlimited in their bit range in Python.

Calculate the epsilon, such that 1.0 + eps == 1.0:
from itertools import count
eps = 1.0
for bits in count():
if 1.0+eps == 1.0:
break
eps *= 0.5

Just to add a third example to the mix.
In IEEE 754, infinity is defined as setting all exponent bits to one
and all mantissa bits to to zero. With negative
infinity, the sign bit is also set to one. This means that the only zero bits will belong to the mantissa.
import struct
def float_bin(f):
"as bin function, but converts floats to their binary representation"
bytes_ = struct.pack("d", f)
format_ = "{:08b}" * len(bytes_)
binary = format_.format(*bytes_[::-1])
return binary
mantissa_bits = float_bin(float("-inf"))
# mantissa_bits == "1111111111110000000000000000000000000000000000000000000000000000"
print(mantissa_bits.count("0")) # prints 52... (rather than 53)
Where is the last bit? IEEE 754 clearly specifies 53 bits for the mantissa, but only 52 are stored. This is because the leading bit of the mantissa is implied.
All numbers expressed in scientific notation must start with a
non-zero digit. For instance, 0.123 * 10^5 is not valid. Instead, the correct representation is 1.23 * 10^4.
However, since there is only one non-zero digit in binary, then there is only one value this bit could ever take. As such it would be a waste to store this bit explicitly. Meaning, for a 53-bit mantissa, you only need to store 52-bits.

Related

Python rounding 3.25 to 3.2 [duplicate]

I want to remove digits from a float to have a fixed number of digits after the dot, like:
1.923328437452 → 1.923
I need to output as a string to another function, not print.
Also I want to ignore the lost digits, not round them.
round(1.923328437452, 3)
See Python's documentation on the standard types. You'll need to scroll down a bit to get to the round function. Essentially the second number says how many decimal places to round it to.
First, the function, for those who just want some copy-and-paste code:
def truncate(f, n):
'''Truncates/pads a float f to n decimal places without rounding'''
s = '{}'.format(f)
if 'e' in s or 'E' in s:
return '{0:.{1}f}'.format(f, n)
i, p, d = s.partition('.')
return '.'.join([i, (d+'0'*n)[:n]])
This is valid in Python 2.7 and 3.1+. For older versions, it's not possible to get the same "intelligent rounding" effect (at least, not without a lot of complicated code), but rounding to 12 decimal places before truncation will work much of the time:
def truncate(f, n):
'''Truncates/pads a float f to n decimal places without rounding'''
s = '%.12f' % f
i, p, d = s.partition('.')
return '.'.join([i, (d+'0'*n)[:n]])
Explanation
The core of the underlying method is to convert the value to a string at full precision and then just chop off everything beyond the desired number of characters. The latter step is easy; it can be done either with string manipulation
i, p, d = s.partition('.')
'.'.join([i, (d+'0'*n)[:n]])
or the decimal module
str(Decimal(s).quantize(Decimal((0, (1,), -n)), rounding=ROUND_DOWN))
The first step, converting to a string, is quite difficult because there are some pairs of floating point literals (i.e. what you write in the source code) which both produce the same binary representation and yet should be truncated differently. For example, consider 0.3 and 0.29999999999999998. If you write 0.3 in a Python program, the compiler encodes it using the IEEE floating-point format into the sequence of bits (assuming a 64-bit float)
0011111111010011001100110011001100110011001100110011001100110011
This is the closest value to 0.3 that can accurately be represented as an IEEE float. But if you write 0.29999999999999998 in a Python program, the compiler translates it into exactly the same value. In one case, you meant it to be truncated (to one digit) as 0.3, whereas in the other case you meant it to be truncated as 0.2, but Python can only give one answer. This is a fundamental limitation of Python, or indeed any programming language without lazy evaluation. The truncation function only has access to the binary value stored in the computer's memory, not the string you actually typed into the source code.1
If you decode the sequence of bits back into a decimal number, again using the IEEE 64-bit floating-point format, you get
0.2999999999999999888977697537484345957637...
so a naive implementation would come up with 0.2 even though that's probably not what you want. For more on floating-point representation error, see the Python tutorial.
It's very rare to be working with a floating-point value that is so close to a round number and yet is intentionally not equal to that round number. So when truncating, it probably makes sense to choose the "nicest" decimal representation out of all that could correspond to the value in memory. Python 2.7 and up (but not 3.0) includes a sophisticated algorithm to do just that, which we can access through the default string formatting operation.
'{}'.format(f)
The only caveat is that this acts like a g format specification, in the sense that it uses exponential notation (1.23e+4) if the number is large or small enough. So the method has to catch this case and handle it differently. There are a few cases where using an f format specification instead causes a problem, such as trying to truncate 3e-10 to 28 digits of precision (it produces 0.0000000002999999999999999980), and I'm not yet sure how best to handle those.
If you actually are working with floats that are very close to round numbers but intentionally not equal to them (like 0.29999999999999998 or 99.959999999999994), this will produce some false positives, i.e. it'll round numbers that you didn't want rounded. In that case the solution is to specify a fixed precision.
'{0:.{1}f}'.format(f, sys.float_info.dig + n + 2)
The number of digits of precision to use here doesn't really matter, it only needs to be large enough to ensure that any rounding performed in the string conversion doesn't "bump up" the value to its nice decimal representation. I think sys.float_info.dig + n + 2 may be enough in all cases, but if not that 2 might have to be increased, and it doesn't hurt to do so.
In earlier versions of Python (up to 2.6, or 3.0), the floating point number formatting was a lot more crude, and would regularly produce things like
>>> 1.1
1.1000000000000001
If this is your situation, if you do want to use "nice" decimal representations for truncation, all you can do (as far as I know) is pick some number of digits, less than the full precision representable by a float, and round the number to that many digits before truncating it. A typical choice is 12,
'%.12f' % f
but you can adjust this to suit the numbers you're using.
1Well... I lied. Technically, you can instruct Python to re-parse its own source code and extract the part corresponding to the first argument you pass to the truncation function. If that argument is a floating-point literal, you can just cut it off a certain number of places after the decimal point and return that. However this strategy doesn't work if the argument is a variable, which makes it fairly useless. The following is presented for entertainment value only:
def trunc_introspect(f, n):
'''Truncates/pads the float f to n decimal places by looking at the caller's source code'''
current_frame = None
caller_frame = None
s = inspect.stack()
try:
current_frame = s[0]
caller_frame = s[1]
gen = tokenize.tokenize(io.BytesIO(caller_frame[4][caller_frame[5]].encode('utf-8')).readline)
for token_type, token_string, _, _, _ in gen:
if token_type == tokenize.NAME and token_string == current_frame[3]:
next(gen) # left parenthesis
token_type, token_string, _, _, _ = next(gen) # float literal
if token_type == tokenize.NUMBER:
try:
cut_point = token_string.index('.') + n + 1
except ValueError: # no decimal in string
return token_string + '.' + '0' * n
else:
if len(token_string) < cut_point:
token_string += '0' * (cut_point - len(token_string))
return token_string[:cut_point]
else:
raise ValueError('Unable to find floating-point literal (this probably means you called {} with a variable)'.format(current_frame[3]))
break
finally:
del s, current_frame, caller_frame
Generalizing this to handle the case where you pass in a variable seems like a lost cause, since you'd have to trace backwards through the program's execution until you find the floating-point literal which gave the variable its value. If there even is one. Most variables will be initialized from user input or mathematical expressions, in which case the binary representation is all there is.
The result of round is a float, so watch out (example is from Python 2.6):
>>> round(1.923328437452, 3)
1.923
>>> round(1.23456, 3)
1.2350000000000001
You will be better off when using a formatted string:
>>> "%.3f" % 1.923328437452
'1.923'
>>> "%.3f" % 1.23456
'1.235'
n = 1.923328437452
str(n)[:4]
At my Python 2.7 prompt:
>>> int(1.923328437452 * 1000)/1000.0
1.923
The truely pythonic way of doing it is
from decimal import *
with localcontext() as ctx:
ctx.rounding = ROUND_DOWN
print Decimal('1.923328437452').quantize(Decimal('0.001'))
or shorter:
from decimal import Decimal as D, ROUND_DOWN
D('1.923328437452').quantize(D('0.001'), rounding=ROUND_DOWN)
Update
Usually the problem is not in truncating floats itself, but in the improper usage of float numbers before rounding.
For example: int(0.7*3*100)/100 == 2.09.
If you are forced to use floats (say, you're accelerating your code with numba), it's better to use cents as "internal representation" of prices: (70*3 == 210) and multiply/divide the inputs/outputs.
Simple python script -
n = 1.923328437452
n = float(int(n * 1000))
n /=1000
def trunc(num, digits):
sp = str(num).split('.')
return '.'.join([sp[0], sp[1][:digits]])
This should work. It should give you the truncation you are looking for.
So many of the answers given for this question are just completely wrong. They either round up floats (rather than truncate) or do not work for all cases.
This is the top Google result when I search for 'Python truncate float', a concept which is really straightforward, and which deserves better answers. I agree with Hatchkins that using the decimal module is the pythonic way of doing this, so I give here a function which I think answers the question correctly, and which works as expected for all cases.
As a side-note, fractional values, in general, cannot be represented exactly by binary floating point variables (see here for a discussion of this), which is why my function returns a string.
from decimal import Decimal, localcontext, ROUND_DOWN
def truncate(number, places):
if not isinstance(places, int):
raise ValueError("Decimal places must be an integer.")
if places < 1:
raise ValueError("Decimal places must be at least 1.")
# If you want to truncate to 0 decimal places, just do int(number).
with localcontext() as context:
context.rounding = ROUND_DOWN
exponent = Decimal(str(10 ** - places))
return Decimal(str(number)).quantize(exponent).to_eng_string()
>>> from math import floor
>>> floor((1.23658945) * 10**4) / 10**4
1.2365
# divide and multiply by 10**number of desired digits
If you fancy some mathemagic, this works for +ve numbers:
>>> v = 1.923328437452
>>> v - v % 1e-3
1.923
I did something like this:
from math import trunc
def truncate(number, decimals=0):
if decimals < 0:
raise ValueError('truncate received an invalid value of decimals ({})'.format(decimals))
elif decimals == 0:
return trunc(number)
else:
factor = float(10**decimals)
return trunc(number*factor)/factor
You can do:
def truncate(f, n):
return math.floor(f * 10 ** n) / 10 ** n
testing:
>>> f=1.923328437452
>>> [truncate(f, n) for n in range(5)]
[1.0, 1.9, 1.92, 1.923, 1.9233]
Just wanted to mention that the old "make round() with floor()" trick of
round(f) = floor(f+0.5)
can be turned around to make floor() from round()
floor(f) = round(f-0.5)
Although both these rules break around negative numbers, so using it is less than ideal:
def trunc(f, n):
if f > 0:
return "%.*f" % (n, (f - 0.5*10**-n))
elif f == 0:
return "%.*f" % (n, f)
elif f < 0:
return "%.*f" % (n, (f + 0.5*10**-n))
def precision(value, precision):
"""
param: value: takes a float
param: precision: int, number of decimal places
returns a float
"""
x = 10.0**precision
num = int(value * x)/ x
return num
precision(1.923328437452, 3)
1.923
Short and easy variant
def truncate_float(value, digits_after_point=2):
pow_10 = 10 ** digits_after_point
return (float(int(value * pow_10))) / pow_10
>>> truncate_float(1.14333, 2)
>>> 1.14
>>> truncate_float(1.14777, 2)
>>> 1.14
>>> truncate_float(1.14777, 4)
>>> 1.1477
When using a pandas df this worked for me
import math
def truncate(number, digits) -> float:
stepper = 10.0 ** digits
return math.trunc(stepper * number) / stepper
df['trunc'] = df['float_val'].apply(lambda x: truncate(x,1))
df['trunc']=df['trunc'].map('{:.1f}'.format)
int(16.5);
this will give an integer value of 16, i.e. trunc, won't be able to specify decimals, but guess you can do that by
import math;
def trunc(invalue, digits):
return int(invalue*math.pow(10,digits))/math.pow(10,digits);
Here is an easy way:
def truncate(num, res=3):
return (floor(num*pow(10, res)+0.5))/pow(10, res)
for num = 1.923328437452, this outputs 1.923
def trunc(f,n):
return ('%.16f' % f)[:(n-16)]
A general and simple function to use:
def truncate_float(number, length):
"""Truncate float numbers, up to the number specified
in length that must be an integer"""
number = number * pow(10, length)
number = int(number)
number = float(number)
number /= pow(10, length)
return number
There is an easy workaround in python 3. Where to cut I defined with an help variable decPlace to make it easy to adapt.
f = 1.12345
decPlace= 4
f_cut = int(f * 10**decPlace) /10**decPlace
Output:
f = 1.1234
Hope it helps.
Most answers are way too complicated in my opinion, how about this?
digits = 2 # Specify how many digits you want
fnum = '122.485221'
truncated_float = float(fnum[:fnum.find('.') + digits + 1])
>>> 122.48
Simply scanning for the index of '.' and truncate as desired (no rounding).
Convert string to float as final step.
Or in your case if you get a float as input and want a string as output:
fnum = str(122.485221) # convert float to string first
truncated_float = fnum[:fnum.find('.') + digits + 1] # string output
I think a better version would be just to find the index of decimal point . and then to take the string slice accordingly:
def truncate(number, n_digits:int=1)->float:
'''
:param number: real number ℝ
:param n_digits: Maximum number of digits after the decimal point after truncation
:return: truncated floating point number with at least one digit after decimal point
'''
decimalIndex = str(number).find('.')
if decimalIndex == -1:
return float(number)
else:
return float(str(number)[:decimalIndex+n_digits+1])
int(1.923328437452 * 1000) / 1000
>>> 1.923
int(1.9239 * 1000) / 1000
>>> 1.923
By multiplying the number by 1000 (10 ^ 3 for 3 digits) we shift the decimal point 3 places to the right and get 1923.3284374520001. When we convert that to an int the fractional part 3284374520001 will be discarded. Then we undo the shifting of the decimal point again by dividing by 1000 which returns 1.923.
use numpy.round
import numpy as np
precision = 3
floats = [1.123123123, 2.321321321321]
new_float = np.round(floats, precision)
Something simple enough to fit in a list-comprehension, with no libraries or other external dependencies. For Python >=3.6, it's very simple to write with f-strings.
The idea is to let the string-conversion do the rounding to one more place than you need and then chop off the last digit.
>>> nout = 3 # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1] for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.800', '0.888', '1.125', '1.250', '1.500']
Of course, there is rounding happening here (namely for the fourth digit), but rounding at some point is unvoidable. In case the transition between truncation and rounding is relevant, here's a slightly better example:
>>> nacc = 6 # desired accuracy (maximum 15!)
>>> nout = 3 # desired number of digits in output
>>> [f'{x:.{nacc}f}'[:-(nacc-nout)] for x in [2.9999, 2.99999, 2.999999, 2.9999999]]
>>> ['2.999', '2.999', '2.999', '3.000']
Bonus: removing zeros on the right
>>> nout = 3 # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1].rstrip('0') for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.8', '0.888', '1.125', '1.25', '1.5']
The core idea given here seems to me to be the best approach for this problem.
Unfortunately, it has received less votes while the later answer that has more votes is not complete (as observed in the comments). Hopefully, the implementation below provides a short and complete solution for truncation.
def trunc(num, digits):
l = str(float(num)).split('.')
digits = min(len(l[1]), digits)
return l[0] + '.' + l[1][:digits]
which should take care of all corner cases found here and here.
Am also a python newbie and after making use of some bits and pieces here, I offer my two cents
print str(int(time.time()))+str(datetime.now().microsecond)[:3]
str(int(time.time())) will take the time epoch as int and convert it to string and join with...
str(datetime.now().microsecond)[:3] which returns the microseconds only, convert to string and truncate to first 3 chars
# value value to be truncated
# n number of values after decimal
value = 0.999782
n = 3
float(int(value*1en))*1e-n

Manually calculating IEEE-754 floating point fractions and splitting up the bits - Python

I'm trying to come up with a way to do this:
Lets say the fraction portion of my IEEE-754 floating point number is 0b10110011001100110011010
I'm trying to take each bit and multiply it by a power of 2 cooresponding to it's bit position and add them all up.
so like this: 1*2-1+ 0*2-2 + 1*2-3 + 1*2-4 + 0*2-5 + 0 * 2-6 + ...
I can't for the life of me figure out how to split the number up into bits.
Ideally it'd be something like this:
In [33]: value = 0x6f
In [34]: result = function(value)
In [35]: result
Out[35]: [0,1,1,0,1,1,1,1]
Doesn't have to be a list but a tuple would work fine too.
Then I am pretty confident I can do a for i in result function to get the answer I need. But if you've got ideas on how to do the second part I'm all ears as well!
Any ideas?
If you want to get the raw bits of a float, then you can use struct.pack for it.
>>> struct.pack('#d', 0.7)
b'ffffff\xe6?'
>>> struct.pack('>d', 0.7)
b'?\xe6ffffff'
The prefixes are # for the platform-dependent “native order, size & alignment” (the default, but # makes it explicit), while > is for “big-endian, std. size & alignment”.
The big-endian one translates to the bit sequence 0_01111111110_0110011001100110011001100110011001100110011001100110, i.e.:
A sign bit of 0, indicating that the number is positive.
An exponent of 1022.
A significand of 0x6666666666666.
Recall that the value of a normalized IEEE 754 double is (-1)**sign * 2 ** (exponent - 1023) * (1 + significand / 2**52). Plugging in sign = 0, exponent = 1022, and significand = 0x6666666666666 gives the expected 0.7 (or to be exact, 0.6999999999999999555910790149937383830547332763671875).
If you want to use math functions instead of struct, then math.frexp is your friend. It returns a tuple of (significand, exponent) such that significand * 2**exponent gives the original number. However, it scales the exponent so that the significand is in the interval [0.5, 1), not [1, 2), so in my code below, I have written the exponent bias as 1022 instead of 1023.
def ieee_double_fields(x):
sign = 0
if x < 0:
sign = 1
x = -x
significand, exponent = math.frexp(x)
return (sign, exponent + 1022, int((significand * 2 - 1) * 2**52))
This returns a tuple of integers consisting of a 1-bit sign, 11-bit exponent, and 52-bit significand (without the implicit leading 1 bit).
Handling of denormals (including ±0.0), infinities, and NaN is left as an exercise for the reader.

fractions.Fraction() returns different nom., denom. pair when parsing a float or its string representation

I am aware of the nature of floating point math but I still find the following surprising:
from fractions import Fraction
print(Fraction(0.2)) # -> 3602879701896397/18014398509481984
print(Fraction(str(0.2))) # -> 1/5
print(Fraction(0.2)==Fraction(str(0.2))) # returns False
print(0.2 == float(str(0.2))) # but this returns True!
From the documentation I could not find anything that would explain that. It does state:
...In addition, any string that represents a finite value and is
accepted by the float constructor is also accepted by the Fraction
constructor...
but to me this implies a similar behavior to float() which I just do not see as shown above.
Is there any explanation for this?
It is important to note that the behavior shown above is not specific to the value (0.2) but rather general; everything I tried behaved the same way.
Interestingly enough:
from fractions import Fraction
for x in range(1, 257):
if Fraction(str(1/x))==Fraction(1/x):
print(x)
prints only the powers of 2 that are smaller than the selected upper bound:
1
2
4
8
16
32
64
128
256
Have a look at the def __new__(): implementation in fractions.py, if a string is given:
The regex _RATIONAL_FORMAT ( see link if you are interested in the parsing part) puts out numerator as 0 and decimal as 2
Start quote from fractions.py source, with comments by me
elif isinstance(numerator, str):
# Handle construction from strings.
m = _RATIONAL_FORMAT.match(numerator)
if m is None:
raise ValueError('Invalid literal for Fraction: %r' %
numerator)
numerator = int(m.group('num') or '0') # 0
denom = m.group('denom')
if denom: # not true for your case
denominator = int(denom)
else: # we are here
denominator = 1
decimal = m.group('decimal') # yep: 2
if decimal:
scale = 10**len(decimal) # thats 10^1
numerator = numerator * scale + int(decimal) # thats 0 * 10^1+0 = 10
denominator *= scale # thats 1*2
exp = m.group('exp')
if exp: # false
exp = int(exp)
if exp >= 0:
numerator *= 10**exp
else:
denominator *= 10**-exp
if m.group('sign') == '-': # false
numerator = -numerator
else:
raise TypeError("argument should be a string "
"or a Rational instance")
end quote from source
So '0.2' is parsed to 2 / 10 = 0.2 exactly, not its nearest float approximation wich my calculater puts out at 0,20000000000000001110223024625157
Quintessential: they are not simply using float( yourstring ) but are parsing and calculating the string itself, that is why both differ.
If you use the same constructor and provide a float or decimal the constructor uses the builtin as_integer_ratio() to get numerator and denominator as representation of that number.
The closest the float representation comes to 0.2 is 0,20000000000000001110223024625157 which is exactly what the as_integer_ratio() method returns nominator and denominator for.
As eric-postpischil
and mark-dickinson pointed out, this float value is limited by its binary representations to "close to 0.2". When put into str() will be truncated to exact '0.2' - hence the differences between
print(Fraction(0.2)) # -> 3602879701896397/18014398509481984
print(Fraction(str(0.2))) # -> 1/5
In print(Fraction(0.2)), the source text 0.2 is converted to a floating-point value. The result of this conversion is exactly 0.200000000000000011102230246251565404236316680908203125, or 3602879701896397/18014398509481984. This value is then passed to Fraction, which produces the same value represented as a rational number.
In print(Fraction(str(0.2))), 0.2 is again converted to a floating-point value, yielding the number above. Then str converts it to a string. In current Python versions, when a floating-point value is converted to a string, Python does not generally produce the exact mathematical value. Instead, it produces the just enough digits so that converting the string back to floating-point produces the input number. In this case, that results in “0.2”. So the string “0.2” is passed to Fraction. Then Fraction analyzes “0.2” and determines it is 1/5.
Notice the last digit in the denominator. It appears the fractions module takes this into consideration when storing the object internally, but when used in operations python can round.
from fractions import Fraction
Fraction(3602879701896397, 18014398509481985) == Fraction(1, 5) # True
Fraction(3602879701896397, 18014398509481984) == Fraction(1, 5) # False
3602879701896397 / 18014398509481985 == 0.2 # True
3602879701896397 / 18014398509481984 == 0.2 # True
Now the question of why the fractions module chooses an approximation (i.e. 18014398509481984 instead of correct 18014398509481985) is not one I can answer.

Floating point Division without using Division Operator

Given two positive floating point numbers x and y, how would you compute x/y to within a specified tolerance e if the division operator
cannot be used?
You cannot use any library functions, such as log and exp; addition
and multiplication are acceptable.
May I know how can I solve it? I know the approach to solving division is to use bitwise operator, but in that approach, when x is less than y, the loop stops.
def divide(x, y):
# break down x/y into (x-by)/y + b , where b is the integer answer
# b can be computed using addition of numbers of power of 2
result = 0
power = 32
y_power = y << power
while x >= y:
while y_power > x:
y_power = y_power>> 1
power -= 1
x = x - y_power
result += 1 << power
return result
An option is to use the Newton-Raphson iterations, known to converge quadratically (so that the number of exact bits will grow like 1, 2, 4, 8, 16, 32, 64).
First compute the inverse of y with the iterates
z(n+1) = z(n) (2 - z(n) y(n)),
and after convergence form the product
x.z(N) ~ x/y
But the challenge is to find a good starting approximation z(0), which should be within a factor 2 of 1/y.
If the context allows it, you can play directly with the exponent of the floating-point representation and replace Y.2^e by 1.2^-e or √2.2^-e.
If this is forbidden, you can setup a table of all the possible powers of 2 in advance and perform a dichotomic search to locate y in the table. Then the inverse power is easily found in the table.
For double precision floats, there are 11 exponent bits so that the table of powers should hold 2047 values, which can be considered a lot. You can trade storage for computation by storing only the exponents 2^0, 2^±1, 2^±2, 2^±3... Then during the dichotomic search, you will recreate the intermediate exponents on demand by means of products (i.e. 2^5 = 2^4.2^1), and at the same time, form the product of inverses. This can be done efficiently, using lg(p) multiplies only, where p=|lg(y)| is the desired power.
Example: lookup of the power for 1000; the exponents are denoted in binary.
1000 > 2^1b = 2
1000 > 2^10b = 4
1000 > 2^100b = 16
1000 > 2^1000b = 256
1000 < 2^10000b = 65536
Then
1000 < 2^1100b = 16.256 = 4096
1000 < 2^1010b = 4.256 = 1024
1000 > 2^1001b = 2.256 = 512
so that
2^9 < 1000 < 2^10.
Now the Newton-Raphson iterations yield
z0 = 0.001381067932
z1 = 0.001381067932 x (2 - 1000 x 0.001381067932) = 0.000854787231197
z2 = 0.000978913251777
z3 = 0.000999555349049
z4 = 0.000999999802286
z5 = 0.001
Likely most straightforward solution is to probably to use Newton's method for division to compute the reciprocal, which may then be multiplied by the numerator to yield the final result.
This is an iterative process gradually refining an initial guess and doubling the precision on every iteration, and involves only multiplication and addition.
One complication is generating a suitable initial guess, since an improper selection may fail to converge or take a larger number of iterations to reach the desired precision. For floating-point numbers the easiest solution is to normalize for the power-of-two exponent and use 1 as the initial guess, then invert and reapply the exponent separately for the final result. This yields roughly 2^iteration bits of precision, and so 6 iterations should be sufficient for a typical IEEE-754 double with a 53-bit mantissa.
Computing the result to within an absolute error tolerance e is difficult however given the limited precision of the intermediate computations. If specified too tightly it may not be representable and, worse, a minimal half-ULP bound requires exact arithmetic. If so you will be forced to manually implement the equivalent of an exact IEEE-754 division function by hand while taking great care with rounding and special cases.
Below is one possible implementation in C:
double divide(double numer, double denom, unsigned int precision) {
int exp;
denom = frexp(denom, &exp);
double guess = 1.4142135623731;
if(denom < 0)
guess = -guess;
while(precision--)
guess *= 2 - denom * guess;
return ldexp(numer * guess, -exp);
}
Handling and analysis of special-cases such as zero, other denormals, infinity or NaNs is left as an exercise for the reader.
The frexp and ldexp library functions are easily substituted for manual bit-extraction of the exponent and mantissa. However this is messy and non-portable, and no specific floating-point representation was specified in the question.
First, you should separate signs and exponents from the both numbers. After that, we'll divide pure positive mantissas and adapt the result using former exponents and signs.
As for dividing mantissas, it is simple, if you'll remember that division is not only inverted multiplication, but also the many-times done substraction. The number of times is the result.
A:B->C, precision e
C=0
allowance= e*B
multiplicator = 1
delta = B
while (delta< allowance && A>0)
if A<delta {
multiplicator*=0.1 // 1/10
delta*=0.1 // 1/10
} else {
A-=delta;
C+=multiplicator
}
}
Really, we can use any number>1 instead of 10. It would be interesting, which will give the most effectivity. Of course, if we use 2, we can use shift instead of multiplication inside the cycle.

How to generate scale-independent random floating point numbers?

I want to generate what I'm choosing to call "arbitrary" positive floating-point numbers; that is, random numbers which are independent of scale (in other words, numbers whose logarithms are uniformly distributed). I'm not much of a mathematician, so for all I know there may be another name for what I'm after.
Here's my initial, naïve solution:
import sys
import random
def arbitrary(min=sys.float_info.min_10_exp, max=sys.float_info.max_10_exp):
return 10 ** random.uniform(min, max)
It strikes me that this is probably not ideal: for one thing, I imagine that there might be some interaction between the limited precision of random.uniform() and the floating point representation itself that would cause bunching and gaps in the expected output at higher orders of magnitude.
Is there a better approach? Would it make more sense to just produce a string of random bits and then turn that into the floating point number they represent?
EDIT: As pointed out by Oli Charlesworth in the comments, the "convert random bits to a float" idea doesn't do what I want (which is a uniform distribution of log(n)).
You are correct that your approach doesn't return some numbers. For example, there is no floating-point number between 1.0 and 1.0000000000000002, but 10**1.0000000000000002 is 10.000000000000005, and there are two numbers between 10.0 and 10.000000000000005: 10.000000000000002 and 10.000000000000004. Those two numbers will never be returned by your algorithm.
But you can cheat and use Decimal to exponentiate with greater precision:
>>> float(10 ** Decimal('1'))
10.0
>>> float(10 ** Decimal('1.0000000000000001'))
10.000000000000002
>>> float(10 ** Decimal('1.00000000000000015'))
10.000000000000004
>>> float(10 ** Decimal('1.0000000000000002'))
10.000000000000005
So, arbitrary needs to generate random Decimal exponents of sufficient precision and use them as exponents. Assuming 64 binary digits is enough precision for the exponent, the code would look like this:
import sys, random
from decimal import Decimal
def _random_decimal(minval, maxval, added_prec):
# generate a Decimal in the range [minval, maxval) with the
# precision of additional ADDED_PREC binary digits
rangelen = maxval - minval
denom = rangelen << added_prec
return minval + Decimal(rangelen) * random.randrange(denom) / denom
def arbitrary():
min_exp = sys.float_info.min_exp - sys.float_info.mant_dig
max_exp = sys.float_info.max_exp
return float(2 ** _random_decimal(min_exp, max_exp, 64))

Categories

Resources