1
print("Getting possible numbers")
import random

h1 = random.uniform(0,30)
h2 = random.uniform(0,30)
h3 = random.uniform(0,30)
t = random.uniform(0,30)

baset = .5 * t * h3
volumeti = baset * h2
baser = t * h2
volumer = h1 * baser
volumetotal = volumeti + volumer
c = 1

if volumetotal == 187.2:
  print("h1=", h1, "h2=", h2, "h3=", h3, "t=", t)

while volumetotal != 187.2:
 c += 1
 print("Wrong, trying again", c)

It will find the volume of a hexagonal prism, but it takes a VERY long time and I have not gotten an output yet.

pradyunsg
  • 18,287
  • 11
  • 43
  • 96
  • 1
    No. Comparing floats with sums of other floats can be tricky. `.3 + .3 + .3 == .9` returns `False`. – eumiro May 07 '13 at 15:06
  • How would I go about fixing this? – Holden Salomon May 07 '13 at 15:06
  • 4
    You could check to see if the difference between your actual and expected values are within a margin of error such as `e = 0.00001` and then consider them equivalent. (Now included as an answer) – BlackVegetable May 07 '13 at 15:08
  • By the way, you can speed up your code significantly by printing `c` every few thousand iterations instead of every single time, because printing to the console is **sloowwww**. There's an implementation included in my answer. – Henry Keiter May 07 '13 at 16:45

6 Answers6

4

the loop:

while volumetotal != 187.2:
 c += 1
 print("Wrong, trying again", c)

is a trap. Once you get into the loop, you can never get out since you don't change volumetotal in the body of the loop.

Also note that checking for equality of floating point numbers can be tricky. Two numbers can be extremely close (1 part in 10**16) and still be different -- and numerical errors on that size are happening all the time.

mgilson
  • 300,191
  • 65
  • 633
  • 696
1

You will probably never get an output.

  1. Looping on the first failure will never let you generate numbers again
  2. Plugging in random numbers to get an exact solution isn't very effective, and may never return a result.
  3. Comparing for equality with floating point numbers probably is not what you want. Read up on floating point number equality comparisons.

A better solution would be to solve the equation so that you can randomly pick 3 numbers and solve for the forth. Maybe like:

187.2 = .5 * t * h3 * h2 + h1 * t * h2
187.2 = h2(.5 * t * h3 + h1 * t)
h2 = 187.2 / (.5 * t * h3 + h1 * t)

then generate t, h3, h1 and compute h2

import random

print("Getting possible numbers")
h1 = random.uniform(0,30)
h3 = random.uniform(0,30)
t = random.uniform(0,30)
h2 = 187.2 / ((.5 * t * h3) + (h1 * t))
print("h1=", h1, "h2=", h2, "h3=", h3, "t=", t)
cmd
  • 5,754
  • 16
  • 30
1

You have to loop over getting new random variables as well. Also, you have to accept some small error as you're dealing with floating point variables. Have a look at this:

print("Getting possible numbers")
import random

volumetotal = 0
c = 0

while abs(volumetotal - 187.2) >= 0.1:
  c += 1
  print("Attempt ", c)

  h1 = random.uniform(0,30)
  h2 = random.uniform(0,30)
  h3 = random.uniform(0,30)
  t = random.uniform(0,30)

  baset = .5 * t * h3
  volumeti = baset * h2
  baser = t * h2
  volumer = h1 * baser
  volumetotal = volumeti + volumer

print("h1=", h1, "h2=", h2, "h3=", h3, "t=", t)
likeitlikeit
  • 5,563
  • 5
  • 42
  • 56
0

After your fix your infinite loop issue, you could use the following check:

error = 0.00001
expected = 187.2
if expected - error <= volumetotal <= expected + error:
    print("Yay, this is roughly correct.") 

Another trick, but this one to reduce your run-time would be to keep track of previously chosen random variables and set them as upper/lower bounds after a failed attempt. Thus:

# Pseudo-code
# if expected > desired:
#    set previously chosen random variables as the upper bound on the next 
#    iteration's random ranges.
# else:
#    set previously chosen random variables as the lower bound on the next
#    iteration's random ranges.
# Be sure to only shrink the range of the random selection until you converge on 
# a solution.
BlackVegetable
  • 12,594
  • 8
  • 50
  • 82
0

You can't test the equality between two float, because of floating-point artihmetics poor accuracy. You have to set a epsilon value which limit the precision of the solution :

print("Getting possible numbers")
import random
import math

epsilon = 0.001
c = 1
h1 = 0
h2 = 0
h3 =0
t = 0

def solve():

    h1 = random.uniform(0,30)
    h2 = random.uniform(0,30)
    h3 = random.uniform(0,30)
    t = random.uniform(0,30)

    baset = .5 * t * h3

    volumeti = baset * h2

    baser = t * h2

    volumer = h1 * baser

    volumetotal = volumeti + volumer

    return volumetotal, h1, h2, h3, t




while math.fabs(volumetotal - 187.2) > epsilon: 
 global h1 
 global h2 
 global h3 
 global t

 c += 1
 volumetotal, h1, h2, h3, t = solve()
 print("Wrong, trying again", c)

print("found a solution : ")
print("h1=", h1, "h2=", h2, "h3=", h3, "t=", t)

EDIT: add output globals.

However, it is a really poor method for solving equation. I advise you to look for gradient descent for deterministic algorithm or genetic algorithms if you want to stay in a random fashion.

lucasg
  • 10,734
  • 4
  • 35
  • 57
0

You have a couple of problems:

  • First and most crucially, your loop never changes the values of your variables, so it will never exit (because it's just performing the same test indefinitely.
  • Second and nearly as importantly, you shouldn't loop on a comparison of floating-point numbers if you want your program to terminate. This is because floating-point representations are inherently inaccurate, and as a result your comparisons will evaluate as False when you might expect them to be True (e.g. eumiro's example in the comments of .3 + .3 + .3 == .9 evaluating to False).

The solution for the first problem is simple: you simply have to make sure you're recalculating your variables each time through the loop. The second one is just as simple: instead of testing for equality, you need to test whether the value is "close enough". So pick a tolerance value and compare your error to that, instead of asking for perfection. Both of these are shown in the code following (I've split out some pieces into their own functions for clarity). I've also made it so c is only printed every 5000 iterations, which actually makes the whole thing run many times faster.

print("Getting possible numbers")
import random

def get_variables():
    '''Initialize some random values.'''
    h1 = random.uniform(0,30)
    h2 = random.uniform(0,30)
    h3 = random.uniform(0,30)
    t = random.uniform(0,30)
    return h1, h2, h3, t

def calculate_volume(h1, h2, h3, t):
    '''Calculate the volume based on the given values.'''
    baset = .5 * t * h3
    volumeti = baset * h2
    baser = t * h2
    volumer = h1 * baser
    volumetotal = volumeti + volumer
    return volumetotal


volumetotal = 0
c = 0
tolerance = 0.00001 # Set the tolerance here!
h1 = h2 = h3 = t = None

while abs(volumetotal - 187.2) >= tolerance: 
    c += 1
    if c % 5000 == 0:
        print("Attempt ", c)

    h1, h2, h3, t = get_variables()
    volumetotal = calculate_volume(h1, h2, h3, t)

print ('h1 = {}\nh2 = {}\nh3 = {}\nt = {}\nc = {}\nv = {}'.format(
        h1, h2, h3, t, c, volumetotal))

Note on line 26 where the while loop compares abs(volumetotal - 187.2) (the amount that the result differs from the expected result) to 0.00001 (the tolerance). You can set this tolerance to whatever you like, but the lower the tolerance, the longer the program will take to run.

Just for fun, what you may really want is to select a precision for your output (h1, h2, and so on). Then instead of using random numbers, you can increment your variables predictably until you get within the tolerance. This is probably the way I'd do it, instead of using randoms, because it does at least guarantee that your loop will terminate, and it provides a bit more control over your inputs/outputs.

print("Getting possible numbers")
import itertools

def get_variable_permutations(precision):
    '''Get a permutation generator. The precision is # of decimal places.'''
    stepsize = 0.1**precision
    all_values = (v*stepsize for v in xrange(int(30/stepsize)))
    return itertools.permutations(all_values, 4)

def calculate_volume(h1, h2, h3, t):
    '''Calculate the volume based on the given values.'''
    baset = .5 * t * h3
    volumeti = baset * h2
    baser = t * h2
    volumer = h1 * baser
    volumetotal = volumeti + volumer
    return volumetotal


volumetotal = 0
c = 0
tolerance = 0.00001
precision = 5 # decimal place precision for h1, h2, h3, and t

for h1, h2, h3, t in get_variable_permutations(precision):
    c += 1
    if c % 5000 == 0: # So much faster!
        print("Attempt ", c)
    volumetotal = calculate_volume(h1, h2, h3, t)
    if abs(volumetotal - 187.2) <= tolerance:
        break

print ('h1 = {}\nh2 = {}\nh3 = {}\nt = {}\nc = {}\nv = {}'.format(
        h1, h2, h3, t, c, volumetotal))

Your biggest speedup (by far) actually comes from cutting down on the print statements. My machine runs the above code (at tolerance of 0.00001 and precision of 5 decimal places for h1, h2, h3, and t) in less than a second (finds a nearly exact solution at ~431,000 iterations). The same calculations take about 40 seconds while printing every line.

Community
  • 1
  • 1
Henry Keiter
  • 16,863
  • 7
  • 51
  • 80