Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

float -> int unusuallness at 1.0 #230

Closed
ladyada opened this issue Sep 5, 2017 · 14 comments
Closed

float -> int unusuallness at 1.0 #230

ladyada opened this issue Sep 5, 2017 · 14 comments
Assignees
Milestone

Comments

@ladyada
Copy link
Member

ladyada commented Sep 5, 2017

>>> uos.uname()
(sysname='samd21', nodename='samd21', release='1.0.0', version='1.0.0 on 2017-07-19', machine='Adafruit Feather M0 Express with samd21g18')
>>> 0.001 * 1000
1.0
>>> int(1.0)
1
>>> int(0.001 * 1000)
0

but on desktop...

Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:01:18) [MSC v.1900 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> 0.001 * 1000
1.0
>>> int(0.001 * 1000)
1
>>> 

i know that floats are imprecise, but this seems unusual...

@tannewt tannewt added this to the 3.0 milestone Sep 5, 2017
@dhalbert
Copy link
Collaborator

dhalbert commented Sep 5, 2017

CircuitPython currently does all arithmetic in single-precision, whereas CPython uses double. So I think you're seeing an artifact of that. [EDIT: no, it's something else] 0.1 * 10 is not quite 1.0, even though it prints as such. (Same for 0.01 *100, 0.001 * 1000.)

int() truncates. Did you want round()?

@ladyada
Copy link
Member Author

ladyada commented Sep 5, 2017

yah im going to use round() but i thought it odd that it prints 1.0 even tho it is not 1.0 :)

@dhalbert
Copy link
Collaborator

dhalbert commented Sep 5, 2017

I did some more experiments. It does appear that there's something weird with MicroPython floating-point arithmetic or conversion from decimal. In MicroPython/CircuitPython:

>>> 1.0 -(0.1*10)
2.384186e-07
>>> 1.0 -(0.001*1000)
4.768372e-07

In CPython, the difference is 0.0.

I can't duplicate this in C with single-precision:

#include <stdio.h>
int main() {
    float f;
    int i;
    printf("enter f and i: ");
    scanf("%f %d", &f, &i);
    float f_times_i = f*i;
    printf("f*i: %f\(int)(f*i): %d\nf*i - (int)(f*i): %f\n", f_times_i, (int) f_times_i, 1.0f - f_times_i);
}

I have to try hard not to let gcc do fancy arithmetic, and still can't get it to fail:

$ gcc -ffloat-store -ffast-math -O0 -fexcess-precision=standard float.c
$ ./a.out
enter f and i: 0.1 10
f*i: 1.000000(int)(f*i): 1
f*i - (int)f*i: 0.000000

@jerryneedell
Copy link
Collaborator

jerryneedell commented Sep 5, 2017

Interesting that the difference is a factor of 2. Makes me suspicious of the handling of the exponent in the floating point.

 1.0 -(0.1*10)
 2.384186e-07

1.0 -(0.001*1000)
4.768372e-07

@dhalbert
Copy link
Collaborator

dhalbert commented Sep 5, 2017

Tried Arduino float arithmetic on 32u4 and M0. 1.0 - (0.1*10) is zero, not some tiny number. I'll see if we should raise an issue with MicroPython. Need to look at actual binary representations of these numbers.

@willingc
Copy link
Collaborator

willingc commented Sep 5, 2017

I'll take a look at the code for MP/CP tomorrow. Looks like a bug to me.

@jerryneedell
Copy link
Collaborator

jerryneedell commented Sep 5, 2017

Hmm.
2.384186e-07 is 1/(0x400000)
big round numbers bother me ;-)
float has a 23 bit mantissa (coincidence??).

@willingc
Copy link
Collaborator

willingc commented Sep 5, 2017

@ladyada Do you see the same behavior on master? It looks as if this change upstream may be related.

@dhalbert
Copy link
Collaborator

dhalbert commented Sep 5, 2017

@willingc The examples I gave above were run on master (2.0.0 past beta1).

@willingc
Copy link
Collaborator

willingc commented Sep 5, 2017

Thanks @dhalbert. If possible, would you mind running exactly the same example? Or did you run the exact same code, receive the same output, and not post it?

@dhalbert
Copy link
Collaborator

dhalbert commented Sep 6, 2017

@willingc This is the absolute latest master, with the v1.9.2 merge.

Adafruit CircuitPython 2.0.0-rc.1-2-g6d9d68344 on 2017-09-05; Adafruit CircuitPlayground Express with samd21g18
>>> 0.001 * 1000
1.0
>>> int(1.0)
1
>>> int(0.001 * 1000)
0
>>> 1.0 -(0.1*10)
2.38419e-07
>>> 1.0 -(0.001*1000)
4.76837e-07
>>> 

@dhalbert
Copy link
Collaborator

dhalbert commented Sep 6, 2017

The bits used to represent 0.1 between CPy and regular Python vary in the least significant bit:

CPy master, on atmel-samd and esp8266:

>>> import ustruct
>>> ustruct.calcsize("f")
4
>>> ustruct.pack("f",0.1)
b'\xcc\xcc\xcc='

Python 3.5.2:

>>> import struct
>>> struct.calcsize("f")
4
>>> struct.pack("f",0.1)
b'\xcd\xcc\xcc='

CircuitPython unix port is also b'\xcd\xcc\xcc='.

Notice 0xcc vs. 0xcdin byte [0], the least significant byte (little-endian order). This webpage is helpful:
https://www.h-schmidt.net/FloatConverter/IEEE754.html

image

If you press the "-1" button on the webpage form above, you get the b'\xcc\xcc\xcc=' value.

@dhalbert
Copy link
Collaborator

dhalbert commented Sep 7, 2017

The answer to what is going on here is that MicroPython stores single-precision floats in 30 bits, not 32. The lowest two bits are used to flag the bits as a float value. When a 32-bit float is converted to 30 bits, it is truncated, not rounded. The 32-bit 0.1 has a 1 in the least significant bit and this goes to a 0 when truncated (and it would round down anyway). I tried extending the print precision to more decimal digits, but it still prints as "0.1", because that's the closest representation after rounding; "0.099999" is further from the actual value than "0.1" is.

MicroPython only prints six digits of precision. Trying to print more digits gives peculiar results, like 1/3 printing as "0.333333325" . MicroPython even adjusted the number of digits of precision it printed to avoid oddities like this.

We could have 32-bit to 30-bit conversion do rounding, but that might introduce other numerical issues, and might make porting code between MicroPython and CircuitPython weirder. And it wouldn't actually fix the 0.1 problem here, because it would round down.

@ladyada
Copy link
Member Author

ladyada commented Sep 8, 2017

oooh ok. got it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants