representation of valid float type range
![](https://secure.gravatar.com/avatar/5409787c7ad6e314a83ff33575345179.jpg?s=120&d=mm&r=g)
I am getting an interesting result, and I'm wondering if anyone would care to give me some intuition of why. The example is simple enough, I want to get a range of values that are representable by a type: ```python f64_info = np.finfo(np.float64) valid_range = np.linspace( start=f64_info.min, stop=f64_info.max, num=10 ) valid_range => array([ nan, inf, inf, inf, inf, inf, inf, inf, inf, 1.79769313e+308]) ``` The minimum value is representable by the type, I can see it: ```python f64_info.min => -1.7976931348623157e+308 ``` I thought that maybe the valid range cannot start with the minimun value, so I've tried a few alternatives: ```python valid_range = np.linspace( start=f64_info.min + f64_info.eps, stop=f64_info.max, num=10 ) valid_range => array([ nan, inf, inf, inf, inf, inf, inf, inf, inf, 1.79769313e+308]) valid_range = np.linspace( start=f64_info.min + f64_info.tiny, stop=f64_info.max, num=10 ) valid_range => array([ nan, inf, inf, inf, inf, inf, inf, inf, inf, 1.79769313e+308]) ``` I thought maybe the range is too wide, but I can do this: ```python valid_range = np.linspace( start=0, stop=f64_info.max, num=10 ) valid_range => array([0.00000000e+000, 1.99743682e+307, 3.99487363e+307, 5.99231045e+307, 7.98974727e+307, 9.98718408e+307, 1.19846209e+308, 1.39820577e+308, 1.59794945e+308, 1.79769313e+308]) ... valid_range = np.linspace( start=f64_info.tiny, stop=f64_info.max, num=10 ) valid_range => array([2.22507386e-308, 1.99743682e+307, 3.99487363e+307, 5.99231045e+307, 7.98974727e+307, 9.98718408e+307, 1.19846209e+308, 1.39820577e+308, 1.59794945e+308, 1.79769313e+308]) ... f32_info = np.finfo(np.float32) valid_range = np.linspace( start=f32_info.tiny, stop=f32_info.max, num=10, dtype=np.float32, ) valid_range => array([1.1754944e-38, 3.7809150e+37, 7.5618299e+37, 1.1342745e+38, 1.5123660e+38, 1.8904575e+38, 2.2685490e+38, 2.6466405e+38, 3.0247320e+38, 3.4028235e+38], dtype=float32) ``` I know that linear space is arbitrary, and perhaps not that useful. In fact this is valid: ```python valid_range = np.logspace( start=f64_info.minexp, stop=f64_info.maxexp, num=10, base=2, endpoint=False ) valid_range => array([2.22507386e-308, 8.67124674e-247, 3.37923704e-185, 1.31690901e-123, 5.13207368e-062, 2.00000000e+000, 7.79412037e+061, 3.03741562e+123, 1.18369915e+185, 4.61294681e+246]) ``` But I'm still confused on why linear space is invalid Thanks!
![](https://secure.gravatar.com/avatar/0b2c27afda735efd834586f95f07f838.jpg?s=120&d=mm&r=g)
• Short answer: It's because
f64_info.max - f64_info.min inf
• Long answer: linspace(a,b,n) tries to calculate the step by (b-a)/n and fails at (b-a). You need to either – split your range into two parts and then glue them back: np.r_[np.linspace(f64_info.min, 0, 5), np.linspace(0, f64_info.max, 5)[1:]] – or select a range that fits into float64: np.linspace(f64_info.min/2, f64_info.max/2, 10) – or select np.float128 as a dtype for linspace (linux/macos only): np.linspace(np.float128(f64_info.min), np.float128(f64_info.max), 10) Best regards, Lev On Wed, Dec 29, 2021 at 8:01 PM Sebastian Gurovich <sebas0@gmail.com> wrote:
![](https://secure.gravatar.com/avatar/5409787c7ad6e314a83ff33575345179.jpg?s=120&d=mm&r=g)
Thanks for your answer. I think i understand it - is it that `f64_info.max - f64_info.min` does not fit in float64? because it approximates `2 * f64_info.max`? In that case, I agree with Klaus, linspace should be able to handle this?
![](https://secure.gravatar.com/avatar/764323a14e554c97ab74177e0bce51d4.jpg?s=120&d=mm&r=g)
On Thu, Jan 6, 2022 at 4:43 PM <alejandro.giacometti@gmail.com> wrote:
Well, it "fits" into a float64 by becoming `inf`, which is a valid float64 value, just one that has particular consequences. In that case, I agree with Klaus, linspace should be able to handle this?
I don't particularly agree that linspace() ought to add special-case logic to handle this. It would be difficult to write logic that reliably recognizes all the ways that something like this is actually the case and then does something sensible to recover from it. Lev showed how you can manually do something sorta reasonable for `np.linspace(f64_info.min, f64_info.max, num)` because the endpoints are symmetric around 0, but there is nothing that can really be done for the asymmetric case. Floating point arithmetic has its limits, and playing with values near the boundaries is going to make you run into those limits. I would rather have linspace() do something consistent that gives non-useful results in these boundary edge cases than try to do a bunch of different logic in these outer limits. The utility of getting "sensible" results for the extreme results is fairly limited (not least because any downstream computation will also be very likely to generate NaNs and infs), so I would happily trade that to have a more consistent mental model about what linspace() does. -- Robert Kern
![](https://secure.gravatar.com/avatar/5409787c7ad6e314a83ff33575345179.jpg?s=120&d=mm&r=g)
I see what you mean, there is, however, some inconsistency on how this is handled, and it's not entirely intuitive ``` _type=np.int8 N=8 np.linspace( start=np.iinfo(_type).min, stop=np.iinfo(_type).max, num=N, dtype=_type, ) => array([-128, -92, -56, -19, 17, 54, 90, 127], dtype=int8) _type=np.float16 np.linspace( start=np.finfo(_type).min, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([-65504., -46784., -28080., -9360., 9360., 28080., 46784., 65504.], dtype=float16) _type=np.float32 np.linspace( start=np.finfo(_type).min, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([-3.4028235e+38, -2.4305882e+38, -1.4583529e+38, -4.8611764e+37, 4.8611764e+37, 1.4583529e+38, 2.4305882e+38, 3.4028235e+38], dtype=float32) _type=np.float64 np.linspace( start=np.finfo(_type).min, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([ nan, inf, inf, inf, inf, inf, inf, 1.79769313e+308]) _type=np.float64 np.linspace( start=0, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([0.00000000e+000, 2.56813305e+307, 5.13626610e+307, 7.70439915e+307, 1.02725322e+308, 1.28406652e+308, 1.54087983e+308, 1.79769313e+308]) _type=np.float64 np.linspace( start=-1e291, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([-1.00000000e+291, 2.56813305e+307, 5.13626610e+307, 7.70439915e+307, 1.02725322e+308, 1.28406652e+308, 1.54087983e+308, 1.79769313e+308]) _type=np.float64 np.linspace( start=-1e292, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([ nan, inf, inf, inf, inf, inf, inf, 1.79769313e+308]) _type=np.float16 np.logspace( start=np.finfo(_type).minexp, stop=np.finfo(_type).maxexp, num=N, dtype=_type, base=2, ) => array([6.104e-05, 1.190e-03, 2.322e-02, 4.529e-01, 8.836e+00, 1.722e+02, 3.360e+03, inf], dtype=float16) _type=np.float16 np.logspace( start=np.finfo(_type).minexp, stop=np.finfo(_type).maxexp, num=N, dtype=_type, base=2, endpoint=False, ) => array([6.104e-05, 8.211e-04, 1.105e-02, 1.487e-01, 2.000e+00, 2.691e+01, 3.620e+02, 4.872e+03], dtype=float16) _type=np.float64 np.logspace( start=np.finfo(_type).minexp, stop=np.finfo(_type).maxexp, num=N, dtype=_type, base=2, endpoint=False, ) => array([2.22507386e-308, 2.16653556e-231, 2.10953732e-154, 2.05403862e-077, 2.00000000e+000, 1.94738306e+077, 1.89615038e+154, 1.84626556e+231]) ```
![](https://secure.gravatar.com/avatar/1198e2d145718c841565712312e04227.jpg?s=120&d=mm&r=g)
Hello all. I believe that over the years there were multiple proposals to replace the linspace formula start + n *(stop - start) / (npoints - 1) with a * start + b * end with a, b linearly spaced between 0 and 1 with npoints. Concretely, a = n / (npoints - 1), b = 1 - a. Here, 0 <= n < npoints. I believe this would fix the issue here among many others. However, it was always rejected due to backward-incompatibility concerns. Perhaps it’s time to revisit this issue. Best regards, Hameer Abbasi
![](https://secure.gravatar.com/avatar/d9aabd177a8ede98fdc1e23f477f8a4f.jpg?s=120&d=mm&r=g)
Hello, perhaps it would be best to have an issue about this on github? It might be worth pointing out that the original problem triggers a floating point error that can be caught and handled via errstate. This might be used either in linspace itself, or, if you think this rare problem is likely to affect you, you might wrap your call to linspace, for example: ``` i = finfo(float64) a = array([i.max, i.min, 0.], dtype=float64) with errstate(over="raise"): a[2] = a[1] - a[0] ``` or ``` with errstate(over="raise"): linspace(i.min, i.max) ``` Cheers Klaus On 1/10/22 13:10, Hameer Abbasi wrote:
![](https://secure.gravatar.com/avatar/79653c343e58afbfbedcfd11e7b668d7.jpg?s=120&d=mm&r=g)
On Mon, Jan 10, 2022 at 7:15 AM Hameer Abbasi <einstein.edison@gmail.com> wrote:
Is there an actual use case for the version of this from the original email? I’m not sure how a linspace with range covering 10^600 or so is something that has an actual use. Just because this fails doesn’t mean that it actually needs to be made to work. Eric
![](https://secure.gravatar.com/avatar/0b2c27afda735efd834586f95f07f838.jpg?s=120&d=mm&r=g)
• Short answer: It's because
f64_info.max - f64_info.min inf
• Long answer: linspace(a,b,n) tries to calculate the step by (b-a)/n and fails at (b-a). You need to either – split your range into two parts and then glue them back: np.r_[np.linspace(f64_info.min, 0, 5), np.linspace(0, f64_info.max, 5)[1:]] – or select a range that fits into float64: np.linspace(f64_info.min/2, f64_info.max/2, 10) – or select np.float128 as a dtype for linspace (linux/macos only): np.linspace(np.float128(f64_info.min), np.float128(f64_info.max), 10) Best regards, Lev On Wed, Dec 29, 2021 at 8:01 PM Sebastian Gurovich <sebas0@gmail.com> wrote:
![](https://secure.gravatar.com/avatar/5409787c7ad6e314a83ff33575345179.jpg?s=120&d=mm&r=g)
Thanks for your answer. I think i understand it - is it that `f64_info.max - f64_info.min` does not fit in float64? because it approximates `2 * f64_info.max`? In that case, I agree with Klaus, linspace should be able to handle this?
![](https://secure.gravatar.com/avatar/764323a14e554c97ab74177e0bce51d4.jpg?s=120&d=mm&r=g)
On Thu, Jan 6, 2022 at 4:43 PM <alejandro.giacometti@gmail.com> wrote:
Well, it "fits" into a float64 by becoming `inf`, which is a valid float64 value, just one that has particular consequences. In that case, I agree with Klaus, linspace should be able to handle this?
I don't particularly agree that linspace() ought to add special-case logic to handle this. It would be difficult to write logic that reliably recognizes all the ways that something like this is actually the case and then does something sensible to recover from it. Lev showed how you can manually do something sorta reasonable for `np.linspace(f64_info.min, f64_info.max, num)` because the endpoints are symmetric around 0, but there is nothing that can really be done for the asymmetric case. Floating point arithmetic has its limits, and playing with values near the boundaries is going to make you run into those limits. I would rather have linspace() do something consistent that gives non-useful results in these boundary edge cases than try to do a bunch of different logic in these outer limits. The utility of getting "sensible" results for the extreme results is fairly limited (not least because any downstream computation will also be very likely to generate NaNs and infs), so I would happily trade that to have a more consistent mental model about what linspace() does. -- Robert Kern
![](https://secure.gravatar.com/avatar/5409787c7ad6e314a83ff33575345179.jpg?s=120&d=mm&r=g)
I see what you mean, there is, however, some inconsistency on how this is handled, and it's not entirely intuitive ``` _type=np.int8 N=8 np.linspace( start=np.iinfo(_type).min, stop=np.iinfo(_type).max, num=N, dtype=_type, ) => array([-128, -92, -56, -19, 17, 54, 90, 127], dtype=int8) _type=np.float16 np.linspace( start=np.finfo(_type).min, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([-65504., -46784., -28080., -9360., 9360., 28080., 46784., 65504.], dtype=float16) _type=np.float32 np.linspace( start=np.finfo(_type).min, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([-3.4028235e+38, -2.4305882e+38, -1.4583529e+38, -4.8611764e+37, 4.8611764e+37, 1.4583529e+38, 2.4305882e+38, 3.4028235e+38], dtype=float32) _type=np.float64 np.linspace( start=np.finfo(_type).min, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([ nan, inf, inf, inf, inf, inf, inf, 1.79769313e+308]) _type=np.float64 np.linspace( start=0, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([0.00000000e+000, 2.56813305e+307, 5.13626610e+307, 7.70439915e+307, 1.02725322e+308, 1.28406652e+308, 1.54087983e+308, 1.79769313e+308]) _type=np.float64 np.linspace( start=-1e291, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([-1.00000000e+291, 2.56813305e+307, 5.13626610e+307, 7.70439915e+307, 1.02725322e+308, 1.28406652e+308, 1.54087983e+308, 1.79769313e+308]) _type=np.float64 np.linspace( start=-1e292, stop=np.finfo(_type).max, num=N, dtype=_type, ) => array([ nan, inf, inf, inf, inf, inf, inf, 1.79769313e+308]) _type=np.float16 np.logspace( start=np.finfo(_type).minexp, stop=np.finfo(_type).maxexp, num=N, dtype=_type, base=2, ) => array([6.104e-05, 1.190e-03, 2.322e-02, 4.529e-01, 8.836e+00, 1.722e+02, 3.360e+03, inf], dtype=float16) _type=np.float16 np.logspace( start=np.finfo(_type).minexp, stop=np.finfo(_type).maxexp, num=N, dtype=_type, base=2, endpoint=False, ) => array([6.104e-05, 8.211e-04, 1.105e-02, 1.487e-01, 2.000e+00, 2.691e+01, 3.620e+02, 4.872e+03], dtype=float16) _type=np.float64 np.logspace( start=np.finfo(_type).minexp, stop=np.finfo(_type).maxexp, num=N, dtype=_type, base=2, endpoint=False, ) => array([2.22507386e-308, 2.16653556e-231, 2.10953732e-154, 2.05403862e-077, 2.00000000e+000, 1.94738306e+077, 1.89615038e+154, 1.84626556e+231]) ```
![](https://secure.gravatar.com/avatar/1198e2d145718c841565712312e04227.jpg?s=120&d=mm&r=g)
Hello all. I believe that over the years there were multiple proposals to replace the linspace formula start + n *(stop - start) / (npoints - 1) with a * start + b * end with a, b linearly spaced between 0 and 1 with npoints. Concretely, a = n / (npoints - 1), b = 1 - a. Here, 0 <= n < npoints. I believe this would fix the issue here among many others. However, it was always rejected due to backward-incompatibility concerns. Perhaps it’s time to revisit this issue. Best regards, Hameer Abbasi
![](https://secure.gravatar.com/avatar/d9aabd177a8ede98fdc1e23f477f8a4f.jpg?s=120&d=mm&r=g)
Hello, perhaps it would be best to have an issue about this on github? It might be worth pointing out that the original problem triggers a floating point error that can be caught and handled via errstate. This might be used either in linspace itself, or, if you think this rare problem is likely to affect you, you might wrap your call to linspace, for example: ``` i = finfo(float64) a = array([i.max, i.min, 0.], dtype=float64) with errstate(over="raise"): a[2] = a[1] - a[0] ``` or ``` with errstate(over="raise"): linspace(i.min, i.max) ``` Cheers Klaus On 1/10/22 13:10, Hameer Abbasi wrote:
![](https://secure.gravatar.com/avatar/79653c343e58afbfbedcfd11e7b668d7.jpg?s=120&d=mm&r=g)
On Mon, Jan 10, 2022 at 7:15 AM Hameer Abbasi <einstein.edison@gmail.com> wrote:
Is there an actual use case for the version of this from the original email? I’m not sure how a linspace with range covering 10^600 or so is something that has an actual use. Just because this fails doesn’t mean that it actually needs to be made to work. Eric
participants (7)
-
alejandro.giacometti@gmail.com
-
Eric Moore
-
Hameer Abbasi
-
Klaus Zimmermann
-
Lev Maximov
-
Robert Kern
-
Sebastian Gurovich