Quick Check

From the signatures, we can tell that they are different:

pow(x, y[, z])

math.pow(x, y)

Also, trying it in the shell will give you a quick idea:

>>> pow is math.pow
False

Testing the differences

Another way to understand the differences in behaviour between the two functions is to test for them:

import math
import traceback
import sys

inf = float("inf")
NaN = float("nan")

vals = [inf, NaN, 0.0, 1.0, 2.2, -1.0, -0.0, -2.2, -inf, 1, 0, 2]

tests = set([])

for vala in vals:
  for valb in vals:
    tests.add( (vala, valb) )
    tests.add( (valb, vala) )


for a,b in tests:
  print("math.pow(%f,%f)"%(a,b) )
  try:
    print("    %f "%math.pow(a,b))
  except:
    traceback.print_exc()
  
  print("__builtins__.pow(%f,%f)"%(a,b) )
  try:
    print("    %f "%__builtins__.pow(a,b))
  except:
    traceback.print_exc()

We can then notice some subtle differences. For example:

math.pow(0.000000,-2.200000)
    ValueError: math domain error

__builtins__.pow(0.000000,-2.200000)
    ZeroDivisionError: 0.0 cannot be raised to a negative power

There are other differences, and the test list above is not complete (no long numbers, no complex, etc...), but this will give us a pragmatic list of how the two functions behave differently. I would also recommend extending the above test to check for the type that each function returns. You could probably write something similar that creates a report of the differences between the two functions.

math.pow()

math.pow() handles its arguments very differently from the builtin ** or pow(). This comes at the cost of flexibility. Having a look at the source, we can see that the arguments to math.pow() are cast directly to doubles:

static PyObject *
math_pow(PyObject *self, PyObject *args)
{
    PyObject *ox, *oy;
    double r, x, y;
    int odd_y;

    if (! PyArg_UnpackTuple(args, "pow", 2, 2, &ox, &oy))
        return NULL;
    x = PyFloat_AsDouble(ox);
    y = PyFloat_AsDouble(oy);
/*...*/

The checks are then carried out against the doubles for validity, and then the result is passed to the underlying C math library.

builtin pow()

The built-in pow() (same as the ** operator) on the other hand behaves very differently, it actually uses the Objects's own implementation of the ** operator, which can be overridden by the end user if need be by replacing a number's __pow__(), __rpow__() or __ipow__(), method.

For built-in types, it is instructive to study the difference between the power function implemented for two numeric types, for example, floats, long and complex.

Overriding the default behaviour

Emulating numeric types is described here. essentially, if you are creating a new type for numbers with uncertainty, what you will have to do is provide the __pow__(), __rpow__() and possibly __ipow__() methods for your type. This will allow your numbers to be used with the operator:

class Uncertain:
  def __init__(self, x, delta=0):
    self.delta = delta
    self.x = x
  def __pow__(self, other):
    return Uncertain(
      self.x**other.x, 
      Uncertain._propagate_power(self, other)
    )
  @staticmethod
  def _propagate_power(A, B):
    return math.sqrt(
      ((B.x*(A.x**(B.x-1)))**2)*A.delta*A.delta +
      (((A.x**B.x)*math.log(B.x))**2)*B.delta*B.delta
    )

In order to override math.pow() you will have to monkey patch it to support your new type:

def new_pow(a,b):
    _a = Uncertain(a)
    _b = Uncertain(b)
    return _a ** _b

math.pow = new_pow

Note that for this to work you'll have to wrangle the Uncertain class to cope with an Uncertain instance as an input to __init__()

Answer from brice on Stack Overflow
🌐
TutorialsPoint
tutorialspoint.com › python-program-to-convert-float-to-exponential
Python program to convert float to exponential
Here, the float number 9876543.21 is converted to exponential notation as 9.87654321e+06. The "e+06" signifies that the decimal point has been shifted six places to the right. Let's discuss the different approaches in Python.
Top answer
1 of 4
60

Quick Check

From the signatures, we can tell that they are different:

pow(x, y[, z])

math.pow(x, y)

Also, trying it in the shell will give you a quick idea:

>>> pow is math.pow
False

Testing the differences

Another way to understand the differences in behaviour between the two functions is to test for them:

import math
import traceback
import sys

inf = float("inf")
NaN = float("nan")

vals = [inf, NaN, 0.0, 1.0, 2.2, -1.0, -0.0, -2.2, -inf, 1, 0, 2]

tests = set([])

for vala in vals:
  for valb in vals:
    tests.add( (vala, valb) )
    tests.add( (valb, vala) )


for a,b in tests:
  print("math.pow(%f,%f)"%(a,b) )
  try:
    print("    %f "%math.pow(a,b))
  except:
    traceback.print_exc()
  
  print("__builtins__.pow(%f,%f)"%(a,b) )
  try:
    print("    %f "%__builtins__.pow(a,b))
  except:
    traceback.print_exc()

We can then notice some subtle differences. For example:

math.pow(0.000000,-2.200000)
    ValueError: math domain error

__builtins__.pow(0.000000,-2.200000)
    ZeroDivisionError: 0.0 cannot be raised to a negative power

There are other differences, and the test list above is not complete (no long numbers, no complex, etc...), but this will give us a pragmatic list of how the two functions behave differently. I would also recommend extending the above test to check for the type that each function returns. You could probably write something similar that creates a report of the differences between the two functions.

math.pow()

math.pow() handles its arguments very differently from the builtin ** or pow(). This comes at the cost of flexibility. Having a look at the source, we can see that the arguments to math.pow() are cast directly to doubles:

static PyObject *
math_pow(PyObject *self, PyObject *args)
{
    PyObject *ox, *oy;
    double r, x, y;
    int odd_y;

    if (! PyArg_UnpackTuple(args, "pow", 2, 2, &ox, &oy))
        return NULL;
    x = PyFloat_AsDouble(ox);
    y = PyFloat_AsDouble(oy);
/*...*/

The checks are then carried out against the doubles for validity, and then the result is passed to the underlying C math library.

builtin pow()

The built-in pow() (same as the ** operator) on the other hand behaves very differently, it actually uses the Objects's own implementation of the ** operator, which can be overridden by the end user if need be by replacing a number's __pow__(), __rpow__() or __ipow__(), method.

For built-in types, it is instructive to study the difference between the power function implemented for two numeric types, for example, floats, long and complex.

Overriding the default behaviour

Emulating numeric types is described here. essentially, if you are creating a new type for numbers with uncertainty, what you will have to do is provide the __pow__(), __rpow__() and possibly __ipow__() methods for your type. This will allow your numbers to be used with the operator:

class Uncertain:
  def __init__(self, x, delta=0):
    self.delta = delta
    self.x = x
  def __pow__(self, other):
    return Uncertain(
      self.x**other.x, 
      Uncertain._propagate_power(self, other)
    )
  @staticmethod
  def _propagate_power(A, B):
    return math.sqrt(
      ((B.x*(A.x**(B.x-1)))**2)*A.delta*A.delta +
      (((A.x**B.x)*math.log(B.x))**2)*B.delta*B.delta
    )

In order to override math.pow() you will have to monkey patch it to support your new type:

def new_pow(a,b):
    _a = Uncertain(a)
    _b = Uncertain(b)
    return _a ** _b

math.pow = new_pow

Note that for this to work you'll have to wrangle the Uncertain class to cope with an Uncertain instance as an input to __init__()

2 of 4
43

math.pow() implicitly converts its arguments to float:

>>> from decimal import Decimal
>>> from fractions import Fraction
>>> math.pow(Fraction(1, 3), 2)
0.1111111111111111
>>> math.pow(Decimal(10), -1)
0.1

but the built-in pow does not:

>>> pow(Fraction(1, 3), 2)
Fraction(1, 9)
>>> pow(Decimal(10), -1)
Decimal('0.1')

My goal is to provide an implementation of both the built-in pow() and of math.pow() for numbers with uncertainty

You can overload pow and ** by defining __pow__ and __rpow__ methods for your class.

However, you can't overload math.pow (without hacks like math.pow = pow). You can make a class usable with math.pow by defining a __float__ conversion, but then you'll lose the uncertainty attached to your numbers.

🌐
GeeksforGeeks
geeksforgeeks.org › python-frexp-function
Python | frexp() Function | GeeksforGeeks
February 14, 2023 - frexp() function is one of the Standard math Library function in Python. It returns mantissa and exponent as a pair (m, e) of a given value x, where mantissa m is a floating point number and e exponent is an integer value. m is a float and e is an integer such that x == m * 2**e exactly.
🌐
datagy
datagy.io › home › python posts › python exponentiation: use python to raise numbers to a power
Python Exponentiation: Use Python to Raise Numbers to a Power • datagy
December 19, 2022 - The Python exponent operator works with both int and float datatypes, returning a float if any of the numbers are floats.
🌐
GeeksforGeeks
geeksforgeeks.org › python-program-to-convert-float-to-exponential
Python program to convert float to exponential | GeeksforGeeks
October 26, 2021 - Given a float number, the task is to write a Python program to convert float to exponential.
🌐
Learning About Electronics
learningaboutelectronics.com › Articles › How-to-raise-a-number-to-a-power-in-Python.php
How to Raise a Number to a Power in Python
The number preceding this operator is the base and the number following this operator is the exponent. Just to show some variations, let's show an example code, where a user can enter a base and an exponent and we calculate the power of this calculation. This is shown in the code below.
Find elsewhere
🌐
Berkeley
pythonnumericalmethods.studentorg.berkeley.edu › notebooks › chapter09.02-Floating-Point-Numbers.html
Floating Point Numbers — Python Numerical Methods
Instead of utilizing each bit as the coefficient of a power of 2, floats allocate bits to three different parts: the sign indicator, \(s\), which says whether a number is positive or negative; characteristic or exponent, \(e\), which is the power of 2; and the fraction, \(f\), which is the coefficient of the exponent. Almost all platforms map Python floats to the IEEE754 double precision - 64 total bits.
🌐
Python documentation
docs.python.org › 3 › tutorial › floatingpoint.html
15. Floating-Point Arithmetic: Issues and Limitations — Python 3.14.3 documentation
Stop at any finite number of bits, and you get an approximation. On most machines today, floats are approximated using a binary fraction with the numerator using the first 53 bits starting with the most significant bit and with the denominator as a power of two.
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.frexp.html
numpy.frexp — NumPy v2.3 Manual
Floating values between -1 and 1. This is a scalar if x is a scalar. ... Integer exponents of 2.
🌐
GeeksforGeeks
geeksforgeeks.org › python › python-program-to-convert-exponential-to-float
Python program to convert exponential to float - GeeksforGeeks
July 23, 2025 - First, we will declare an exponential number and save it in a variable. Then we will use the float() function to convert it to float datatype. Then we will print the converted number. ... The float() method is used to return a floating-point number from a number or a string.
🌐
TutorialsPoint
tutorialspoint.com › get-the-number-of-bits-in-the-exponent-portion-of-the-floating-point-representation-in-python
Get the number of bits in the exponent portion of the floating point representation in Python
# Checking for float16 type # The iexp is to get the number of bits in the exponent portion # The min is the minimum value of given dtype. # The max is the minimum value of given dtype. a = np.finfo(np.float16(45.9)) print("Number of bits in the exponent portion float16 type...\n",a.iexp) print("Minimum of float16 type...\n",a.min) print("Maximum of float16 type...\n",a.max) # Checking for float32 type with instances b = np.finfo(np.float32(22.3)) print("\nNumber of bits in the exponent portion float32 type...\n",b.iexp) print("Minimum of float32 type...\n",b.min) print("Maximum of float32 type...\n",b.max) # Checking for float type with instances c = np.finfo(np.float64(29.2)) print("\nNumber of bits in the exponent portion float64 type...\n",c.iexp) print("Minimum of float64 type...\n",c.min) print("Maximum of float64 type...\n",c.max)
🌐
University of Toronto
cs.toronto.edu › ~guerzhoy › 180f16 › lectures › W01 › lec3 › numericalTypes.html
Numerical types in Python
Because the exponent can have only a limited number of digits, a float cannot be arbitrarily large (or arbitrarily close to 0). Because the matissa can only have a limited number of digits, a float cannot be arbitrarily precise (i.e., basically, it cannot have arbitrarily #many digits after the decimal points). Here is an example: we cannot store $10^350$ as a float (Python considers the power of 10.0 to always be a float)
🌐
PrepBytes
prepbytes.com › home › python › python power function
Python Power Function
October 31, 2023 - The syntax for using the math.pow() function in Python is as follows: ... Here, x and y are the two input parameters where x is the base and y is the exponent. The function returns the value of x raised to the power of y as a floating-point number.
🌐
Python
docs.python.org › 3 › library › decimal.html
decimal — Decimal fixed-point and floating-point arithmetic
If value is a tuple, it should ... (1, 4, 1, 4), -3)) returns Decimal('1.414'). If value is a float, the binary floating-point value is losslessly converted to its exact decimal equivalent....
🌐
GitHub
github.com › sammchardy › python-binance › issues › 168
functions returning exponential numbers instead of floats · Issue #168 · sammchardy/python-binance
January 4, 2018 - Hey everyone. So I have a method thats calculates maxquantity... actually there have been a few instances where I am trying to get back a float value and it comes out as an exponential number. I have read that python should read both flo...
Published   Feb 07, 2018