1

This question is only for Python programmers. This question is not duplicate not working Increment a python floating point value by the smallest possible amount see explanation bottom.


I want to add/subtract for any float some smallest values which will change this float value about one bit of mantissa/significant part. How to calculate such small number efficiently in pure Python.

For example I have such array of x:

xs = [1e300, 1e0, 1e-300]

What will be function for it to generate the smallest value? All assertion should be valid.

for x in xs:
  assert x < x + smallestChange(x)
  assert x > x - smallestChange(x)

Consider that 1e308 + 1 == 1e308 since 1 does means 0 for mantissa so `smallestChange' should be dynamic.

Pure Python solution will be the best.


Why this is not duplicate of Increment a python floating point value by the smallest possible amount - two simple tests prove it with invalid results.

(1) The question is not aswered in Increment a python floating point value by the smallest possible amount difference:

Increment a python floating point value by the smallest possible amount just not works try this code:

import math
epsilon  = math.ldexp(1.0, -53) # smallest double that 0.5+epsilon != 0.5
maxDouble = float(2**1024 - 2**971)  # From the IEEE 754 standard
minDouble  = math.ldexp(1.0, -1022) # min positive normalized double
smallEpsilon  = math.ldexp(1.0, -1074) # smallest increment for doubles < minFloat
infinity = math.ldexp(1.0, 1023) * 2

def nextafter(x,y):    
    """returns the next IEEE double after x in the direction of y if possible"""
    if y==x:
       return y         #if x==y, no increment

    # handle NaN
    if x!=x or y!=y:
        return x + y       

    if x >= infinity:
        return infinity

    if x <= -infinity:
        return -infinity

    if -minDouble < x < minDouble:
        if y > x:
            return x + smallEpsilon
        else:
            return x - smallEpsilon  

    m, e = math.frexp(x)        
    if y > x:
        m += epsilon
    else:
        m -= epsilon

    return math.ldexp(m,e)  
print nextafter(0.0, -1.0), 'nextafter(0.0, -1.0)'
print nextafter(-1.0, 0.0), 'nextafter(-1.0, 0.0)'

Results of Increment a python floating point value by the smallest possible amount is invalid:

>>> nextafter(0.0, -1)
0.0

Should be nonzero.

>>> nextafter(-1,0)
-0.9999999999999998

Should be '-0.9999999999999999'.

(2) It was not asked how to add/substract the smallest value but was asked how to add/substract value in specific direction - propose solution is need to know x and y. Here is required to know only x.

(3) Propose solution in Increment a python floating point value by the smallest possible amount will not work on border conditions.

Community
  • 1
  • 1
Chameleon
  • 9,722
  • 16
  • 65
  • 127
  • If you care about this, you're probably working in an area where numpy would be very helpful. Does `numpy.nextafter` work for you? You can use `numpy.nextafter(x, numpy.inf)` to always go towards positive. – user2357112 Nov 27 '13 at 23:18
  • I not want use numpy to reduce memory footprint but maybe need study numpy code to extract small function. I prefer use simple python modules. – Chameleon Nov 27 '13 at 23:21
  • Reduce memory footprint? NumPy only takes about 8 MB to import on my machine. What kind of program are you writing? – user2357112 Nov 27 '13 at 23:24
  • Sure for simple PC it is not matter I will use GAE some additional +8Mb will cost and load time will be longer - it is different scale you can not know this :) If I will be use PC I will not care as you ... – Chameleon Nov 27 '13 at 23:27

2 Answers2

2
>>> (1.0).hex()
'0x1.0000000000000p+0'
>>> float.fromhex('0x0.0000000000001p+0')
2.220446049250313e-16
>>> 1.0 + float.fromhex('0x0.0000000000001p+0')
1.0000000000000002
>>> (1.0 + float.fromhex('0x0.0000000000001p+0')).hex()
'0x1.0000000000001p+0'

Just use the same sign and exponent.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • You approach is good. I found something much faster with use `sys` and `math` without string conversions using `frexp` and `ldexp` and `sys.float_info.mant_dig`. – Chameleon Nov 28 '13 at 00:40
  • I do some test but not finished code and result from timeit is `hex about 85.5520534034` and `frexp 19.9034547687` before optimization and tests. – Chameleon Nov 28 '13 at 12:44
0

Mark Dickinson's answer to a duplicate fares much better, but still fails to give the correct results for the parameters (0, 1).

This is probably a good starting point for a pure Python solution. However, getting this exactly right in all cases is not easy, as there are many corner cases. So you should have a really good unit test suite to cover all corner cases.

Whenever possible, you should consider using one of the solutions that are based on the well-tested C runtime function instead (i.e. via ctypes or numpy).

You mentioned somewhere that you are concerned about the memory overhead of numpy. However, the effect of this one function on your working set shout be very small, certainly not several Megabytes (that might be virtual memory or private bytes.)

Community
  • 1
  • 1
oefe
  • 19,298
  • 7
  • 47
  • 66
  • First of all `1e16` too small for `1e308` it will be not visible. Why mantissa is `53 bits`, exponent is `10 bits` so it is `1024 in base2` so the smallest value is `0x1p971`. Check this `(float.fromhex('0x1.1ccf385ebc8a0p+1023') - float.fromhex('0x1.1ccf385ebc89fp+1023')).hex()` in decimal notation `1.99584030953472e+292`. The smallest mantissa change depends can be 53 bits `2.220446049250313e-16` (if first bit set to 1). – Chameleon Dec 01 '13 at 22:12
  • Simple example of bug: `-0x1.ffffffffffffep-1 -0x1.0000000000000p+0 nextafter(-1.0, 0.0)`. Why compare `float.fromhex('-0x1.ffffffffffffep-1') float.fromhex('-0x1.fffffffffffffp-1') float.fromhex('-0x1.0000000000000p+0')` - value between `float.fromhex('-0x1.ffffffffffffep-1')` and `float.fromhex('-0x1.0000000000000p+0')` is `float.fromhex('-0x1.fffffffffffffp-1')`. – Chameleon Dec 01 '13 at 22:55
  • So for which input do you get wrong results? Show the actual result *and* the expected result. – oefe Dec 02 '13 at 22:13
  • for example first one `-0x1.ffffffffffffep-1 -0x1.0000000000000p+0 nextafter(-1.0, 0.0)` - should be `-0x1.fffffffffffffp-1`. – Chameleon Dec 03 '13 at 00:48
  • True, and the cause is easy to see. Have you tried this http://stackoverflow.com/a/10426033/49793 This still doesn't handle 0 correctly. – oefe Dec 03 '13 at 21:05
  • Yes I read this but no recommend to use it. Struct pack is in my option too low level approach - it is false assumption that float has some fixed format - maybe I am wrong. I do own code with use `hex` and other with `ldexp` and `frexp`. It is much simpler and `hex` is working - no testedt `ldexp` and `frexp`. I uses also high level `sys` to understand float format. – Chameleon Dec 04 '13 at 11:09