I am exploring App Inventor, an Android application development environment for novice programmers. I am teaching it to my kids, as an introductory step towards “real” app development. While playing with it I wondered: are its numbers implemented in decimal? No, they aren’t. They are implemented in double-precision binary floating-point. I put together a few simple block programs to demonstrate this, and to expose the usual floating-point “gotchas”.
(Also check out this recent discovery about integers: entered in blocks they are represented in floating-point; generated through calculations they are represented as arbitrary-precision integers.)
App Template
Each testcase I wrote is embedded in a simple app “template”, consisting of a button and a label (I used the default names, Button1 and Label1). The button is used to run the testcase, and the label is used to display its output. I ran each test on both the emulator and a real device (a Samsung Galaxy Tab 3), although I only show screenshots of the emulator in the examples below.
The main tool I used is a math block called “format as decimal”:
The ‘places’ parameter specifies how many digits to print after the decimal point. I discovered it can print a number to any number of decimal places; this was a surprise, given that many industrial strength programming language implementations can’t do that! This allowed me to show the floating-point implementation in all its glory.
Printing Enough Digits To Expose Floating-Point Implementation
The following program (the same one shown in the introduction) prints the number 0.1 to 17 digits:
Here is its output:
You can see the “extra 1 on the end”, the tell-tale sign of a binary floating-point representation; let’s verify by printing all its digits:
Those blocks in fact display the exact representation of 0.1 in double-precision floating-point:
Numbers Are Converted Before They’re Displayed
There’s a simpler way to show that App Inventor uses floating-point, or at least get a clue that that’s what it’s using. If you enter a number of 16 to 17 digits or more, it will be changed as soon as you hit enter. It is converted instantly, like in a spreadsheet cell.
In this example, I entered 0.12345678901234567, even though App Inventor displays it as 0.12345678901234566 in the number block:
I wrapped the number in a “format as decimal” so that I could show all its floating-point digits:
That number, 0.1234567890123456634920984242853592149913311004638671875, is in fact the double-precision floating-point number closest to 0.12345678901234567. App Inventor displays it as 0.12345678901234566 because that is the full floating-point number rounded to 17 digits. So even though App Inventor changed the external representation from 0.12345678901234567 to 0.12345678901234566, it doesn’t matter; both convert to the same floating-point number internally.
Floating-Point Gotchas
The three examples that follow show mathematical results that surprise the uninitiated; I’ve written about them in other contexts.
Equality is Broken
Simple decimal addition fails to work in floating-point; for example, 0.3 + 0.6 ≠ 0.9. Here are blocks to demonstrate that:
(Notice the ‘\n’ formatting — a shout out to C!)
This exposes two issues:
- 0.3 and 0.6 converted to floating-point, and then added, does not give 0.9.
- 0.9 converted to floating-point does not even give 0.9!
The Floor Function Is Broken
4.35 * 100 is 435, so floor(4.35 * 100) is 435, right? Not in floating-point. 4.35 * 100 is slightly less than 435, so the floor is 434.
Let’s look at all the digits to verify:
Algebra Is Broken
I wrote this C function to show that floating-point does not honor algebraic manipulations:
double f(double a) { double b, c; b = 10*a - 10; c = a - 0.1*b; return (c); }
Algebraically, it should always return 1: c = a – 0.1*(10*a – 10) = a – (a-1) = 1.
I rewrote that function in blocks, and I tested it on input 719525522284533115.3 (which App Inventor converted instantly to 719525522284533100, a clue that this is not going to go as expected):
The answer is not 1, but -128!
Should App Inventor Hide Binary?
App Inventor hides much of the complexity of Android app development, allowing non-programmers to easily write simple apps. Yet it doesn’t hide binary floating-point, something that trips up countless people, beginners and non-beginners alike. Should it? Arbitrary-precision decimal floating-point would fix the most glaring gotchas, while rational arithmetic would handle expressions involving fractions like 1/3. (Rational arithmetic might be overkill though, since anyone that has used a calculator would expect inexact results using decimal fractions.)
Maybe it’s best to hide binary until new programmers get more experience — and start programming real apps in Java.
I’ve got a unit conversion program that was giving me weird answers for 0C to F and R. and sometimes for other problems. I jury-rigged a work-around but I know all I did was hid the problem.
So THAT’S what’s going on. What can we do about it? Can I just show the results of calculation (since it’s big intigers) as decimals and just eliminate the whole thing?
@Frank,
I would have to know more about your calculation to attempt to answer.
Well, for each unit, there’s two conversion fractions (two denominators and two numerators each) and for the temperatures there’s also subtraction and addition. Some of the parts of the fractions are in decimal form.
Tomorrow, I’ll give you a specific example.
So here’s an example, m to ft.
m*(100/1)*(1/2.54)*(1/12)=ft.
As you can see, part of this is in decimal form. I can think of three solutions.
1. Use text blocks rather than number blocks. Math functions could simply reject them.
2. Convert to decimal at specific points, but that would still leave decimals in text blocks to be automatically converted to bicimals.
3. turn 1/2.54 to 100/254, but there would be a LOT of code to get through to fully implement. So, of course, this is the most likely to work.
@Frank,
I think you have to go with something along the lines of solution #3.; that is, use integers. Floating-point literals in text blocks are still converted to IEEE (i.e., limited-precision) floating-point.
Drat. O.K., thank you.
Just in case you’re ever interested in my project, you can find it here:
https://play.google.com/store/apps/details?id=appinventor.ai_RoyceGrey.Frank_Harr_s_Conversion_App&hl=en
I know that this isn’t what this site is about, but it’s too cool to not share.
I made Android give me a complex number as an answer. I’d done it before, but this time was cooler than the others.
In my app’s probability conversion units I have logarithmic units of probability and in the upcoming version, I have /π as a unit (that is, per π as % is per hundred) as a unit. And I was playing around, converting various things from /π to various logarithmic units and I discovered that anything greater than π/π is not only ridiculous (as that would be greater than 1), but also crates a complex number when converted to one of the logarithmic units of probability. I ALSO discovered that when converted to nats (which is a logit based on e) it the complex number has the form x+ πi.
Which got me excited.
So I set finding out what probability gets you 1+ πi. It turns out that 4.96992639477188 /π probability (or just 1.58197671 which I know is more than certainty) equals 1+ πi nats. Which I think is REALLY cool.