# Can sine converge to zero at infinity?

In common languages, sine produces an error, NaN, or exception when evaluated at infinity. For example, in Python:

>>> math.sin(float("inf"))
ValueError: math domain error


However, consider the fuzzy nature of floating-point arithmetic. Large floating-point numbers are sparse and prone to rounding errors. As a result, sine is frustratingly inexact on large inputs:

>>> math.sin(10**300)
-0.8178819121159085
>>> math.sin(10**300 + 1)
-0.8178819121159085
>>> math.sin(10**300 * math.pi)
-0.902464352009667


Consider an inexact large input as a continuous range. The average value of sine on that range is small, because most of the range covers pairs of peaks and troughs which cancel out to zero.

So, suppose that we attenuate sine for large inputs. Specifically, we assign each large input a range, and divide the result of sine at that input by the number of wavelengths in that range (effectively the range divided by 2π); at (positive) infinity, we let sine converge to (positive) zero. What breaks or has counterintuitive behavior as a result?

• I'm voting to close because I'm not convinced this is a programming language question and not a math/floating point question. Dec 2, 2023 at 20:31
• I'm voting to leave open. I can't quite put into words why I feel this should be on-topic but I do. Dec 2, 2023 at 22:20
• What would the benefit of this be? Why should 0 be a better wrong answer than any other? Dec 2, 2023 at 23:02
• As I mentioned in chat, this seems to be a question about the implications of making a standard library function have non-standard behaviour. Asking about standard library implementation is allowed on this site, so asking about the implications of a standard library implementation/design choice would be on topic for this site too.
– lyxal
Dec 3, 2023 at 3:39
• You should rather ask what doesn't break when you define sin(∞) := 0. Among others, since ∞ + pi / 2 == ∞ and sin(x + pi / 2) == cos(x), your definition would imply that sin(∞) would be both 0 and 1. BTW, sympy returns the correct result : sympy.sin(10**99999 * sympy.pi) # => 0 Dec 4, 2023 at 12:04

The most obvious breakage, to me, would be the constraint that $$\sin^2+\cos^2$$ should be approximately one -- and related higher order constraints, like a rotation matrix being orthogonal.

I think math libraries generally consider floating point values as exact but sparse values, rather than intervals. It's worth mentioning that "interval arithmetic" does exist, and it comes with its own set of rounding rules.

• Isn't the "sparse value" interpretation inconsistent with having signed zero? Dec 3, 2023 at 0:00
• So you're arguing that if sin(∞) = 0, then cos(∞) = 1? Dec 3, 2023 at 14:16
• @Bergi well, for Inf, I think the current behavior (NaN) is better, but, if sin being 0 was a given, I would set cos to 1 or -1 Dec 3, 2023 at 17:12
• @KarlKnechtel, so? Those are also exact values. Mathematically speaking, they're the exact same value, and exist just as a bit of an odd feature of to the implementation (but may sometimes be useful to distinguish positive and negative really-close-to-zero values, which can't be represented accurately due to the inherent limits of FP numbers). Even if you object to having two zeroes, that has nothing to do with the other values being treated as exact, or not. Dec 4, 2023 at 8:16
• @Bergi: That would break another constraint, that cos(phi)=sin(phi + pi/2). That rule implies cos(∞)=0. There are no winners here. Dec 4, 2023 at 10:56

Asking for sin(1e300) in floating-point context is not even wrong.

Way before 1e300, one gets into troubles with aliasing, π inaccuracies and other bad things arising from the floating-point conceptual limitations that make asking for sine of big enough numbers pointless.

That said, you are proposing to make the result even more wrong (for some quite precise measure of wrong) by making the implementation more complex.

What you will get is generally some minor performance hit, because no sane person will use sin(x) in the range you are trying to alter, except by mistake.

But, good luck debugging such a mistake.

• This answer seems to not consider numerical error, which is always part of numerical methods. Folks asking for sines of large numbers are already aware that the results will not be especially accurate, and folks using sine-heavy algorithms already take care to scale their inputs to be near the unit circle. Consider e.g. integration over bandlimited textures in the context of raytracing for an analogous construction; a checkerboard texture is just a thresholded sine, after all. Dec 3, 2023 at 17:49
• OK, you need a sine-like function that works in particular manner outside of the reasonable sine application. Go on and implement it under appropriate name in your program or library. You may (or may not) have more luck using fixed-point? Dec 5, 2023 at 5:37
• @fraxinus: In what non-contrived applications would a function that computes a floating-point value within 0.5ulp of sin(x +/- 0.5ulp) be any less useful than one that always computes sin(x) +/- 0.5ulp? In what non-contrived applications would a function that precisely computes the latter value be more useful than a faster functiont that computes the faster? Dec 20, 2023 at 19:37
• @supercat in FP world, any value is +/- 1/2ulp, isn't it? Dec 21, 2023 at 7:52
• @fraxinus: An "ideal" transcendental function would be +/- 1/2ulp of the exact value specified, but in many cases achieving that level of precision may be much more expensive than a function with looser tolerances. Computing within 1/2 LSB the double-precision sine of some numbers that are near 1E300 would require either performing computations with well over 1,000 bits of precision, or else having a table listing argument values that are extremely close to being exact multiples of pi, and the associated return values. Dec 21, 2023 at 17:18

Well, the IEEE-754 standard breaks, for a start. The standard specifies that, if supported, the domain of the $$\sin$$ operation is $$\left( -\infty,\infty \right)$$.

The standard does not impose explicit requirements on how precise an implementation of $$\sin$$ should be, however there is an objectively correct answer for what, in an ideal world, sin(1e300 * PI) should return:

• Represent 1e300 * PI as the nearest correctly-rounded floating-point number. Call this $$x$$.
• Return the nearest correctly-rounded floating point number to the exact value of $$\sin(x)$$.

It is the 21st century, and this is what a programmer should expect. Unsurprisingly, this is what most real math library implementations try very hard to do, along with proofs of correctness.

The cost is usually run-time performance. Evaluating sin(1e300*PI) requires some special-path code to handle that region of the domain which is likely to be much slower than, say, evaluating $$\sin$$ in the subset of the domain $$\left(-10\pi,10\pi\right)$$. The feeling is that it's not an error to ask for sin(1e300*PI), but the programmer arguably deserves the performance they get.

• It would be the fp number nearest to 1e300, multiplied by the FP number nearest to PI, rounded to the nearest FP number. Dec 3, 2023 at 17:03
• (+1) Side remark: In an ideal world someone who wants to compute $\sin(10^{300}\pi)$ would invoke sinpi (1e300). Dec 4, 2023 at 7:37
• ...assuming that sinpi is available. The assumption here is that it's not, or that the value just happens to be $\sin\left(10^{300}\pi\right)$. Dec 4, 2023 at 11:25
• @Pseudonym To be clear: Does your "(−∞,∞)" include or exclude the endpoints? I would expect exclude and "[−∞,∞]" includes the ends. Dec 4, 2023 at 15:58
• @chux-ReinstateMonica square brackets include, round brackets exclude Dec 5, 2023 at 0:35

This simply directly contradicts the mathematical (and sensible) notion of "converges," which we usually interpret as the value at a limit. The limit $$\lim_{x \to \infty} \sin x$$ doesn't exist. (This can be proven by contradiction using the Epsilon-Delta definition and setting $$\epsilon$$ to 1/2).

Generally speaking, it is best when mathematical results in programming languages most closely match the representation of the mathematical result they represent in mathematics. In the case of floats, $$\sin \infty = [-1,1]$$, and a range is obviously Not a Number.

• Hi! I agree fully with everything that you've said. One important bit of context, though: in numerical methods, ∞ is just the result of overflow. A floating-point number system has a maximum and minimum possible exponent, and sometimes a computation will underflow to 0 or overflow to ∞. This could make for a good third paragraph. Dec 4, 2023 at 8:37
• Another cool possible direction to explore is user experience and human-computer interfaces. If we were programming a pocket calculator, we'd want sin(∞) to display something not like "overflow" or "ValueError", but perhaps "sin does not converge at inf". Something informative and helpful. Dec 4, 2023 at 8:40
• @Corbin "in numerical methods, ∞ is just the result of overflow." or division by 0, IOWs a true infinity, not just an oversized one. Dec 4, 2023 at 15:52
• @JustinHilyard, Rather than return NAN for finite/0 per your should return NaN in all cases, not ∞ or -∞, IEEE-754 returns +/- ∞, without regard to how you/I think should happen. Dec 5, 2023 at 20:37
• @JustinHilyard You may enjoy the affine/projective infinity discussed here. Dec 6, 2023 at 16:40

It is unclear which properties of sine and cosine are most important to retain as we start to deal with roundoff. The preferred set of properties would certainly be application specific.

A compromise I have seen in a few languages is to include a version of sin which multiplies by pi before taking the sine, such as C++23's sinpi. This doesn't fix all of the roundoff issues that can occur, but it does align key values. Inputting integers to sinpi always yields exactly 0 because the particular floating point subtraction needed is exact.

The real challenge with treating these numbers as ranges would be consistency. If sin and cos were to treat the input as a range in a language, I would expect the other operations to do so as well. In particular, multiplying a number by 4 should result in a range that is 4x larger than the smallest possible interval. This does not have hardware support, which would make it tricky. It also would lead to tricky challenges supporting inverses.

Fuzzy logic might be an interesting direction to go. In such a case, the values are not ranges, but distributions over that range. In such cases, assuming cos(x * HUGE_VALUE) is a uniform distribution from -1 to 1 may be a very reasonable approach.

• For argument reduction, you can subtract the nearest even integer, which will always be an exact operation if x ≠ inf. Or subtract the nearest multiple of 0.5 and keep track of the quadrant for the sign and to decide whether to calculate sine or cosine from 0 to pi/2. Dec 5, 2023 at 15:48
• @gnasher729 I'm not privvy to all the decisions the C++ team made regarding sinpi, but I woudln't be surprised if you nailed it on the head: subtracting 1.0 or 0.5 is always an exact operation. Subtracting pi or pi/2 is not. Dec 5, 2023 at 20:01
• Ideally, this would clarify that sinpi et al were specified in C23, and just imported to C++ by its reference to the C Standard Library. They have separate standards committees, who occasionally have worked in tandem. Dec 5, 2023 at 22:22
• "This doesn't fix all of the roundoff issues that can occur, but it does align key values." It fixes the biggest issue: Taking the fractional part of a floating-point number is an exact operation. Dec 8, 2023 at 0:34

What breaks or has counterintuitive behavior as a result?

Well, if you define $$\sin(\infty) = 0$$, then by the same reasoning you'd have to define $$\cos(\infty) = 0$$ as well, which breaks the Pythagorean identity $$\sin^2 x + \cos^2 x = 1$$. If you support complex numbers, then you also get $$e^{i\infty} = 0$$, which breaks the assumption $$|e^{i\infty}| = 1$$. And then there's all the other trig identities for sums and differences of angles...

The “correct” interpretation of an expression like math.sin(10**300) depends on what you consider 10**300 to mean. Possible interpretations are:

1. Exactly $$10^{300}$$. Then the correct (to IEEE 754 double-precision) result of the sin call is -0.985750425160377.
2. The nearest representable floating-point value to $$10^{300}$$, which works out to $$2^{946} \times 1681218273811815$$ (again assuming double-precision). Then sin should return -0.8178819121159085. This is the interpretation that Python is using.
3. Some unspecified number whose floating-point approximation is closer to 1e+300 than it is to either of the adjacent floating-point values (9.999999999999999e+299 and 1.0000000000000002e+300). So, somewhere within an interval with a length on the order of $$10^{284}$$. But since that includes multiple complete periods of the sin function, we have no idea what the angle modulo $$2\pi$$ might be.

If you use the “interval” interpretation of floating-point numbers (#3 above), then there are several ways that you could handle sin(x) given $$x \in [a, b]$$:

• Arbitrarily pick a specific value within the interval, and take the sine of that as best as you can. (#1 and #2 above are special cases of this.)
• Return an interval (or value/tolerance pair) from the function instead of just a single number. So sin(10**300) returns $$[-1, 1]$$ or $$0 \pm 1$$, since we can't pinpoint the value any further.
• Return the average value of sin within the interval $$[a, b]$$. From calculus, this works out to $$\frac{\cos(a) - \cos(b)}{b - a}$$. Or if the interval is represented as $$x \pm \epsilon$$, then it's $$\frac{\cos(x-\epsilon) - \cos(x+\epsilon)}{2\epsilon} = \frac{\sin(x)\sin(\epsilon)}{\epsilon}$$. If $$\epsilon$$ is small, then $$\sin(\epsilon) \approx \epsilon$$, so the average value over the interval $$[x - \epsilon, x + \epsilon]$$ approaches $$\sin(x)$$. But if $$\epsilon$$ is big, then $$|\sin(\epsilon)|\le 1$$ while $$\epsilon$$ grows without bound, so the average sine value is 0. This is apparently how you want it to be handled.
• Decide that if $$|x| > 10^{15}$$ or so, then the inherent floating-point representation error in x is large enough that it's not even meaningful to try to compute sin(x), and treat it as an error (throw an exception or return NaN).
• Great answer. I like the point about complex identities. One missing detail: trig identities usually don't hold in FP implementations; instead, we use trig identities as part of numerical methods when we are deciding how to encode formulae as algorithms, akin to strength reduction. Dec 5, 2023 at 17:05
• Why would you "have to" define cos(∞) to be zero rather than, say one? The latter would give consistent identities. As you say, the reasoning isn't consistent, and we could equally well choose sin(∞)==1 && cos(∞)==0 or any other consistent pair; that leads me to find returning NaN or throwing an exception the most reasonable result for both. Dec 20, 2023 at 14:53
• @TobySpeight: OP's justification for defining sin(∞)=0 is that sin has an average value of 0. Since cos is just a horizontal shift of sin, it has the same average value of 0. Jan 2 at 21:34

A long time ago I was reading an article about game development somewhere and it said something along the lines of: "If your algorithm does not fit in a float and you feel like you need a double, you're doing it wrong." And this really made floating point and significant digits click in my mind. In the real world, you don't need infinite precision. If you're talking about the distance from Earth to Mars, you're not going to care about millimeters. If you're talking about the size of an amoeba, you won't care about lightyears. Etc.

Looking at large values of floating-point numbers and bemoaning the "large intervals" and "frustrating imprecision" is missing the point. You're doing it wrong. You shouldn't care. (Of course, sometimes you do actually need a double, but in the grand scheme of things those cases are fairly rare)

Similarly about calculating sin at infinity. First of all, "infinity" isn't even a number in the mathematical sense. Doing math on it doesn't make sense in the first place. The IEEE floating point allows it because it's a neat "sentinel value" which you might find useful sometimes - but never as an actual value. It's just a marker to signify that some calculation you were doing went off the rails. Or maybe you can use it to mark some other condition. But don't use it in calculations.

So I think that debating about what value sin(infinity) should take is a moot discussion. It doesn't matter. You shouldn't be doing this. It doesn't make sense. There is no right answer. You can assign it 0 if you want to. Or 1. Or -1. Or NaN. Or throw an exception. Or nasal demons.

Personally, I like the exception because it clearly shows that you're doing something wrong. However I also understand that exceptions can be expensive so maybe you'd rather use a sentinel value instead. May I suggest NaN?

• Hi! I like your point about how ∞ is a sentinel value for errors; specifically, it indicates overflow conditions. In some ways, ∞ is just the edge of the number line. Dec 4, 2023 at 8:18
• Gonna channel Moonchild and link Kahan's MINDLESS.PDF, which has several examples of how just bumping up float to double doesn't work. Numerical analysis is required and fundamentally messy. Dec 4, 2023 at 8:19
• That's channeling me?! I'm honoured. Dec 5, 2023 at 3:15
• Hi. A bit off-topic, but what's your opinion on using +inf and -inf as initialisation values, especially in algorithms that compute minimums and maximums? For instance function minimum(array) { m = +inf; foreach x in array { m = x < m ? x : m; } return m }. Also useful to initialise the distances in Dijkstra's algorithm, the node values in a minmax tree search, etc.
– Stef
Feb 9 at 10:25
• @Stef - Yeah, that's fair. Note that in this case you're not doing math on infinity - you're just comparing it to other values. Feb 9 at 10:36

So, suppose that we attenuate sine for large inputs. Specifically, we assign each large input a range, and divide the result of sine at that input by the number of wavelengths in that range (effectively the range divided by 2π); at (positive) infinity, we let sine converge to (positive) zero.

This idea doesn't even remotely make mathematical sense, so changing things like this isn't going to improve the computer-science results. In particular:

• This kind of attenuation is just unsound; the limit of sin(x) as x goes to infinity does not exist, and the function does not converge in any sense.

• As soon as the "range" (actually a domain) represented by a "large input" includes even one complete cycle (half a cycle in the worst case, actually), the result can meaningfully be anything in the range of the sin function (-1 to +1). Larger values for the input, meanwhile, do not have any more uncertainty.

If you want a calculation to reflect an inexact result, other than imprecision inherent to the numeric type itself, that requires a custom type that actually tracks the amount of inexactness. This can be as simple as a pair of floats, a best-guess value and a size-of-the-error-bar value.

However, even this model has its faults. Simply saying that a value is "0 +/- 1" because it could possibly be anywhere in [-1, 1], loses information about the best guess we might have for the value. The natural way to figure out the imprecision in a function, generally, is to use a straight-line (secant) approximation at the best-guess point, and use that to scale the error in the input value into an error in the output value. But that obviously breaks as the input errors get larger, and especially becomes nonsensical for periodic functions.

People who need to track a large number that represents a quantity in radians with which trigonometric functions need to be calculated later, should bear the responsibility for numerical instability. In particular, they have the option of storing it as a pair of values: an integer (perhaps arbitrary-precision) for the number of complete circular arcs, plus a floating-point value in [0, 2pi).

One alternative to ensure that programmers don't attempt nonsensical calculations, is to restrict the permitted domain: either to something like [0, 2pi) or [-pi, pi), or else to the values that allow the output to be correct within floating-point precision. (Someone else has probably already worked out what that limit is.)

The answer to the titular math question is no. sine has no limit as its argument goes to infinity. Whether a math library should assign 0 to sin(infinity) is a distinct question. There is really no basis for assigning any particular number to sin(infinity). NaN is really the only sensible alternative when NaN is available. Whether to bother with sin(1e300) is yet another question. It's been done. Look up crlibm. It is practical. At worst, it is good to just over half an ulp. Probably it is good to better than half an ulp. Not all floating point values are the result of rounding. 1e300 would certainly not be obtained by adding 1e300 ones. Even when demanding a precise value for a sine seems silly, having a value that is consistent with the cosine is not silly. Accurate values can accomplish that.

Can sine converge to zero at infinity?

No, since it violates the principle of least astonishment on several fronts.

Sine at infinity is NaN, because

1. lim(x→∞) sin(x) doesn't exist,
2. sin(x) is not defined for non-numeric domain, and ∞ is not a number,
3. Having lim(x→∞) sin(x)=0 makes most of calculus internally inconsistent. Sure, you perhaps could have a calculus where this convergence is a fact, but it'd work differently from what everyone else calls basic univariate calculus.

Whether it's a signaling or non-signaling NaN is an orthogonal problem.

However, consider the fuzzy nature of floating-point arithmetic.

There is absolutely nothing fuzzy about floating point arithmetic. Each non-NaN IEEE 754 bit pattern represents a specific, well-defined number on the real number line. Full stop.

As a result, sine is frustratingly inexact on large inputs

Nope. No matter how large an input, as long as it fits into the domain of the numbers a given sin works with (IEEE 754 single or double precision in C etc.), a sane sin implementation should provide a result accurate to the last bit.

math.sin(10**300 + 1)

The fact that 10**300+1 == 10**300 in double IEEE 754 arithmetic has nothing to do with the implementation of the sine function!

What you're really computing in both cases is sin(1.0000000000000000525047602552E301), and this has very much a definite answer that can be computed to arbitrary precision. Mainstream C runtime libraries do provide an exact rounded answer for this, as they well should.

Python isn't a language designed for arbitrary precision computation on reals. It implements arbitrary width integers only. If you'd like to play with arbitrary precision transcendentals, Wolfram Language is your best bet. It has algorithms that compute sines to whatever precision you want, as long as you got time and memory :)

• There are a few inconsistencies in your stance which are worth examining. In particular, what is "exact rounded"? After rounding, a result is definitionally no longer exact! Feb 7 at 19:26
• @Corbin I can think of a one non-inconsistent interpretation of that particular sentence: the mathematical sine of 1.0000000000000000525047602552E301 is a transcendental real number between -1 and +1, and there is a unique floating-point number which is the closest floating-point number to that transcendental number, and this floating-point number is the value returned by sin(1.0000000000000000525047602552E301) in mainstream C runtime libraries. Less-accurate libraries possibly return another float approximation which is not the closest float, hence not "the exact rounded answer".
– Stef
Feb 9 at 10:33

I'll contrast other's saying it's not a proglang problem - IEEE-754 specify aspects that're defined by languages and some that may be deferred to implementations.

If you make sine converge to 0 towards inf, then I suggest you make cosine converge to 1 similarly. This is required to preserve the invariant of Pythagorean theorem. Vincent Lefèvre (Co-author of "Handbook of Floating Point Arithmetic", and you might consider reading it if you're interested) said in his 2023-11-30 mail on the IEEE-754 standard revision mailing list:

Some applications may require properties like $$\operatorname{sin}^2 x + \operatorname{cos}^2 x = 1$$ (approximately).

I used MathJax notation, while the the original mail's in plaintext.

• Why wouldn't cosine also converge towards zero, by the same logic given in the question? See discussion on this answer. Feb 9 at 16:59

Sine, cosine etc have mathematical properties that you want to preserve.

For large $$x$$, in an ideal world you would calculate $$\sin (x-2\pi k)$$ for a suitable integer $$k$$, same for $$\cos$$. In a less ideal world, we replace a large $$x$$ with a small $$x’$$ close to $$x - 2\pi k$$, and with the same $$x’$$ for sin and cos. This preserves $$|\sin x|\le 1$$ and $$|\cos x|\le 1$$ which is much easier for small $$x’$$, and $$\sin^2 x + \cos^2 x \approx 1$$.

When $$x$$ is large enough that the difference between two floating point numbers is more than $$2\pi$$, and $$x$$ is the result of some floating point operation, then $$\sin x$$ cannot produce a meaningful result, so we can let $$x’=0$$, giving $$\sin x = 0$$ and $$\cos x = 1$$. Or we can calculate $$x’$$ as precise as possible. You can find $$x’$$ with almost perfect precision if $$x \le 10^{15}$$ and degrading precision if $$x \le 10^{30}$$ very fast; you could do that and set $$x’=0$$ for larger $$x’$$ or use a $$300$$ digit approximation for $$\pi$$ and do it completely over the whole range.