Compute sine and cosine with reproducible floating-point bits. Angles are in radians.
dotnet add package RecursiveAI.OneMathRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using RecursiveAI.OneMath; var (sin, cos) = Math64.SinCos(1.0); Console.WriteLine($"{sin:R}, {cos:R}");
0.8414709848078965, 0.5403023058681398Unzip the example, then dotnet run.
The full program
The full program computes both precisions for one radian and prints their numeric values and hexadecimal bit patterns. Its final two lines check infinite input and an inverse-trigonometry domain error; these return NaN rather than throwing.
using RecursiveAI.OneMath; var (sin64, cos64) = Math64.SinCos(1.0); var (sin32, cos32) = Math32.SinCos(1.0f); Console.WriteLine($"sin64: {sin64:R} 0x{BitConverter.DoubleToUInt64Bits(sin64):X16}"); Console.WriteLine($"cos64: {cos64:R} 0x{BitConverter.DoubleToUInt64Bits(cos64):X16}"); Console.WriteLine($"sin32: {sin32:R} 0x{BitConverter.SingleToUInt32Bits(sin32):X8}"); Console.WriteLine($"cos32: {cos32:R} 0x{BitConverter.SingleToUInt32Bits(cos32):X8}"); Console.WriteLine($"Infinite input: {double.IsNaN(Math64.Sin(double.PositiveInfinity))}"); Console.WriteLine($"Outside asin domain: {double.IsNaN(Math64.Asin(2))}");
dotnet run prints:
sin64: 0.8414709848078965 0x3FEAED548F090CEE cos64: 0.5403023058681398 0x3FE14A280FB5068C sin32: 0.84147096 0x3F576AA4 cos32: 0.5403023 0x3F0A5140 Infinite input: True Outside asin domain: True
Variations
Call sine and cosine separately when you do not need the pair:
using RecursiveAI.OneMath; Console.WriteLine(Math32.Sin(0.5f)); Console.WriteLine(Math32.Cos(0.5f)); Console.WriteLine(Math64.Sin(1e300));
0.47942555 0.87758255 -0.8178819121159085
Related recipes
The short answer
Use Math64.SinCos(radians) for doubles or Math32.SinCos(radians) for floats. The tuple contains sine first and cosine second. Both functions also have separate Sin and Cos methods. Choose the input precision deliberately: 1.0f and 1.0 select different floating-point representations even when they denote the same exact number.
The elementary-functions contract says every function is correctly rounded for every input: round to nearest, ties to even, with gradual underflow and overflow to infinity. It states that results are bit-identical on every platform. That is a claim about the function’s result for a given input, not a promise that unrelated application code will choose the same input.
Is floating point math deterministic?
Math.Sin and Math.Cos use platform math, so their results are not covered by OneMath’s same-bits guarantee across machines and runtimes. OneMath instead defines its own rounding and special-value behavior for both Math32 and Math64, with the same result bits for the same input.
Reproducibility still starts with equal input bits. If one machine calculates its angle through a different expression, or your application changes units or precision, the function may receive a different value. Keep your input data, conversion rules and evaluation order consistent. The example fixes the input to one radian and prints the result’s bits to make that boundary visible.
The full program uses BitConverter to display hexadecimal representations. The double results have a different width from the float results. Decimal output is useful for people, but the bit representation distinguishes results without relying on how many decimal places you decide to display. Keep formatting separate from the calculation when comparing runs.
How do I compute deterministic sine and cosine in C#?
Use the tuple-returning call in the snippet at the top, or the separate calls in the variation. Math32 rounds its result to float precision; it is not simply a promise that taking a rounded double result and casting it yourself will always give the intended float result. The documentation says float inputs are widened exactly and the final rounding differs.
Large finite angles are supported; the variation includes Math64.Sin(1e300). Do not reduce such an angle using a rounded multiple of π in your own code first and expect to retain the original input’s result. Passing the original value lets the function perform its own argument handling.
Non-finite inputs and domain errors do not become exceptions. The example checks that sine of positive infinity and arcsine of 2 return NaN. The functions documentation also specifies canonical double.NaN or float.NaN for every NaN result, regardless of input payload. This is the elementary-functions contract; do not generalize it to every operation on vectors and matrices, whose NaN payload rules are documented separately. Validate inputs before calculation if NaN is not an acceptable value in your application’s state.