1

I need some strings of floats in scientific notation such as ['5e-6','1e-5','1.5e-5','2e-5','2.5e-5',...].

So I try to generate them with [str(n*1e-6) for n in list(range(5,60,5))] yet it gives ['4.9999999999999996e-06', '9.999999999999999e-06', '1.4999999999999999e-05',...].

Then I try to format them with ["{:.1e}".format(n*1e-6) for n in list(range(5,60,5))] and it gives ['5.0e-06', '1.0e-05', '1.5e-05', '2.0e-05', '2.5e-05',...] which are still not what I want.

I wonder is there any simple way to do this other than writing a custom function? Thanks.

Qin Heyang
  • 1,456
  • 1
  • 16
  • 18
  • 3
    Have you tried ["{:.0e}".format(n*1e-6) for n in list(range(5,60,5))] ? – Sarah Messer Jun 10 '21 at 17:20
  • I don't have the link handy, but search for Is floating point broken? – Mark Ransom Jun 10 '21 at 17:35
  • 1
    @Mark That's not really relevant here cause OP can round. Plus I tried using `decimal.Decimal` but it counts trailing zeroes as sig figs. But for reference, here's the link: [Is floating point math broken?](https://stackoverflow.com/q/588004/4518341) – wjandrea Jun 10 '21 at 17:37
  • 1
    What is the rule that tells you how many decimal places to use? Why should the output include `'5e-6'` rather than `'5.0e-06'`, for example - especially given that `'1.5e-05'` is okay? – Karl Knechtel Jun 10 '21 at 17:40
  • @wjandrea rounding won't help unless you let the printing function do it. Some numbers just can't be represented in binary, and the problem gets worse as the numbers get smaller. – Mark Ransom Jun 10 '21 at 17:45
  • @Mark So OP should use `decimal.Decimal`, right? I'm not sure how to do it myself. – wjandrea Jun 10 '21 at 17:46

2 Answers2

1

This works for your specific example, but note that it's quite hardcoded:

["{:.0e}".format(n*1e-6) if not n%10 or n<10 else "{:.1e}".format(n*1e-6) for n in list(range(5,60,5))]

Here you change the format for numbers that you don't want to display with any decimal part, but you still display the x.5 numbers properly. The code makes an exception for 5, otherwise it would be printed with a decimal.

This is a version that fully addresses your question - it also removes the trailing 0 in the exponent:

["{:.0e}".format(n*1e-6).replace("e-0", "e-") if not n%10 or n<10 else "{:.1e}".format(n*1e-6).replace("e-0", "e-") for n in list(range(5,60,5))]

replace trick comes from this post - as stated in there, you'd need to extend it for e+0.

atru
  • 4,699
  • 2
  • 18
  • 19
1

You could just replace the .0e part with e.

["{:.1e}".format(n*1e-6).replace(".0e", "e") for n in list(range(5,60,5))]

This gives

['5e-06',  '1e-05',  '1.5e-05',  '2e-05',  '2.5e-05',  '3e-05',  '3.5e-05',  '4e-05',  '4.5e-05',  '5e-05',  '5.5e-05']

Which still leaves us with the leading zero in the exponent. To get rid of that, we can use a regular expression:

(e-?)0(\d+)

Explanation: https://regex101.com/r/8bXvDH/1

  • (e-?): Match a literal e followed by an optional minus sign. Capturing group 1
  • 0: Match a literal zero
  • (\d+): Match one or more digits. Capturing group 2.

To do the substitution, we use re.sub():

# \1\2 in the replace string means capturing group 1, then capturing group 2
re.sub(r"(e-?)0(\d+)", r"\1\2", "5e-06") # Gives "5e-6"

In our list compregension:

[re.sub(r"(e-?)0(\d+)", r"\1\2", "{:.1e}".format(n*1e-6).replace(".0e", "e")) for n in list(range(5,60,5))]

gives what we want:

['5e-6',  '1e-5',  '1.5e-5',  '2e-5',  '2.5e-5',  '3e-5',  '3.5e-5',  '4e-5',  '4.5e-5',  '5e-5',  '5.5e-5']
Pranav Hosangadi
  • 23,755
  • 7
  • 44
  • 70