In this article I will write a simple module to display very large or very small numbers in scientific notation in a more appealing format than that used by Python.

## Scientific Notation

It is common to show very large or very small numbers such as

589214800563781025612561422054102510

or

0.00000000000000000000000000000515

in scientific notation, so the above numbers would be

5.892 × 10^{35}

and

5.15 × 10^{-30}

You may remember being taught how to convert to and from scientific notation but here's a refresher.

- Move the decimal point left or right until you have a single digit in the integer part of the number (remember how far you moved the decimal point and in which direction), so the number

589214800563781025612561422054102510

becomes

5.89214800563781025612561422054102510

- Cull decimal places as ruthlessly as you want. About 3 is a good number to end up with IMHO, so we now have

5.892

- Now add " × 10"

5.892 × 10

- Raise the 10 to the power of the number of digits you moved the decimal point; the power is negative if you moved it to the right.

5.892 × 10

^{35}

Reversing the process is simple: just move the decimal point by the number of digits specified by the power, right for positive, left for negative, padding with 0s where necessary.

That's fine if you are doing it using pen and paper but in terms of programming it's basically string processing. It's perfectly possible to program it that way but it would be very clumsy so for this project we'll do it properly, using mathematics.

As you probably realised scientific notation breaks a number down into two components, the significand, 5.892 in the above example, and the exponent, eg. 35. Once we have these it's simple to assemble them into a string representation of scientific notation. I'll leave the description of this process until we get to the code, but note that as part of the process we need to take the logarithm of the number. This causes a couple of problems: what if the number is 0? And what if the number is <0? In both cases the logarithm is undefined, ie. it is a mathematical impossibility and if we try it Python will throw a ValueError: math domain error at us but we still need to represent numbers <= 0 as scientific notation. This is how I'll handle these issues:

- If the number is 0 we'll just return "0" as the scientific notation.
- If the number is <= 0 we'll remember the fact, flip it to positive to generate the scientific notation, and then suffix the significand with a minus sign.

If you print a very large or very small number Python will automatically format it in its own scientific notation format. You can also force it to do so with any number using the :e format string. Python's format looks like this:

5.892148e+35

Personally I think this is ugly and difficult to read. The purpose of this project is therefore to provide a more conventional format, and hopefully learn a bit about scientific notation along the way.

## The Project

This project consists of the following files:

- scientificnotation.py
- scientificnotationdemo.py

The files can be downloaded as a zip, or you can clone/download the Github repository if you prefer.

Source Code Links

## The Code

The scientificnotation.py file contains functions to generate a scientific notation string from a number.

scientificnotation.py

from math import *

superscript = {

"0": "\u2070",

"1": "\u00B9",

"2": "\u00B2",

"3": "\u00B3",

"4": "\u2074",

"5": "\u2075",

"6": "\u2076",

"7": "\u2077",

"8": "\u2078",

"9": "\u2079",

"-": "\u207B",

}

def num_to_sn_string(n: float, use_superscript = True, dp = 3) -> str:

"""

Returns a string representation of n in scientific notation.

The exponent can be printed in superscript characters by setting

use_superscript to True (the default).

Number of decimal places in the significand is set with dp (default 3).

"""

scinot = __get_sig_exp(n)

scinot_string = __sig_exp_to_sn_string(scinot, use_superscript, dp)

return scinot_string

def __get_sig_exp(n: float):

if n == 0:

return None

negative = n < 0

n = abs(n)

exponent = floor(log10(n))

significand = n / (10**exponent)

if negative:

significand *= -1

return (significand, exponent)

def __sig_exp_to_sn_string(scinot, use_superscript = True, dp = 3) -> str:

if scinot == None:

return "0"

if use_superscript == True:

exponent = __to_superscript(scinot[1])

else:

exponent = "^" + str(scinot[1])

scinot_string = f"{scinot[0]:0.{dp}f} × 10{exponent}"

return scinot_string

def __to_superscript(n: float) -> str:

n_as_string = str(n)

sup_string = ""

for i in range(0, len(n_as_string)):

sup_string += superscript[n_as_string[i]]

return sup_string

## The superscript Dictionary

After importing math we have a dictionary mapping digits 1-9 and the minus sign to the Unicode values of their superscript equivalents. These are used to show exponents and as you can see they are not in a single sequence.

## The num_to_sn_string Function

This is the public function and takes a number as well as optional use_superscript and dp arguments. It simply calls other functions to get the significand and exponent as a tuple, and then to format these in a scientific notation string.

## The __get_sig_exp Function

Firstly we need to check if the number is 0, in which case we return None. Then we need to check whether the number is negative, and convert it to positive using the abs() function.

The next two lines of code are at the heart of this whole project as they calculate the exponent and significand.

The exponent is calculated by taking the base 10 logarithm of the number. The log10 function basically answers the question "what power do I need to raise 10 to to get a given number?" The logarithm is then rounded down to the nearest integer with the floor function.

Now we can calculate the significand. Calculating 10 to the power of the exponent will give us a number containing the digit 1, as well as a number of 0s corresponding to how many digits we need to move the decimal place according to the "pen-and-paper" method described above. Once we have this number we divide n by it to increase or decrease it to a value with a single digit integer part.

Obviously we need a couple of examples don't we? Here goes then.

n = 33729

exponent = floor(log10(33729)) = 4

10^{4} = 10000

significand = 33729 / 10000 = 3.3729

scientific notation = 3.3729 × 10^{4}

Let's have another example, this time with a very small number.

n = 0.000000642

exponent = floor(log10(0.000000642)) = -7

10^{-7} = 0.0000001

significand = 0.000000642 / 0.0001 = 6.42

scientific notation = 6.42 × 10^{-7}

Next we flip the significand if n is negative, and finally return the significand and exponent as a tuple.

## The __sig_exp_to_sn_string Function

This function takes a tuple generated by __get_sig_exp and builds a string around it. There are two optional arguments, use_superscript and dp to set the number of decimal places in the significand.

Firstly we check whether the tuple is None which it will be if n was 0. In this case we just return the string "0".

Next we create a string for the exponent, the format depending on use_superscript. If it is True we call __to_superscript which I'll look at later. If not the string will be "^" plus the non-superscript exponent.

Finally we just need to assemble the scientific notation string. Note the {dp} to set the number of decimal places in the significand. (Yes, you can embed {} within {}.)

## The __to_superscript Function

This function takes a number and returns a string containing the number in superscript characters. The number is converted to a string, a new empty string is created for the result, and then we iterate the first string, adding the superscript equivalent of each digit to the result.

As I mentioned earlier, digits and the minus sign are mapped to their superscript equivalents in the superscript dictionary.

Now we'll look at scientificnotationdemo.py which contains a few functions to try out the above module.

scientificnotationdemo.py

import scientificnotation as sn

def main():

print("-----------------------")

print("| codedrome.com |")

print("| Scientific Notation |")

print("-----------------------\n")

python_output()

# module_demo()

# show_table()

def python_output():

very_small = 0.000000000003782

very_large = 33589900145728984.0

print(f"{very_small:e}")

print(f"{very_small:.15f}")

print()

print(f"{very_large:e}")

print(f"{very_large:.0f}")

def module_demo():

very_small = 0.000000000005782

very_large = 33589900145728984

print(f"{sn.num_to_sn_string(very_small)}")

print(f"{sn.num_to_sn_string(-very_small)}")

print(f"{sn.num_to_sn_string(very_large)}")

print(f"{sn.num_to_sn_string(-very_large)}")

print(f"{sn.num_to_sn_string(0)}")

print()

print(f"{sn.num_to_sn_string(very_large, True)}")

print(f"{sn.num_to_sn_string(very_large, False)}")

def show_table():

n = 3.794

# n = -3.794

dp = 27

for x in range(-24, 24):

c = n * 10**x

scinot_string = sn.num_to_sn_string(c)

print(f" {c:>32,.{dp}f}", end="")

print(f" {c:.3e}", end="")

print(f" {scinot_string:<14} ")

if dp > 0:

dp -= 1

if __name__ == "__main__":

main()

## The main Function

Here we can call the three following function one at a time by commenting/uncommenting them.

## python_output

The puropse of this function is to show the default Python scientific notation, including how to force its use with :e.

Run the program like this.

Running the Program

python3 scientificnotationdemo.py

This is the output.

## module_demo

After creating a couple of numbers we pass them to num_to_sn_string and print the results, including negative versions. I have also included a call to num_to_sn_string with 0.

Next are two more calls to num_to_sn_string, one with use_superscript as True and the other with False so we can compare the results. Uncomment module_demo() in main and run the program again.

Here we can see we get the correct negative significands for negative numbers and the correct result for 0.

When using superscript with a monospaced font such as here, if there is more than one digit they are spread out which looks a bit stupid. I would therefore suggest that you set use_superscript to False for terminal output.

## show_table

This function prints a range of numbers from the very small to the very large. These are obtained by starting with a very ordinary looking number with a single digit integer part, in this case 3.794, and within a loop raising it to the power of a range of integers between -24 and 24. We then output each number together with both its Python-generated and our module-generated versions of scientific notation. Note the use of .3e to force Python to use scientific notation even when it wouldn't normally.

The number of decimal places we need to show starts at 27 and then decreases to 0 about half way through the loop. This is stored in the dp variable which is decremented until it reaches 0.

Uncomment show_table in main and run the program. This is the result. You can try starting off with a negative number such as -3.794 if you wish.