Decimal-fraction precision question

Questions and discussion about running MicroPython on a micro:bit board.
Target audience: MicroPython users with a micro:bit.
Post Reply
MicroGuy
Posts: 17
Joined: Sun Jan 17, 2021 8:31 pm

Decimal-fraction precision question

Post by MicroGuy » Sat Jul 03, 2021 5:29 pm

These two lines:
my_string = str(-0.3470) #Test value
basic.show_string(my_string)

yield the string "-0.347". That trailing zero is important in science and engineering because it indicates accuracy. For example, 0.3 mm is not the same as 0.30 mm. The latter indicates accuracy to a hundredth of a mm. So, it seems MakeCode saves the original value as -0.347, not as -0.3470.

Does MicroPython have a way to set the precision of a floating-point number so the trailing zero becomes part of the value and thus part of the string? Thanks. Happy July 4th. --Jon

User avatar
scruss
Posts: 360
Joined: Sat Aug 12, 2017 2:27 pm
Location: Toronto, Canada
Contact:

Re: Decimal-fraction precision question

Post by scruss » Sat Jul 03, 2021 6:29 pm

Sadly, no. I don't know of any (non-symbolic¹) programming language that retains that concept. I haven't seen the "trailing zeros indicate retained precision" convention since I was in grammar school in the 1980s and I've been a mechanical engineer for most of the time since then.

Because of the way that MicroPython stores floating point numbers, it can't even store '-0.3470' exactly. It's probably storing something closer in value to -0.34700000286102294921875 (the four bytes 0xbeb1a9fc: see IEEE-754 Floating Point Converter for details). The vast majority of computer systems work this way.

Most MicroPython ports have integer precision limited only by available memory. If you wish to retain precision, you could store numbers as ratios of two integers: something like -3470 / 10000. Unfortunately, you'd have to write all the code to ensure that all the calculations were done correctly, for as soon as it hits floating point, it's back to weird binary rounding again. The two Python data types that might help here — Decimal and Fraction — are not available for MicroPython.

If you're asking how to represent that number of decimal places, there are various ways to format floating point numbers, such as:

Code: Select all

print('%.4f' % -0.3470 )
-0.3470
but note that this is simple rounding:

Code: Select all

print('%.4f' % -0.34758 )
-0.3476
---
¹: such as maple, mathematica, maxima, etc. I suspect that the charmingly niche language Frink might retain trailing zeros, as it's the sort of thing it would do.

MicroGuy
Posts: 17
Joined: Sun Jan 17, 2021 8:31 pm

Re: Decimal-fraction precision question

Post by MicroGuy » Sun Jul 04, 2021 4:15 pm

Thank you very much for your insight. The information you provided is very helpful and appreciated. Happy July 4th.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Decimal-fraction precision question

Post by pythoncoder » Mon Jul 05, 2021 10:53 am

Alternatively (which I find more readable than the string modulus operator):

Code: Select all

print('{:6.3f}'.format(0.3))
The 3 guarantees that three digits will be printed to the right of the decimal point, yielding "0.300". This is the same pretty formatting by a different means and does not contradict the above observations about stored precision.
Peter Hinch
Index to my micropython libraries.

User avatar
scruss
Posts: 360
Joined: Sat Aug 12, 2017 2:27 pm
Location: Toronto, Canada
Contact:

Re: Decimal-fraction precision question

Post by scruss » Mon Jul 05, 2021 8:34 pm

Yes, .format() is probably more readable and recommended these days. I stick to the first thing I learned, so I'm perpetually annoyed that few languages support the one true FORTRAN codes ...

Thinking out loud (= ‘uh-oh, he has no idea what he's doing’) I wonder if working with numbers as strings might help? Entering a literal

Code: Select all

s=-0.3470
on the source will lose any trailing zeroes, but entering

Code: Select all

s="-0.3470"
will retain them. If there is a decimal point in the string s, len(s) - (s.find('.')+1) returns the number of decimal places in the string.

I don't know how to do this with .format(), and this is a not-remotely-useful outline of how to get data-defined precision in MicroPython, but something like:

Code: Select all

print("%*.*f" % (len(s),
                 len(s) - (s.find('.')+1),
                 float(s)))
would reconstruct your numeric string exactly.

Just be careful to remember that Python numbers-as-strings don't do what you might expect, but rather what you'd suspect: s+s returns the rather hilarious '-0.3470-0.3470' instead of the more sensible -0.694

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Decimal-fraction precision question

Post by pythoncoder » Tue Jul 06, 2021 5:57 am

One way to write programmatic format specifiers:

Code: Select all

x = 5
y = 3
print('{val:{wid}.{dec}f}'.format(val=0.3, wid=x, dec=y))
Peter Hinch
Index to my micropython libraries.

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Decimal-fraction precision question

Post by jimmo » Tue Jul 06, 2021 7:29 am

pythoncoder wrote:
Tue Jul 06, 2021 5:57 am
One way to write programmatic format specifiers:

Code: Select all

x = 5
y = 3
print('{val:{wid}.{dec}f}'.format(val=0.3, wid=x, dec=y))
🤯 TIL!

User avatar
scruss
Posts: 360
Joined: Sat Aug 12, 2017 2:27 pm
Location: Toronto, Canada
Contact:

Re: Decimal-fraction precision question

Post by scruss » Wed Nov 03, 2021 11:39 am

The OP hasn't checked in since July, but this might have been useful for them: DecimalNumber: floating point arithmetic with arbitrary precision for micropython. It would still require some work to keep track of the trailing zero precision convention, though.

Post Reply