Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
1.5 is the midpoint between 0 and infinity in Ruby (peterzhu.ca)
237 points by pimterry on Nov 27, 2020 | hide | past | favorite | 121 comments


Like every blog post or article that talks about something weird related to IEEE floats, this absolutely needs to find a way to link to "What Every Computer Scientist Should Know About Floating-Point Arithmetic", over on https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.h..., because that's mandatory reading if you're programming.


Shorter and at a level the simple masses of programmers will likely work at -

https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp...


Having the actual explanation of why floats work the way they do, by explaining how the various kinds of numbers map to the various IEEE defined bit patterns, is crucial in understanding, rather than just having read some text. This python article skips over that entirely, making it yet another tutorial about the fact "that" floats are approximations instead of "why" floats are approximations.

Spend the time up from to understand the why, especially if you're the simple masses. If you're programming, it pays to properly learn how something that is integral to programming works and what you can therefore expect when you use it. Which will be all the time.


When would knowing that they're an approximation ever be inadequate compared to knowing why they're an approximation in the real world for the simple masses? 99.99% of cases the decision goes "floats are fast and memory efficient, but I need full decimal precision, so I'll use the library"


Another short introduction to floating point number with pictures giving a better intuition of their distribution among real numbers:

https://blogs.mathworks.com/cleve/2014/07/07/floating-point-...


In surreal numbers [1], the midpoint between 0 and infinity would be the simplest number greater than 0, which is { 0 | } = 1

[1] https://en.wikipedia.org/wiki/Surreal_number


Some other senses in which 1 can be thought of as the "midpoint" between 0 and ∞ come from the many common ways to invertibly map an interval onto (0, ∞), in which the midpoint of that interval maps onto 1. For example:

• tan maps (0, π/2) onto (0, ∞), and tan(π/4) = 1.

• f(x) = x/(1 - x) maps (0, 1) onto (0, ∞), and f(1/2) = 1.

• exp is an isomorphism from the reals under addition to the positive reals under multiplication. 0 is the natural "midpoint" of the reals under addition, and exp(0) = 1.

(Closely related to that last one: when dealing only with positive numbers (and their limits 0 and ∞), it's natural to think of their group operation, multiplication, for which 1 is the identity, creating a symmetry between the numbers smaller than 1 and the numbers larger than 1 under reciprocation.)


I have a question

If this was true:

tan maps (0, π/2) onto (0, ∞), and tan(π/4) = 1

Wouldnt it imply that

tan(π/8) would be halfway between 0 and 1 ie .5?

By my calculations it is 0.414 or √2 - 1

ALso with:

• f(x) = x/(1 - x) maps (0, 1) onto (0, ∞), and f(1/2) = 1.

wouldnt this mean that f(0.25) is supposed to be half way between 0 and 1 or .5. However f(0.25) = 0.25/0.75 = 1/3


> tan(π/8) would be halfway between 0 and 1 ie .5?

It implies that tan(π/8) would be "halfway" between 0 and 1 in this sense of what "halfway" means. In this sense, 0.5 is not halfway between 0 and 1, 0.414 is.

It's easier to visualize it. You're standing on the roof of a 1-meter-tall building on a flat earth with a perfectly clear atmosphere. If you look straight out (90°) you can see to infinity (tan 90°). If you look down (0°) you can see where you are (tan 0°). If you look halfway between those (45°), you can see 1 (tan 45°) meter straight in front of you. But if you look halfway down again, you won't see exactly 0.5 meters, will you?


This does not imply that tan(pi/8) is 0.5. There’s some overloaded language here that I’d like to unpack:

The original issue at hand is to talk sensibly about “half way” between 0 and infinity. But in the standard way we think about distance between numbers, there’s obviously no way to do that — you can’t add and subtract real numbers to infinity!

So, implicitly what’s happening here is that by talking about a map between a finite interval and (0,inf), we are equipping (0,inf) with a new, special definition of distance between numbers. This is called a metric.

Usually, when we talk about the distance between x and y, we mean `d(x,y) = |x-y|` — this is called the Euclidean metric (in 1 dimension).

Here, we’ve introduced a new metric on (0,inf): `d2(x,y) = |tan^-1(x) - tan^-1(y)|/(pi/8)`

The half way point between 0 and 1 under the Euclidean metric is 0.5. The half way point between 0 and 1 under our fancy new metric d2 is ~0.414.

TL;DR: You can generalize the notion of “distance between two numbers”, using distance functions called metrics. Under the typical metric, the half way point between 0 and 1 is 0.5. Under our cool new infinite-tan metric, the half way point between 0 and 1 is ~0.414.


> tan(π/8) would be halfway between 0 and 1 ie .5?

tan(π/8) = √2 - 1, not 1/2, but the value is indeed halfway between 0 and 1 if you take the distance function to be

d(a, b) = |b - a| / (√(1 + a²)√(1 + b²)) or

d(a, b) = |b - a| / |1 + ab|

(These are chordal distance or stereographic distance, respectively, when the real number line is used to represent points on a circle under stereographic projection.)

The halfway point is 0.5 when you use the distance function d(a, b) = |b - a|.


It would only imply that if the tangent was a linear function of the angle, but it is not.


This is also consistent with the old joke that developers count like cavemen: zero, one, many!


Well, if you map the contents of the unit circle (|z| < 1) through 1/z you can reach any point |z| > 1. So there really is no need to give unique names to anything beyond 1 as it's already contained in the proverbial nutshell of the unit circle via 1/z.


You don't even need the unit circle. The unit interval is enough, as is any set whose cardinality is aleph-1.


Parent (who meant disc not circle) didn't give a dimension. An interval is just a 1 dimensional disc.


It's also the midpoint of the positive rationals, as modeled by the Stern-Brocot tree.

https://www.cut-the-knot.org/blue/Stern.shtml


Exactly what I came to post. This is an important model for numbers.

Traditionally one distinguishes between rational, algebraic, and transcendental numbers; e is transcendental. This tree is related to continued fraction expansions. Any number is a limit of a path in this tree, and one can talk about the pattern in the path from a CS automata theory point of view. Now e comes out one of the easier limits.


Perhaps I'm confused, but doesn't that site indicate that the midpoint of the positive rationals is 1.0? Or am I misreading it?


You are not misreading. That's correct.


But the article is saying that in Ruby, it's 1.5, right? So it's not the same as here? Or were you referring to the general notion of a midpoint of an infinite range?


Right again. The "also" is a reference to the parent comment.

> In surreal numbers [1], the midpoint between 0 and infinity would be the simplest number greater than 0, which is { 0 | } = 1


Oh, I see! For some reason I didn't spot the parent comment.


That's not right. 1 is different to half the sum of 0 and infinity. Another way to look at it is that infinity minus one is larger than one minus zero (which can be easily proven using the basic definitions used to define the surreals). Moreover, what is at the right side of {0|} is not infinity, but the empty set.


> Moreover, what is at the right side of {0|} is not infinity, but the empty set.

But { 0 | ω } = 1, right?


Correct. As is { 0 | n } for any n > 1.


TIL: apart from infinity (number, larger than any real number in absolute value) there are infinitesimal (number, less than any real number in absolute value and not a zero).

Edit: also, those infinitesimals were the subject of political and religious controversies in 17th century Europe, including a ban on infinitesimals issued by clerics in Rome in 1632.


Those clerics were ahead of their time. They probably would have banned large cardinals as well (infinites so large we can't prove whether or not they exist.)



Even with the shortened URLs, I just knew this was going to be some reference to the Catholic church’s cardinals


Not sure whether 'large cardinals' phrase in your message is intentional or accidental pun.


You win the internet, sir.


Infinitesimals and infinities are nice if you're doing some sort of bucketing logic.

    tiny = infinitesimal
    huge = infinity
    N = number to be bucketed

    tiny <= N < 10  --> bucket 1
    10 <= N < 20    --> bucket 2
    ...
    X <= N < huge   --> last bucket

This removes edge cases you need to test for if you're trying to bucket positive values. This may not be something you've had to do, but I've had reason to want this before on a few occasions.


Infinitesimals do not exist in the standard real number system. 'tiny' seems to be more related to the smallest positive representable (normal or subnormal) IEEE 754 float/double type of value which is a real number.


Infinitesimals tautologically exist in any finite representation of numbers. For floats, it's the smallest representable positive number. For integers of any type, it's 1.

As for how common they are, we learn about them in any introductory calculus course when defining derivatives. You come across the idea whenever discussing limits, if somewhat obliquely.

If I learned about it in high school math, and again in "real" math courses at my university, I'd say it's pretty standard.


I've never heard of that, and your definition of 1 as infinitesimal is incompatible withbits properties (infinitesimal + infinitesimal + infinitesimal is greater than a non-infinitesimal 2?!) and I don't see a mention on Wikipedia, and it goes against the plain read it of "in-finite-simal".

Also, you seem to be conflating "common" with "standard". "standard" is a mathematical term. Infinitesimal are handwavy in standard analysis (epsilon-delta are the rigorous alternative), but exist rigorously in nonstandard analysis.

https://en.m.wikipedia.org/wiki/Infinitesimal


I guess I'm using the wrong term, then. I often find it useful to have a concept of "smallest representable positive number," specifically for handling edge cases such as the one I gave up-thread. I see how that doesn't map to infinitesimal as defined in the shared link.

There are other instances where I've had a need for such a smallest positive number, where logic is simplified as opposed to checking for 0 in a special way. Whether there's an agreed upon term for that, I know where I've found value in programming tasks.

When I need such a thing, it is almost invariably in comparisons, so I am not doing arithmetic with multiple instances of that smallest representable positive number.


The theory of infinitesimals is intimately connected to how analysis (differential and integral calculus) was first formulated. Leibniz and Newton understood that you could approximate instant rates of change, or areas under a curve, by taking smaller and smaller "slices" of a function, but they did not yet have the rigorously formalized notion of limits that modern analysis depends on. So they developed an arithmetic of infinitesimals, numbers greater than zero but less than any positive real number¹, with some rather ad-hoc properties to make them work out in calculations.

Philosophical problems surrounding the perplexing concept of infinity were already hotly debated by the ancient Greeks. Aristotle made an ontological distinction between actual and potential infinities, and argued that actual infinity cannot exist but potential infinity can. This was also the consensus position of later scholars, and became a sticking point in the acceptance of calculus because infinitesimals (and infinite sums of them) were an example of the ontologically questionable actual infinities.

As I mentioned before, standard modern analysis is based on limits, not infinitesimals, and requires no extension of real numbers. Indeed the limit definition of calculus only requires the concept of potential infinities, so philosophers should be able to rest easy! But infinitesimals still occur in our notation which is largely inherited from Leibniz, however. We say that the derivative of y(x) is dy/dx, or the antiderivative of y(x) is ∫ y(x) dx, and while acknowledging that dy and dx are not actual mathematical objects, just syntax, we still do arithmetic on them whenever it's convenient to do so! For example, when we make a change of variables in an integral, we can substitute x = f(t) for some f, and then say dx/dt = f'(t) and "multiply by dt" to get dx = f'(t) dt to figure out what we should put in the place of the "dx" in the integral.

Actual infinitesimal numbers are not dead, either, they're used in a branch of analysis called nonstandard analysis which formalizes them in the logically rigorous manner that is now expected from mathematics.

________

¹ Not that they had a rigorous theory of real numbers, either, that came in the 19th and early 20th century. In fact what we now understand as formal, axiomatized math didn't really exist before the 19th century at all!


On a log scale 1 is also halfway between 0 and infinity.


On a log scale, all numbers are equally distant from 0 and infinity. 1 is no more “halfway” than 10, or 13, or 1/95.


Can you tell me the hackenbush interpretation of this? sounds cool!



Clearly we'll need to some work in TruffleRuby to be fully compatible. I really hope nobody's code depends on this.

  Infinity
  8.988465674311579e+307
  4.4942328371557893e+307
  2.2471164185778946e+307
  1.1235582092889473e+307
  5.6177910464447366e+306
  ...
  32767.999999999996
  16383.999999999998
  8191.999999999999
  4095.9999999999995
  2047.9999999999998
  1023.9999999999999
  511.99999999999994
  255.99999999999997
  127.99999999999999
  63.99999999999999
  31.999999999999996
  47.99999999999999
  39.99999999999999
  43.99999999999999
  41.99999999999999
  42.99999999999999
  42.49999999999999
  42.24999999999999
  42.12499999999999
  42.06249999999999
  42.03124999999999
  42.01562499999999
  42.00781249999999
  ...
  42.00000000000363
  42.00000000000181
  42.0000000000009
  42.00000000000045
  42.00000000000022
  42.00000000000011
  42.00000000000005
  42.00000000000002
  42.00000000000001
  42.0
  42.0


Interesting -- there are more rows (incl removed rows) in yours, because the first step in TFA halves the exponent. I guess it behaves more like binary searching the list of numbers than binary searching the range of numbers, which should be more efficient when the distribution is so heavily skewed.


Yes, in fact we have a special version of bsearch to handle all the float corner cases, and part of that deals with the max being infinity [0], we can check that and then just try the largest finite floating point number.

[0] (https://github.com/oracle/truffleruby/blob/fde88d4019ab83716...)


Just a shout out that I really appreciate the work you guys are doing on GraalVM, especially the polyglot parts. It’s one of the more exciting parts of Graal that I don’t see too many people talk about, it’s usually all about native image and the JIT.


I'm left wondering if this is real behavior, or you're doing a Douglas Adams joke...


The 42 comes from the article itself.

Both solutions make sense... the blog post refers to a ieee<->integer representation hack that likely finds answers faster, because it more quickly finds the values that tend to be in realistic numbers. It works by doing a binary search of all possible values representable in floating point (of which there are more for smaller numbers, due to the design tradeoff of IEEE floating point).

The TruffleRuby binary search solution is also "correct" in its own way, in that it's binary searching using the actual floating point values themselves.


I doubt that 42.0 > 42


That's the list of checks made by the bsearch algorithm. It will have to do at least one check near the end that's <= 42.0.

EDIT: It will also be probing to the ULP level, which the print could be rounding to 42.0. That could explain why there are at least two checks of "42.0".


  The following values were inspected:
  1
  2
  4
  8
  16
  32
  64
  32
  48
  40
  44
  42
  43
The most surprising part for me is that in the integer search 32 is inspected twice. From my brief testing it seems to only happen with infinite ranges. Is that a bug in bsearch or am I missing something?


With a finite range, you can bisect directly by splitting in the middle of the range.

With infinite ranges, you can't do that; so the usual way is to start with a small number and increase exponentially until you find a number that is too large; which is what is done here. When you got that number, it becomes the upper bound of a finite interval.

So that's a two step process, which we can see here. The first 32 is in the exponential growth step (so is 64), and the second one is in the bisect step.

This will always happen exactly once (unless the expected result is 0) and for only the first pivot in the bisect, so it's not that bad; but indeed, they could get rid of it by bisecting on [1/n; n] instead of [0; n], as they already know that 1/n (and numbers lower than 1/n) isn't a valid candidate from the first step.


> they could get rid of it by bisecting on [1/n; n] instead of [0; n], as they already know that 1/n (and numbers lower than 1/n) isn't a valid candidate from the first step.

Did you mean [n/2; n]?


Oops, yes, I did.


Interestingly, they're doing unnecessary work here. In this case, the algorithm has already checked 32, so there's no need to check again.

In fact, if you're checking N, it's guaranteed that N/2 has already been checked, and the next best step should be (3/4)N; in this case 48.


It shouldn't be a problem but technically you are right that Ruby does one more comparison than needed. My guess is that it would mean to keep the previous value of mid (as in `bsearch_integer_range(prev_mid, mid, 0)` instead of the current `bsearch_integer_range(beg, mid, 0)`) and that might be annoying to do in C.


Meta: what is the time between the same URLs being considered unique posts on HN now? I think it was 24 hours back in the old days, but seems to be somewhat less now as the post's author submitted it only 9 hours ago: https://news.ycombinator.com/item?id=25224627 :-)


As I understand it, it depends on traction. That one got only 9 points; I don't know what the bar is, but I think resubmissions get another shot if it's not met.

(And if it's really soon after, submitting a duplicate just redirects you to the extant submission and upvotes it for you.)


>And of course, 0 is represented in floating-point with all the bits set to 0.

+0 that is, -0 has the MSB set. That's one of the potential annoyances of IEEE floats, unlike two's complement integers there are two zeroes.


I vaguely remember learning similar fact years ago, but it was phrased something like "There are as many floats in [0, 1] range as in [1, infty]". Leaving aside where exactly "midpoint" of floats lies (either in Ruby's implementation or other languages'): what would be implications of this for writing code dealing with floats? Can I shoot myself in the foot somehow with low precision if numbers I'm using are "too close" to infinity?


What you describe is a common problem for video games, because if the engine uses "absolute" floats for everything in the game you effectively lose precision as you move away from the origin. If your maps get very large it gets very noticeable. I remember for instance in No Man's Sky when you traveled very far in a system you'd start to see the various animations become very jerky because the precision would become too low to represent the coordinates of the intermediary steps correctly.

See a random video showcasing the issue here: https://youtu.be/D2XX2ZnRk8M?t=197

You can see all the moving elements jerking around as they "snap" to the next available float value.

A common fix for this issue is to use two sets of coordinates: you can for instance represent your world as a grid with fixed-size cells, then you translate all your models into the local cell before computing anything, this way you always have good enough precision since you effectively limit the amplitude of your floats. Of course I handwave many complications here, such as what happens when you're at the edge of a cell for instance, but it's workable.


This video goes into something vaguely related with how the coordinate system works in Mario 64: https://youtu.be/kpk2tdsPh0A

There's a lot of interesting problems that arise when making a 3d game vs a 2d one.


This channel is a fantastic resource for anyone interested in SM64 hacking, here is another of his videos that specifically deals with floating point rounding error glitches: https://www.youtube.com/watch?v=9hdFG2GcNuA


> A common fix for this issue is to use two sets of coordinates: you can for instance represent your world as a grid with fixed-size cells, then you translate all your models into the local cell before computing anything, this way you always have good enough precision since you effectively limit the amplitude of your floats.

Isn't this in practise creating a double-precision float by adding a second "significant figure" in a "base float" system?


You can think of it that way, sure, but since you want your computations to remain as fast as possible you don't want to use the full "double-precision" float everywhere, so that's where it gets tricky. You essentially want to translate everything to your local referential once and for all, then do everything with local coordinates if possible.

It's somewhat reminiscent of segmented memory in a way.


>> a grid with fixed-size cells

> creating a double precision float ... ?

This added top-level is uniformly distributed - so yes, it's a more precise float, but no, it's not a direct analogue.


Kind of but you get significantly better precision than if you just use a normal double float type because doubles only push the issues further out. Especially in games you're likely already breaking the world into cells for streaming chunks into/out of memory anyways.


Isn't this also just pushing the problem out? Once you get far enough out, you wouldn't be able to transition between cells because of the rounding error of the cell boundary being bigger than the cell size.

You can't represent infinite precision with finite bits so you must run into an issue eventually.


Of course but remember that it grows exponentially with the number of bits. Every bit you add one bit it doubles your usable size.

The visible universe (according to Wikipedia) is 8.8 * 10^26 meters or about 5.4 * 10^61 Plank lengths. That gives us a log2 of 205.1

In other words if my maths is correct if you want to represent the entire visible universe down to the scale of the Planck length you need 206bits of resolution, or 4 64 bit integers with a lot of room to spare.

And if you don't actually aim to simulate subatomic particles you can get down to millimeter resolution with "only" 100bits.

And that's not even taking into account that, our universe being so empty, you can probably fudge super large distances a bit (having a lower resolution at scales where the universe is mostly empty). Nobody is going to notice if Andromeda if a few parsecs closer than it should be.


Yes, you can. A good way to think about it is that a floating point number has a fixed amount of significant digits. A 32-bit float has between 6 to 9 significant digits[0]; a 64-bit float ("a double") can represent just double that - i.e. ~16 significant figures.

This means you can represent 12345678 and 0.12345678 as 32-bit floats just fine, but if you try to add the two, you'll just get 12345678. But there's also a more subtle case, in which you try to add 1234.5678 to 0.12345678, and lose half of the significant digits from the smaller number.

These types of errors are common in calculations dealing with large differences in orders of magnitude, and a part of the common wisdom to not use floats for anything involving money.

Another common occurrence of precision errors happens when modelling large 2D or 3D environments in video games. The simplest approach starts with a global coordinate system, in which calculations take place. Since the number of significant digits remains fixed, the further you are from origin, the less precision you have available for your unit of measure of position - meaning the same calculations happening far away from (0, 0, 0) will have more errors than those happening close.

A somewhat well-known example of that was[1] Kerbal Space Program - a game that models realistic spaceflight within a fictional solar system. As you ventured further out and visited the most distant celestial bodies, you'd notice your ships would spontaneously shake apart and/or explode, as if physics stopped working. Affectionately called a "Kraken attack", it was due to floating point precision errors - that far out, in the global coordinate system, the difference between two closest representable positions became comparable with the size of parts from which the ships were built. With that big of a delta, physical calculations would round some forces down to zero, and massively magnify others, as simulated objects could suddenly only move in increments comparable with their own size.

--

[0] - The limitation is in binary, which doesn't map cleanly to base 10.

[1] - I think they got around to mitigating this eventually. One way you can approach such mitigation is by dividing your space into cells (and possibly subcells), each with its own coordinate system, and do as many calculations as you can within these cells, so that everything is close to the origin of their local coordinate system. Then, for (hopefully few) remaining calculations that involve multiple distant cells, you can use larger-precision representations, which are much more computationally expensive.


> [1] - I think they got around to mitigating this eventually.

Yes, as far as I know nowadays KSP works by inversing the point of reference, that is, your spaceship does not move and sits at (0,0,0) at all times, it's the whole solar system that moves around you.

Of course, functionnally it does not make a difference, and it solves the problem ;)

EDIT: ah, interesting that you have another theory, now I wonder :)


I don't know how they solved it in KSP, I was just presenting a general approach for when you need to have things happen in multiple places around a large simulated universe.

I can imagine KSP going with what you described, because in that game, things you aren't actively controlling and that aren't in "physics range" (2.5 kilometers IIRC) are either treated as stationary or on-rails (i.e. they're in orbit, and their positions are computed with a closed-form equation); anything that's neither stationary nor in orbit will be deleted once it leaves the physics range. This way, there is no physics simulation to be done on things you don't control or aren't withing few kilometers of, so centering the coordinate system at your active vessel would be a very good approach.


> Can I shoot myself in the foot somehow with low precision if numbers I'm using are "too close" to infinity?

The "number of sig figs" precision will be the same for large values far away from zero, as for values close to zero. E.g. IEEE 64 bit reliably gives you about 15 decimal digits of precision. Any decimal number with 15 digits of precision that is in the range of the IEEE 64 bit double can be converted to that type, and then back to decimal, such that all those digits of precision are recovered.

You can shoot yourself in the foot if you rely on floating-point values being able to exactly represent integers, but the values go beyond the range where that is possible. Beyond a certain range, all consecutive integers are no longer representable; some integers get approximated by nearby integers.


I guess the fact would be something like: "There are as many floats in [0,1] as integers in [1, infty]".

Probably you will have problems mixing operations between numbers too close to infinity with numbers too close to zero. But if you are working with floats you are probably going to shoot yourself in the foot anyway unless you understand IEEE floating point arithmetic, precisions and so on, so better stick to Decimals and BigDecimals if you can.


Finally, that settles it: ∞ = 3


And we know from the bible that π = 3, so in effect π = ∞.

This should simplify many things that people have agonized over!


Better said, we know from the State of Indiana that π = 3. The biblical thing was observation; the State of Indiana codified it in law.


This fact is also used in quantum chromodynamics: https://en.wikipedia.org/wiki/1/N_expansion


C++ :)

  #include <iostream>
  #include <limits>
  
  double midpoint(double first, double second) {
    auto firstAsInt = reinterpret_cast < int64_t & > (first);
    auto secondAsInt = reinterpret_cast < int64_t & > (second);
    auto mid = (secondAsInt + firstAsInt) / 2;
    return reinterpret_cast < double & > (mid);
  }
  
  int main() {
    std::cout
      << "1.0 <=> 1.5 = " << midpoint(1.0, 1.5) << "\n"
      << "0.0 <=> inf = " << midpoint(0.0, std::numeric_limits < double > ::infinity()) << "\n";
  
    return 0;
  }


Rant:

Technically, that code invokes undefined behavior as you use `reinterpret_cast` to alias variables. The only standards conforming way (prior to `std::bit_cast`[0] in C++20) was to use `memcpy`.[a] `reinterpret_cast` was added for situations where code you have no control over requires a certain type, but you need to force it to take your variable.[b]

[a]: As `memcpy` (in addition to reinterpreting bits) copies the bits (although a compiler will most likely optimize it out), it doesn’t violate aliasing rules.

[b]: This also requires that the code you give your kludged variable to not read out those bits as what it thinks it is (because aliasing). Kindof pointless because: what’s the point of passing a variable that’s never read?

[0]: https://en.cppreference.com/w/cpp/numeric/bit_cast


memcpy certainly violates aliasing rules. You can access an object as an array of bytes, but you can't memcpy an object to one of a different type and then access it as that type without invoking undefined behavior.


Not true. You actually can. Aliasing is about viewing the same bits as two different types (what `reinterpret_cast`, union-based type punning, and cast pointers do) `memcpy` copies the bits to a new location which doesn’t violate aliasing. I don’t have time to find the reference in the standard, but cppreference.com[0] says it’s ok:

> Where strict aliasing prohibits examining the same memory as values of two different types, std::memcpy may be used to convert the values.

[0]: https://en.cppreference.com/w/cpp/string/byte/memcpy


Who is cppreference.com? The domain registration is concealed, just like that of the other dubious site, cplusplus.com.

I don't see any such a claim about memcpy having magic powers in the N4659 C++ draft.

All it says is that the underlying bytes of an object can be copied to an array of char, unsigned char, or std::byte. Then if that array is copied back into the object, the object subsequently holds its original value.

Anything more about memcpy comes from C by reference, and that would be about the last document on Earth to grant definedness and portability to something like this.


memcpy() by itself cannot violate aliasing rules. By definition, char can alias any object.

Whether you can access an object that has been memcpy()'d into is a separate question. I can't find specific language about this, but I strongly suspect that the result is implementation-defined or unspecified (not undefined), unless there is a possibility of a trap representation.


This remark doesn't appear to add anything to my original unedited comment, which includes the clarifying words "you can't memcpy an object to one of a different type and then access it as that type without invoking undefined behavior".


My reading of section 8.2.1 suggests that it is not UB to copy bytes from an object of one type to an object of another type, and then to read the value of the second object, since it is explicitly allowed to access each object as bytes.


It being well-defined to access an object as an array of bytes doesn't add up to it being well-defined to access a whole object as an lvalue of any type. Type aliasing rules do not work this in this constructive way.


That's because the well known fact that e^e = inf. Therefore log(inf) = e. Ruby is probably rounding up e/2.

Proof: the infinite series 1 + 1/2 +1/3! +... is known to converge to e. Now arrange in binomials: (1+1/2)^e + (1/3!)^e + ... this is known to be larger than (1+1/2)^2 + ... = 1 + 1/2 + 1/4 + 1 + 2/3 + 1/9 + ..... But this infinite series contains the series that sums to 2 (1 + 1/2 + 1/4 +...) and so the series that sums to 3, etc. Then it is equal to the sum of all positive integers which is infinite.

(This is a joke.)


been looking at `e^e = inf` for a minute now and I just don't get the joke. What am I missing?


That \sum_{n=1}^inf 1/n is actually -1/12 /s


"if x and y are doubles and x > y, then we have shown that double_as_int64(x) > double_as_int64(y)"

This only applies for numbers of the same sign though. As soon as the sign changes, the relation breaks since floating point numbers use an independent sign bit while int64 is 2's complement.


double_as_int64 as defined in the linked source code does take care of the sign so that double_as_int64(x) == -double_as_int64(-x). This is also important for the negative zero case.


Maybe it isn't wrong after all. Two uncountable infinities are equal!


Sorry but they are not, power set of R is bigger than R.


Unfortunately not, since infinity minus infinity == pi.

[0] https://www.youtube.com/watch?v=CTHS6Xuym7M


This all makes sense. It seems like there would be some special edge cases around the finite/NaN boundary that would be rarely exercised. I wonder if/how those are handled.


From Python:

    >>> import numpy as np
    >>> 1 + np.Inf
    inf
    >>> (0 + np.Inf)/2
    inf
    >>>
Infinity is not a number it is a concept[1] My math teacher used to say think of infinity like a impossibly large number. An impossibly large number divided by two is still an impossibly large number

[1] http://mathforum.org/dr.math/faq/faq.large.numbers.html


I'm not talking about math. I'm talking about computer numbers, specifically IEEE-754.

NaN is represented by a maximal (all ones) exponent, and at least one non-zero in the mantissa. Infinity has the same exponent but an all-zero mantissa. So with a naive comparison, NaN compares greater than infinity.

If you only believe output from programming languages, javascript claims that the type of infinity is "number".

    >>> typeof Number.POSITIVE_INFINITY
    "number"


I know you were talking about floating point. That is why I said what I said using python code. It seems to be a concrete representation of IEEE-754 math. Also from my reading and understanding numpy is written by scientists that understand IEEE-754 perhaps a little more than some others.

I was fully away of that stuff you say about how NaN is represented and was also aware of how Inf is represented. My statement and link to the concept of Infinity on mathforum was an effort to support the idea of why most math done with code involving Inf will also result in an answer of Inf.

The article that this whole discussion is about seems to think it is OK (ie not a bug) to convert a float like Inf to a integer do some math and convert back to a float and get 1.5 and that is OK. It seems like a bug to me.


While you’re correct, you’re being downvoted probably because you’re taking this seriously. I’m sure most everyone here knows infinity isn’t a number, but a concept, but you never know.


I think any conversation that says there is halfway between inf and 0 is treating inf like it is a fixed point and therefore a number and not a concept

This is why participating on Hacker News conversations are problematic. I think you are talking about downvoting because a persons misunderstands me. Even in your statement you imply by saying "but you never know" that it is possible people aren't being aware of the point I was making


This seems inefficient especially if the answer is close to a power of two? You'll spend a lot of time bouncing between exponents instead of refining the mantissa.


It's a binary search in a 64-bit search space. Powers of two are no more inefficient than other answers, on average, if you assume the answers are uniformly distributed.

You could do an exponential search, which is a reasonable choice and makes logical sense, but the exponential search won't be any faster on average.


Paging Dr. Cantor. Dr. Georg Cantor. Paging...


The odds of him picking up are infinitesimal.


Isn't it -1/12 actually?


I was trying to remember the situation where in physics, under certain conditions, a proof that shows infinity can be represented numerically as -1/12 (it was some obscure fraction yet after seeing it, I think this was the value).

I couldn't for the life of me remember the set of conditions and proof that shows this but it was interesting when reading through it a few years ago.


In physics, it is something related to removing divergences in bosonic string theory, I am not an expert in that. Here, however, is a fantastic article that shows how -1/12 is actually derived: https://medium.com/cantors-paradise/the-ramanujan-summation-... (spoiler: it is really easy and could be done by a high schooler).


That proof (and the more general statement) is not correct because it makes assumptions about infinite summation of divergent series that are not true.

There is a link between the sum of natural numbers -- namely, the analytic continuation of the Reimann Zeta function has Z(-1) = -1/12. But the Zeta function is only defined to equal the sum of inverse powers for Re(z) > 1. And under a specific definition of summation (Ramanujan summation) you can say that "the sum is equal to -1/12" but that isn't the same as normal summation.

Mathologer did a fairly in-depth video[1] into why this proof is wrong and what the actual link is between the infinite series and -1/12. The upshot is that even if you use more complicated definitions of summation, you cannot define sums of the kind 1+2+3+... to equal a finite number.

If you assume the infinite sum of 1+2+3+... converges to a finite number you can easily prove a contradictory statement using the same summation properties assumed by that proof. Namely:

  S  = 1 + 2 + 3 + ... = -1/12
  S2 = S-S = 0
     = 1 + 2 + 3 + ...
     - 1 - 2 - 3 - ... = 0
     = 1 + 2 + 3 + ...
         - 1 - 2 - ... = 0
  S2 = 1 + 1 + 1 + 1 + ... = 0
  S3 = S2-S2 = 0
     = (1-1) + (1-1) + ...
     = 1 - 1 + 1 - 1 + ... = 0
But we "derived" in the original proof that S3 is equal to 1/2, which is a contradiction. (You could derive S3 is 1/2 from S but that's what the article does in reverse.)

[1]: https://youtu.be/YuIIjLr6vUA



3 should be enough for everyone.


Every time I see a bizarre mathematical output I am reminded that IEEE 754 is gross as hell.

I just use bigints wherever it's possible to transform the algorithm to work with bigints, and I "render to decimal" in views


I disagree: IEEE 754 is quite elegant. The fact that they are monotonically increasing in correspondence to their bit representation is one of the many nice things about it.


IEEE 754 specifies some not-a-number values X that are not equal to themselves; i.e. such that X != X.

The bit pattern is actually not equal to itself: if we compared X bitwise, like with memcmp in C, it would be equal: memcmp(&X, &X, sizeof X) == 0.

That obnoxiously violates the philosophical Law of Identity, as we would like to see it applied in programming languages.

https://en.wikipedia.org/wiki/Law_of_identity


The lack of precision leads to increasing error over time in many contexts (off the top of my head, multiplication and division). I still think that if an algorithm can be rewritten to work with ints instead of floats, it will be served better the vast majority of the time.


Multiplication and division of floats creates rounding errors of <1 ulp each time. In most contexts you never need to worry about them.

The operations you need to watch out for are addition/subtraction, in cases where your result has much smaller magnitude than your inputs, causing loss of significance. Sometimes great care must be taken in implementing numerical algorithms to avoid this. But this is an inherent problem in numerical computing, not the fault of the floating point format per se.


When does this problem crop up when only dealing with pure ints?


Doing integer/rational arithmetic gives you a choice: either never do any rounding and require exponentially growing precision that makes even the simplest algorithms impractically expensive (not to mention giving up entirely on the many common computations which cannot be represented whatsoever in an exact rational arithmetic system), or allow rounding/approximation of some kind and end up with roughly the same problems floats have.


While division is a problem, you could symbolically represent numbers such as 1/3 by storing the numerator and denominator as a fraction of 2 values.


I wondering why Ruby's binary search uses infinity as the max to kick off the search, instead of max float.


The programmer wrote 'found_value = (0..Float::INFINITY).bsearch do'...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: