Can you use float numbers in this for loop?
Asked Answered
V

3

6

I need some help. I'm trying to make my for loop work with decimals, but my code won't accept floats and I'm not sure what to do next. Can anyone point out where I went wrong?

It's a code used for converting Celsius to Fahrenheit in steps (Delta) that the user defines. Here it is:

def main():

    # Handshake
    print("This program will convert a range of Celsius degrees to")
    print("Fahrenheit degrees based on your input.")

    # Ask and read low end of range
    Rangelow = eval(input("Enter the low end of your range: "))

    # Ask and read top end of range
    Rangehigh = 1 + eval(input("Enter the high end of your range: "))

    # Ask and read Delta
    Delta = eval(input("Enter the Delta for your range: "))

    #Display output
    print("Celsius to Fahrenheit by", Delta)
    for i in range(Rangelow, Rangehigh, Delta):
        print(i, "               ", 9/5 * i + 32)



main()

This is an example of what I mean:

This program will convert a range of Celsius degrees to Fahrenheit degrees based on your input. Enter the low end of your range: 3.8 Enter the high end of your range: 14.7 Enter the Delta for your range: 1.1 Celsius to Fahrenheit by 1.1 Traceback (most recent call last): File "C:\Users\jarre\Desktop\Python Programs\Conversion.py", line 27, in main() File "C:\Users\jarre\Desktop\Python Programs\Conversion.py", line 22, in main for i in range(Rangelow, Rangehigh + 1, Delta): TypeError: 'float' object cannot be interpreted as an integer

I should note that the problem seems to lie with the input, the output has no issue throwing out a decimal after the input has been converted.

Vulgar answered 27/9, 2016 at 1:17 Comment(7)
When you say "my code won't accept floats", what does that mean? If there is a traceback, please edit your question and add it.Cosgrave
Check out the docs: docs.python.org/2/library/functions.html... The arguments must be plain integers.Thrush
Side-note: Please don't use eval on user supplied input. If you expect float, then use float instead of eval. If you want to allow int or float (or any Python literal), you can use ast.literal_eval, which will safely handle arbitrary Python literals without opening you up to executing arbitrary code.Autoicous
@Autoicous float only results in an error: Traceback (most recent call last): File "C:\Users\jarre\Desktop\Python Programs\Conversion.py", line 27, in <module> main() File "C:\Users\jarre\Desktop\Python Programs\Conversion.py", line 22, in main for i in range(Rangelow, Rangehigh + 1, Delta): TypeError: 'float' object cannot be interpreted as an integerVulgar
@Jarbcd: That was a side-note for a reason. Using float is an example of what you might accept if the rest of your code expected floats (it doesn't, because range only supports int). The point is that eval is unsafe/unstable, and you should be using something more specific to what you want. If you want float (probably a bad idea here; if you really want to allow decimals, you'd want decimal.Decimal for lossless storage and use AChampion's decimal_range function with it), use float. If you want int, use int. If you want any old Python literal, use ast.literal_eval.Autoicous
Okay, I think I got it @Autoicous Instead of range I should use float? or decimal.Decimal? (I've only just started coding, btw) How would I implement that? I'd bet it's not just as simple as replacing range with decimal.Decimal Or would it maybe be as simple as using the code that AChampion wrote as it is?Vulgar
@Jarbcd: No. Instead of float or eval you'd use decimal.Decimal. Instead of range, you'd use AChampion's decimal_range function.Autoicous
O
12

You can't use the built in to do float/decimal increments but it is fairly easy to construct your own generator:

def decimal_range(start, stop, increment):
    while start < stop: # and not math.isclose(start, stop): Py>3.5
        yield start
        start += increment

for i in decimal_range(Rangelow, Rangehigh, Delta):
    ...

Or you could use numpy but this feels like a sledgehammer cracking a nut:

import numpy as np
for i in np.arange(Rangelow, Rangehigh, Delta):
    ...
Oilbird answered 27/9, 2016 at 1:25 Comment(5)
Note: This will not behave as you expect if you use it with floats in many cases. For example, if the arguments are 0.2, 0.3, 0.1, you'll get one output (correctly excluding a "roughly 0.3" output). But if you pass it 0.7, 0.8, 0.1, you'll get two outputs, for 0.7, and 0.7999999999999.... As the name suggests, the only safe way to use this would be to convert input strings directly to decimal.Decimal, not to float.Autoicous
Yes, float rounding errors are a problem - so agree Decimal is the way to go. You could use math.isclose() to fix some of the errors for Py > 3.5Oilbird
How would I implement this? I'm having a hard time figuring this outVulgar
@Autoicous When I use the Decimal code above, all I get is an error about three missing positionsVulgar
@Vulgar You may want to ask another question because without seeing what you've done - it's hard to tell what the error is.Oilbird
K
0
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import decimal

def range_decimal(start, stop, step, stop_inclusive=False):
    """ The Python range() function, using decimals.  A decimal loop_value generator.

    Note: The decimal math (addition) defines the rounding.

    If the stop is None, then:
        stop = start
        start = 0 (zero)

    If the step is 0 (zero) or None, then:
        if (stop < start) then step = -1 (minus one)
        if (stop >= start) then step = 1 (one)

    Example:
        for index in range_decimal(0, 1.0, '.1', stop_inclusive=True):
            print(index)

    :param start: The loop start value
    :param stop: The loop stop value
    :param step: The loop step value
    :param stop_inclusive: Include the stop value in the loop's yield generator: False = excluded ; True = included
    :return: The loop generator's yield increment value (decimal)
    """
    try:
        # Input argument(s) error check
        zero = decimal.Decimal('0')

        if start is None:
            start = zero

        if not isinstance(start, decimal.Decimal):
            start = decimal.Decimal(f'{start}')

        if stop is None:
            stop = start
            start = zero

        if not isinstance(stop, decimal.Decimal):
            stop = decimal.Decimal(f'{stop}')

        if step is None:
            step = decimal.Decimal('-1' if stop < start else '1')

        if not isinstance(step, decimal.Decimal):
            step = decimal.Decimal(f'{step}')

        if step == zero:
            step = decimal.Decimal('-1' if stop < start else '1')

        # Check for valid loop conditions
        if start == stop or (start < stop and step < zero) or (start > stop and step > zero):
            return  # Not valid: no loop

        # Case: increment step ( > 0 )
        if step > zero:
            while start < stop:  # Yield the decimal loop points (stop value excluded)
                yield start
                start += step

        # Case: decrement step ( < 0 )
        else:
            while start > stop:  # Yield the decimal loop points (stop value excluded)
                yield start
                start += step

        # Yield the stop value (inclusive)
        if stop_inclusive:
            yield stop

    except (ValueError, decimal.DecimalException) as ex:
        raise ValueError(f'{__name__}.range_decimal() error: {ex}')

This is a Python range() equivalent function, using decimals. The yielded values are exact.


    Rangelow = 36
    Rangehigh = 37
    Delta = 0.1
    
    print("Celsius to Fahrenheit by", Delta)
    for i in range_decimal(Rangelow, Rangehigh, Delta, stop_inclusive=True):
        print(f'{i:.1f}               {i * 9 / 5 + 32}')

Celsius to Fahrenheit by 0.1
36.0               96.8
36.1               96.98
36.2               97.16
36.3               97.34
36.4               97.52
36.5               97.7
36.6               97.88
36.7               98.06
36.8               98.24
36.9               98.42
37.0               98.6
Kulp answered 8/5, 2022 at 3:12 Comment(2)
This will support the input types: integer, float, decimal.Decimal. The yielded output decimal will work well for addition, subtraction, and multiplication. Division in Python is a float, and the decimal type has different levels of support, depending on the Python version and order of precedence.Kulp
{i * 9 / 5 + 32} and {9 * i / 5 + 32} will return a value. {9 / 5 * i + 32} returns a TypeError: unsupported operand type(s) for *: 'float' and 'decimal.Decimal' {9 / 5 * float(i) + 32} converts the decimal to a float and returns a value. Your computer's floating_point to binary number bit_resolution may not return the number (in base 10) that you expect.Kulp
G
0

Here's an implementation of the decimal_range idea that covers both incrementing and decrementing without converting to decimal class and without bothering with a ton of validation and handling for none types etc.

It will also return the stop value itself

def decimal_range(start, stop, increment):
    """
    Like built-in range, but works for floating point numbers
    """
    current = start

    while (start < stop and current < stop) \
          or (start > stop and current > stop) \
          or decimal_is_close(current, stop, increment / 4.0):

        # optional, but useful if you really need to guarantee the stop value itself
        # is returned and not something that is just really really close
        if decimal_is_close(current, stop, increment / 4.0):
            current = stop

        yield current
        current += increment

def decimal_is_close(value, target, what_is_close):
    return abs(value - target) < what_is_close
Ginder answered 16/5, 2022 at 19:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.