Wipy no float and math!
- roncromberge
- Posts: 8
- Joined: Tue Jan 12, 2016 8:04 pm
Wipy no float and math!
Hello,
I have a question. Why have the wipy no float and math lib??? One reason is lack of memory. Sure good answer, But the esp8266 implementation with far less memory has at least float arithmetic build in... >>> 12/2 gives me a proper 6.0 as answer.
Is it in the pipeline to implement float / math in the wipy?
Greetz,
Ron
I have a question. Why have the wipy no float and math lib??? One reason is lack of memory. Sure good answer, But the esp8266 implementation with far less memory has at least float arithmetic build in... >>> 12/2 gives me a proper 6.0 as answer.
Is it in the pipeline to implement float / math in the wipy?
Greetz,
Ron
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Wipy no float and math!
Alas the answer is no. See http://forum.micropython.org/viewtopic. ... 4&start=20.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
- roncromberge
- Posts: 8
- Joined: Tue Jan 12, 2016 8:04 pm
Re: Wipy no float and math!
Yes, I've read it. when I plowed through the forum. I am a novice user in both micro-controllers and micro-Python. So I can not judge how much memory takes a minimum float and math implementation. I've seen come across a number of 40kb, but have no idea how many percent of the space it occupies.
Currently a esp8266 board with micro-python is for me the choice to continue. (even a model-01 does the work.) (I'm one of the backers of the esp8266 kickstart). Too bad for my two wipies, that can't do their job without float.
Pity!
Translated from dutch 2 English via Google Translate.
Currently a esp8266 board with micro-python is for me the choice to continue. (even a model-01 does the work.) (I'm one of the backers of the esp8266 kickstart). Too bad for my two wipies, that can't do their job without float.
Pity!
Translated from dutch 2 English via Google Translate.
Re: Wipy no float and math!
What do you _need_ floating point for?
Reconsider how you implement things for microcontrollers.
Floating point is generally overkill for most all applications. Merely a convenience that desktop computer users have become used to.
Reconsider how you implement things for microcontrollers.
Floating point is generally overkill for most all applications. Merely a convenience that desktop computer users have become used to.
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Wipy no float and math!
Perhaps a sweeping statement: it rather depends on what you're doing. Anything involving G sensors, magnetometers and gyros leads you into floating point and trigonometry. DSP processor chips have been available with floating point since the late 1980's for one good reason: while you can do DSP in fixed point it needs a great deal of care with regard to scaling.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Wipy no float and math!
@roncromberge
Hi Ron,
I've written a simple and kludgy WiPy float package that you are welcome to try, including add/subtract/multiply/divide. On my WiPy it takes about 5 Kb as measured by gc.mem_free(), leaving 45 Kb free (the test module takes much more but isn't normally needed).
Warning - there is approximately no error checking. So far it seems to behave reasonably over a useful range, and it's hopefully OK for sensor readings and similar.
Example usage for a hypothetical temperature conversion:
Please study the tests for more info.
Best regards,
Jim
WiPyFloat.py
Float_tests.py
Hi Ron,
I've written a simple and kludgy WiPy float package that you are welcome to try, including add/subtract/multiply/divide. On my WiPy it takes about 5 Kb as measured by gc.mem_free(), leaving 45 Kb free (the test module takes much more but isn't normally needed).
Warning - there is approximately no error checking. So far it seems to behave reasonably over a useful range, and it's hopefully OK for sensor readings and similar.
Example usage for a hypothetical temperature conversion:
Code: Select all
f1 = Float("299.34") # e.g. an absolute temperature from somewhere
f2 = Float.subtract(f1, Float(273)) # compute f2 = f1 - 273
print(f2.str() + "deg C") # "26.34degC"
Best regards,
Jim
WiPyFloat.py
Code: Select all
# Float.py
# Last modified: 26 Aug 2016
# J.G.Davies, Jacobus Systems Ltd., Brighton & Hove, UK.
# This code is hereby released as Public Domain. I would appreciate an acknowledgement if you find it useful.
class Float():
mantissa = 0
exponent = 0
def __init__(self, f=None):
if f:
if (type(f).__name__ == "Float"):
self.mantissa = f.mantissa
self.exponent = f.exponent
elif (type(f).__name__ == "int"):
self.mantissa = int(f)
self.exponent = 0
elif (type(f).__name__ == "str"):
self.load(str(f))
self.normalise()
return
def __eq__(self, other):
self.normalise()
other.normalise()
return (self.mantissa == other.mantissa) and (self.exponent == other.exponent)
def __str__(self):
return self.str()
# Static methods.
@staticmethod
def add(f1, f2):
# Return a new Float holding (f1 + f2).
result = Float(f1)
result.add_float(f2)
return result
@staticmethod
def divide(f1, f2):
# Return a new Float holding (f1 / f2).
result = Float(f1)
result.divide_float(f2)
return result
@staticmethod
def multiply(f1, f2):
# Return a new Float holding (f1 * f2).
result = Float(f1)
result.multiply_float(f2)
return result
@staticmethod
def subtract(f1, f2):
# Return a new Float holding (f1 - f2).
result = Float(f1)
result.subtract_float(f2)
return result
# Class methods.
def add_float(self, f, add=True):
# Add Float 'f' to this Float.
# Align the exponents.
self_mantissa = self.mantissa
self_exponent = self.exponent
f_mantissa = f.mantissa
f_exponent = f.exponent
if (f_exponent < self_exponent):
shift = self_exponent - f_exponent
self_mantissa *= 10 ** shift
self_exponent -= shift
elif (f_exponent > self_exponent):
shift = f_exponent - self_exponent
f_mantissa *= 10 ** shift
f_exponent -= shift
if add:
self.mantissa = self_mantissa + f_mantissa
else:
self.mantissa = self_mantissa - f_mantissa
self.exponent = self_exponent
self.normalise()
return self
def debug(self):
s = "Float: {0}, {1}".format(self.mantissa, self.exponent)
return s
def divide_float(self, f):
# Divide this Float by 'f'.
self.mantissa *= 100000000
self.exponent -= 8
self.mantissa //= f.mantissa
self.exponent -= f.exponent
self.normalise()
return self
def int(self):
result = 0
return result
def load(self, s):
s = s.strip().lower()
pos = True
if (s[0] == "+"):
s = s[1:]
elif (s[0] == "-"):
s = s[1:]
pos = False
if "e" in s:
return self.load_scientific(s, pos)
if "." in s:
whole, decimal = s.split(".")
if not decimal:
# There is no decimal part.
# TODO: if 'whole' ends in zeros, could scale the number and set self.exponent.
# This would help keep 'mantissa' a simple 31-bit int on the WiPy.
self.mantissa = int(whole)
if not pos: self.mantissa = -self.mantissa
self.exponent = 0
self.normalise()
return True
# There is a decimal part.
if whole and (int(whole) == 0):
whole = None
if not whole:
# There is no whole part.
value, nleading = self.parse_decimal(decimal)
self.mantissa = int(value)
if not pos: self.mantissa = -self.mantissa
self.exponent = -nleading - len(value)
self.normalise()
return True
# There is a whole part and a decimal part.
whole = whole.lstrip("0")
decimal = decimal.rstrip('0')
self.mantissa = int(whole + decimal)
if not pos: self.mantissa = -self.mantissa
self.exponent = -len(str(decimal))
self.normalise()
else:
# TODO: if 'whole' ends in zeros, could scale the number and set self.exponent.
# This would help keep 'mantissa' a simple 31-bit int on the WiPy.
self.mantissa = int(s)
if not pos: self.mantissa = -self.mantissa
self.exponent = 0
self.normalise()
return True
return True
def multiply_float(self, f):
# Multiply this Float by 'f'.
self.mantissa *= f.mantissa
self.exponent += f.exponent
self.normalise()
return self
def normalise(self):
# Normalise where possible.
if (self.mantissa != 0):
s = str(self.mantissa)
stripped = s.rstrip("0")
diff = len(s) - len(stripped)
if (diff > 0):
self.mantissa = int(stripped)
self.exponent += diff
return True
def str(self, format="F"):
if ("E" in format):
return self.str_scientific()
# Use format "F".
pos = (self.mantissa >= 0)
result = str(abs(self.mantissa))
if (self.exponent > 0):
result += "0" * self.exponent
elif (self.exponent < 0):
if (-self.exponent < len(result)):
cut = len(result) + self.exponent
result = result[:cut] + "." + result[cut:]
else:
n_zeros = -self.exponent - len(result)
result = "0." + ("0" * n_zeros) + result
if not pos: result = "-" + result
return result
def subtract_float(self, f):
# Subtract Float 'f' from this Float.
return self.add_float(f, False)
# Helper methods.
def load_scientific(self, s, pos):
mant, exp = s.split("e")
self.load(mant)
self.exponent += int(exp)
self.normalise()
return False
def parse_decimal(self, s):
nleading = 0
for c in s:
if (c != "0"): break
nleading += 1
if (nleading == 0):
value = s
else:
value = s[nleading:]
return value, nleading
def str_scientific(self):
pos = (self.mantissa >= 0)
result = str(abs(self.mantissa))
len_mantissa = len(result)
if (len(result) > 1):
result = result[:1] + "." + result[1:]
result = result.rstrip("0")
result = result.rstrip(".")
result += "e" + str(self.exponent + len_mantissa - 1)
if not pos: result = "-" + result
return result
#import Float_tests
Code: Select all
# Float_tests.py
# Last modified: 26 Aug 2016
# J.G.Davies, Jacobus Systems Ltd., Brighton & Hove, UK.
# This code is hereby released as Public Domain. I would appreciate an acknowledgement if you find it useful.
from WiPyFloat import Float
def test1():
# 'load' and 'str' methods.
print("test1 - load / str")
f1 = Float()
f1.load("1.3")
print(PassFail(f1.str() == "1.3"), "1.3 ->", f1, f1.str("E"))
f1.load("-1.3")
print(PassFail(f1.str() == "-1.3"), "-1.3 ->", f1, f1.str("E"))
f1.load("2000.05")
print(PassFail(f1.str() == "2000.05"), "2000.05 ->", f1, f1.str("E"))
f1.load("0.01")
print(PassFail(f1.str() == "0.01"), "0.01 ->", f1, f1.str("E"))
f1.load("0.0123")
print(PassFail(f1.str() == "0.0123"), "0.0123 ->", f1, f1.str("E"))
f1.load("0.00123")
print(PassFail(f1.str() == "0.00123"), "0.00123 ->", f1, f1.str("E"))
f1.load("1.23e-3")
print(PassFail(f1.str() == "0.00123"), "1.23e-3 ->", f1, f1.str("E"))
f1.load("1.23e6")
print(PassFail(f1.str() == "1230000"), "1.23e6 ->", f1, f1.str("E"))
f1.load("5e6")
print(PassFail(f1.str() == "5000000"), "5e6 ->", f1, f1.str("E"))
print("")
return True
def test2():
# Addition.
print("test2 - Addition")
f1 = Float()
f2 = Float()
f1.load("2000.05")
f2.load("0.001")
f3 = Float.add(f1, f2)
print(PassFail(f3.str() == "2000.051"), f1, "+", f2, "->", f3, f3.str("E"))
f1.load("0.1")
f2.load("0.2")
f3 = Float.add(f1, f2)
print(PassFail(f3.str() == "0.3"), f1, "+", f2, "->", f3, f3.str("E"))
f1.load("500")
f2.load("600")
f3 = Float.add(f1, f2)
print(PassFail(f3.str() == "1100"), f1, "+", f2, "->", f3, f3.str("E"))
f1.load("500")
f2.load("-600")
f3 = Float.add(f1, f2)
print(PassFail(f3.str() == "-100"), f1, "+", f2, "->", f3, f3.str("E"))
f1.load("-500")
f2.load("600")
f3 = Float.add(f1, f2)
print(PassFail(f3.str() == "100"), f1, "+", f2, "->", f3, f3.str("E"))
print("")
return True
def test3():
# Subtraction.
print("test3 - Subtraction")
f1 = Float()
f2 = Float()
f1.load("2000.05")
f2.load("0.001")
f3 = Float.subtract(f1, f2)
print(PassFail(f3.str() == "2000.049"), f1, "-", f2, "->", f3, f3.str("E"))
f1.load("0.1")
f2.load("0.2")
f3 = Float.subtract(f1, f2)
print(PassFail(f3.str() == "-0.1"), f1, "-", f2, "->", f3, f3.str("E"))
f1.load("500")
f2.load("600")
f3 = Float.subtract(f1, f2)
print(PassFail(f3.str() == "-100"), f1, "-", f2, "->", f3, f3.str("E"))
f1.load("500")
f2.load("-600")
f3 = Float.subtract(f1, f2)
print(PassFail(f3.str() == "1100"), f1, "-", f2, "->", f3, f3.str("E"))
f1.load("-500")
f2.load("600")
f3 = Float.subtract(f1, f2)
print(PassFail(f3.str() == "-1100"), f1, "-", f2, "->", f3, f3.str("E"))
print("")
return True
def test4():
# Multiplication.
print("test4 - Multiplication")
f1 = Float()
f2 = Float()
f1.load("5")
f2.load("6")
f3 = Float.multiply(f1, f2)
print(PassFail(f3.str() == "30"), f1, "*", f2, "->", f3, f3.str("E"))
f1.load("500")
f2.load("600")
f3 = Float.multiply(f1, f2)
print(PassFail(f3.str() == "300000"), f1, "*", f2, "->", f3, f3.str("E"))
f1.load("5e3")
f2.load("6e3")
f3 = Float.multiply(f1, f2)
print(PassFail(f3.str() == "30000000"), f1, "*", f2, "->", f3, f3.str("E"))
f1.load("5e6")
f2.load("6e6")
f3 = Float.multiply(f1, f2)
print(PassFail(f3.str() == "30000000000000"), f1, "*", f2, "->", f3, f3.str("E"))
print("")
return True
def test5():
# Division.
print("test5 - Division")
f1 = Float()
f2 = Float()
f1.load("1")
f2.load("3")
f3 = Float.divide(f1, f2)
print(PassFail(f3.str()[:7] == "0.33333"), f1, "/", f2, "->", f3, f3.str("E"), f3.debug())
f1.load("5")
f2.load("6")
f3 = Float.divide(f1, f2)
print(PassFail(f3.str()[:7] == "0.83333"), f1, "/", f2, "->", f3, f3.str("E"), f3.debug())
f1.load("500")
f2.load("600")
f3 = Float.divide(f1, f2)
print(PassFail(f3.str()[:7] == "0.83333"), f1, "/", f2, "->", f3, f3.str("E"), f3.debug())
f1.load("5e3")
f2.load("6e3")
f3 = Float.divide(f1, f2)
print(PassFail(f3.str()[:7] == "0.83333"), f1, "/", f2, "->", f3, f3.str("E"), f3.debug())
f1.load("5e6")
f2.load("6e6")
f3 = Float.divide(f1, f2)
print(PassFail(f3.str()[:7] == "0.83333"), f1, "/", f2, "->", f3, f3.str("E"), f3.debug())
print("")
return True
def test6():
# Object creation options.
print("test6 - Object creation")
f1 = Float()
print(PassFail(f1.str() == "0"), f1, f1.debug())
f1 = Float(123)
print(PassFail(f1.str() == "123"), f1, f1.debug())
f1 = Float("3.1416")
print(PassFail(f1.str() == "3.1416"), f1, f1.debug())
f2 = Float(f1)
print(PassFail(f1.str() == "3.1416"), f2, f2.debug())
print("")
return True
def test7():
# Equality / Inequality.
print("test7 - Equality / Inequality")
f1 = Float(123)
f2 = Float("123")
f3 = Float("123.0001")
print(PassFail(f1 == f2), "(", f1, "==", f2, ") is", f1 == f2)
print(PassFail(f1 != f3), "(", f1, "==", f3, ") is", f1 == f3)
print(PassFail(f1 == f2), "(", f1, "!=", f2, ") is", f1 != f2)
print(PassFail(f1 != f3), "(", f1, "!=", f3, ") is", f1 != f3)
print("")
return True
def PassFail(cond):
return ("PASS" if cond else "FAIL") + ": "
test1()
test2()
test3()
test4()
test5()
test6()
test7()
Re: Wipy no float and math!
It bears mentioning that 30 years ago, we accomplished a great deal with 8-bit integer-only operations on small micro controllers so to say you "need" floating point is a bit of an overstatement, though it may make things easier. Everything devolves to integer math under the covers, anyway and, ultimately, you can only get an integer response at some number of significant digits.
The secret to doing things with integer math is to decide how much precision you need (better than 0.5% is achieved with 8 significant bits, 0.1% with 10 bits and so on.) Maintaining n bits of precision across your calculations requires up-scaling for operations and then down-scaling when you are done, mostly for division operations. For instance to maintain 8 bits of integer precision in a divide operation you need to upscale your numerator by 8 bits (multiply by 256), do your integer division with an unscaled denominator, then downscale the answer by 8 bits (integer divide by 256). You must consider your problem space carefully to determine the scaling required (bits of precision and dynamic range of your numbers). Chained operation can introduce compounding rounding errors that require additional bits of precision in the scaling operations. Trigonometric and logarithmic operations require surprisingly small tables of pre-calculated values along with small algorithms to calculate the answer based on table look-ups and mathematical laws (for instance, you only need trig look-ups for 0-45 degrees to get values for an entire circle.)
Properly done, this will be always be faster than software-based floating point and frequently faster than hardware floating point. It also bears mentioning that most people tend to overstate their required precision. Look at all aspects of your control loop and figure out the precision and error for each step. If you are doing anything with a timing loop, for instance, the WiPy seems to only have an error of +/- 500us even though the stated precision is 1us.
The secret to doing things with integer math is to decide how much precision you need (better than 0.5% is achieved with 8 significant bits, 0.1% with 10 bits and so on.) Maintaining n bits of precision across your calculations requires up-scaling for operations and then down-scaling when you are done, mostly for division operations. For instance to maintain 8 bits of integer precision in a divide operation you need to upscale your numerator by 8 bits (multiply by 256), do your integer division with an unscaled denominator, then downscale the answer by 8 bits (integer divide by 256). You must consider your problem space carefully to determine the scaling required (bits of precision and dynamic range of your numbers). Chained operation can introduce compounding rounding errors that require additional bits of precision in the scaling operations. Trigonometric and logarithmic operations require surprisingly small tables of pre-calculated values along with small algorithms to calculate the answer based on table look-ups and mathematical laws (for instance, you only need trig look-ups for 0-45 degrees to get values for an entire circle.)
Properly done, this will be always be faster than software-based floating point and frequently faster than hardware floating point. It also bears mentioning that most people tend to overstate their required precision. Look at all aspects of your control loop and figure out the precision and error for each step. If you are doing anything with a timing loop, for instance, the WiPy seems to only have an error of +/- 500us even though the stated precision is 1us.
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Wipy no float and math!
OK but some algorithms require floating point. One example is Madgwick sensor fusion used in robotics and drones. Also astronomical calculations such as predicting sun and moon rise and set.
Other algorithms can be performed using integers but with limitations: fast fourier transforms being an example. An integer implementation works for small numbers of frequency bins and low precision, but rounding errors soon accumulate and degrade the results if you need more bins/precision. Error accumulation applies to other filtering algorithms too.
I could go on, but I'm sure you get the picture. I've designed systems based on integer arithmetic, in the days when there was no choice. Guaranteeing that there will never be overflow or underflow under all conditions of inputs can be a serious hassle.
Adding some floating point capability to the WiPy is seriously useful in my opinion.
Other algorithms can be performed using integers but with limitations: fast fourier transforms being an example. An integer implementation works for small numbers of frequency bins and low precision, but rounding errors soon accumulate and degrade the results if you need more bins/precision. Error accumulation applies to other filtering algorithms too.
I could go on, but I'm sure you get the picture. I've designed systems based on integer arithmetic, in the days when there was no choice. Guaranteeing that there will never be overflow or underflow under all conditions of inputs can be a serious hassle.
Adding some floating point capability to the WiPy is seriously useful in my opinion.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Wipy no float and math!
The existing but empty presence of the WiPy on the Pycom website makes me believe, that it is an abandoned product.
And yes adding float support would a nice addition. Counting the number of requests or false assumptions for this, also lets me assume this might be reasonably invested time.
Of course there are reasons against this, but maybe providing an additional build with enabled float emulation and a warning for the trade-off is reasonable.
My use-cases would let me reuse unmodified sensor drivers, which otherwise need to be reworked for fixed-point arithmetic. This is not that of an issue by itself, but this is somewhat wasted work for the probably abandoned WiPy, being the only non-float platform of MicroPython. Simply using the ESP port is a far easier at the moment.
And yes adding float support would a nice addition. Counting the number of requests or false assumptions for this, also lets me assume this might be reasonably invested time.
Of course there are reasons against this, but maybe providing an additional build with enabled float emulation and a warning for the trade-off is reasonable.
My use-cases would let me reuse unmodified sensor drivers, which otherwise need to be reworked for fixed-point arithmetic. This is not that of an issue by itself, but this is somewhat wasted work for the probably abandoned WiPy, being the only non-float platform of MicroPython. Simply using the ESP port is a far easier at the moment.
Re: Wipy no float and math!
Maybe it's just that special link. On another spot in this site, it's still there: https://www.pycom.io/solutions/py-boards/wipy/ and available in the web shop. And yes, floating point would be nice, but as discussed at other places, it will need additional RAM space, since, as I've been told by Damien, all code on WiPy is executed in RAM.The existing but empty presence of the WiPy on the Pycom website makes me believe, that it is an abandoned product.