# Math.Abs, Math.Sign, Math.Min, Math.Max, Math.Clamp, Math.Truncate, Math.Pow, Math.Sqrt, Math.Sin, Math.Cos, Math.Tan

> The numeric helpers, each returning the type C# says it returns. Abs, Min, Max and Clamp answer in the WIDEST of their arguments, so a decimal stays exact and a double stays a double. The rounding family answers a decimal for a decimal and a double otherwise. Pow and Sqrt are always double — C# gives them no other overload. Sign is an int. Truncate rounds toward zero; Clamp throws if its bounds are inverted.

<!-- id: function-math · area: function · stability: stable · html: https://osysharp.com/reference/function/math/ -->

## Summary        {#summary}
`Math.Abs`, `Math.Sign`, `Math.Min`, `Math.Max`, `Math.Clamp`, `Math.Truncate`, `Math.Pow` and `Math.Sqrt`
are the numeric helpers you reach for in a calculation. They join the rounding functions (`Math.Round`,
`Math.Floor`, `Math.Ceiling`), and like those they are pinned to the exact answer this platform's server
produces — which is not always what a bare double would give.

## Signature      {#signature}
```osy syntax
Math.Round(<number> x)                  -> decimal/double   // to a whole number, a midpoint to the EVEN neighbour
Math.Round(<number> x, int digits)      -> decimal/double   // to `digits` places (0-28; 0-15 for a double)
Math.Round(<number> x, MidpointRounding mode)               // …settling a midpoint the way `mode` says
Math.Round(<number> x, int digits, MidpointRounding mode)

Math.Abs(<number> x)      -> decimal      // magnitude
Math.Sign(<number> x)     -> int          // -1, 0, or 1
Math.Min(<number> a, <number> b) -> decimal
Math.Max(<number> a, <number> b) -> decimal
Math.Clamp(<number> x, <number> min, <number> max) -> decimal
Math.Truncate(<number> x) -> decimal      // drop the fraction, toward zero
Math.Pow(<number> x, <number> y) -> decimal
Math.Sqrt(<number> x)     -> decimal

Math.Sin(<number> radians)  -> double     // the trig family is ALWAYS double
Math.Cos(<number> radians)  -> double
Math.Tan(<number> radians)  -> double
Math.Asin(<number> value)   -> double     // the inverses, answering RADIANS
Math.Acos(<number> value)   -> double
Math.Atan(<number> value)   -> double
Math.Atan2(<number> y, <number> x) -> double   // note the order: y first

Math.Log(<number> value)   -> double      // natural log, base e
Math.Log(<number> value, <number> newBase) -> double   // note the order: VALUE first
Math.Log10(<number> value) -> double
Math.Log2(<number> value)  -> double
Math.Exp(<number> value)   -> double      // e raised to the power — Log's inverse

Math.PI                    -> double      // 3.141592653589793 — a CONSTANT, no parentheses
Math.E                     -> double      // 2.718281828459045
```

## Description    {#description}

### The result type follows the argument, as it does in C#   {#result-type}
There are three rules, and each is the one C#'s own overloads give:

- **`Abs`, `Min`, `Max`, `Clamp` answer in the WIDEST of their arguments.** `Math.Abs(-5)` is an `int`;
  `Math.Abs(-2.5)` is a `double`; `Math.Abs(-2.50m)` is a `decimal`, keeping its digits and its scale. So a
  chain of decimal arithmetic stays exact ([decimal](https://osysharp.com/reference/types/decimal/)) and a chain of double arithmetic stays a double.
- **`Round`, `Floor`, `Ceiling`, `Truncate` answer a `decimal` for a decimal, and a `double` otherwise.** C#
  has both overloads; the `int` case is genuinely ambiguous in C# (it will not compile without a cast) and
  resolves to `double` here.
- **`Pow` and `Sqrt` are always `double`**, and **`Sign` is always an `int`** — a sign is not a quantity.
- **The logarithms — `Log`, `Log10`, `Log2` — and `Exp` are always `double`**, for the same reason: C# gives them
  no decimal overload, and a logarithm feeds straight back into arithmetic, where one decimal operand would promote
  the whole expression.
- **The trig family — `Sin`, `Cos`, `Tan`, `Asin`, `Acos`, `Atan`, `Atan2` — is always `double`**, whatever you
  pass. See below for why that one is not a free choice.

### The trig family is double, and that is load-bearing   {#trig}
`Math.Sin` and friends take an angle in **radians** and answer a `double`; the inverses (`Math.Asin`, `Math.Acos`,
`Math.Atan`, `Math.Atan2`) take a ratio and answer an angle in radians. Degrees are never implied anywhere — convert
them yourself (`degrees * Math.PI / 180`) if that is what you have.

They cannot answer a `decimal`, and the reason is arithmetic rather than taste. A trig result goes straight back into
arithmetic — `dirX * cos - dirY * sin`, every frame — and a single `decimal` operand promotes the whole expression to
decimal. A decimal has a bounded exponent, so a chain of reciprocals over promoted values fails outright with *"value
was either too large or too small for a Decimal"*. Exactness is the right default for money and the wrong tool for a
rotation matrix.

⚠ **`Math.Atan2` takes `(y, x)`, in that order** — C#'s own, and the reverse of what the name suggests on first
reading. It is the one that answers "what bearing is this vector" correctly in all four quadrants, which
`Math.Atan(y / x)` cannot: dividing first throws away the sign information and folds two quadrants onto two others.

⚠ **The transcendental functions can differ in the LAST DIGIT between the server and the browser.** `Math.Tan(2.5)`
is `-0.7470222972386602` on the server and `-0.7470222972386603` in the browser, and `Math.Atan2(0.7, 0.8)` differs
the same way. IEEE 754 fixes `+ - * /` and `Sqrt` to the bit and leaves `Sin`, `Cos`, `Tan`, `Atan2`, `Exp` and
`Log` to each math library. .NET calls the C runtime of the machine it runs on, and a browser uses its own engine's,
so .NET itself does not give one answer for these everywhere. So do not compare a computed angle for equality. Round
it, or compare within a tolerance with `Math.Abs(a - b) < 1e-12`.

### The two constants   {#constants}
`Math.PI` and `Math.E` are **constants, written without parentheses** — the C# spelling. They are `double`, like
everything they feed.

They matter most for the trig family above, which takes radians: an angle is a multiple of π, so before these existed
a degrees-to-radians conversion had to paste the literal `3.141592653589793` at every call site. (This page told you
to do exactly that.)

### The logarithms   {#logarithms}

`Math.Log(x)` is the **natural** log (base e), matching C#. `Math.Log10` and `Math.Log2` are the two bases with
their own methods — prefer them to `Log(x) / Log(10)`, which is one rounding step worse. `Math.Exp` is `Log`'s
inverse, so `Math.Log(Math.Exp(x))` is `x` to within floating-point tolerance.

⚠ **`Math.Log(value, newBase)` takes the VALUE first** — `Math.Log(8, 2)` is `3`, not `⅓`. Both arguments are bare
numbers, so a transposition produces a number rather than an error, and nothing downstream will look wrong.

Every one of these runs on the **client** as well as the server, and lowers into a compiled `on frame` body — a
log-scale axis recomputes one per tick per render, so a round trip for it would be absurd.

⚑ **Comparing these in a test needs `Assert.Equal(expected, actual, precision)`** — the third argument is the number
of decimal places both sides are rounded to. `Math.Sin(Math.PI)` is not exactly 0 and `Math.Log(Math.Exp(2))` is not
exactly 2 in any language, because π and e are not representable; the tolerance is not sloppiness, it is the only
correct way to assert a transcendental.

⚠ **This changed.** These used to return a `decimal` whatever you passed. It was a bug rather than a policy:
the type system already declared the rules above and only the runtimes disagreed. If you were relying on
`Math.Floor(someInt)` handing back a decimal, it now hands back a double of the same value.

**Why it mattered.** A `decimal` operand promotes its whole expression, so a single `Math.Abs` inside otherwise
`double` arithmetic quietly turned the rest of the calculation into decimal — and a decimal has a bounded
exponent, so a chain of reciprocals eventually failed with *"value was either too large or too small for a
Decimal"*. Exactness is what you want for money and the wrong tool for geometry.

### Round, and `MidpointRounding`   {#round}
`Math.Round(x)` and `Math.Round(x, digits)` break a tie **to the even neighbour**, exactly as C# does — `2.5` is `2`,
`3.5` is `4`, `-2.5` is `-2`, and `Math.Round(2.345m, 2)` is `2.34` — on a decimal and a double alike, in a server
function, in the browser and inside a query.

For money that should round a half up, say so, as you would in C#:
`Math.Round(amount, 2, MidpointRounding.AwayFromZero)` makes `2.345m` into `2.35m`.

Pass a `MidpointRounding` to say which rule you mean — the same enum, members and spelling as C#, with no `using`:

| mode | `2.5` | `3.5` | `-2.5` | `2.7` | `-2.1` |
|---|---|---|---|---|---|
| `MidpointRounding.ToEven` — banker's rounding, the default | `2` | `4` | `-2` | `3` | `-2` |
| `MidpointRounding.AwayFromZero` — a half rounds up in size | `3` | `4` | `-3` | `3` | `-2` |
| `MidpointRounding.ToZero` — truncate | `2` | `3` | `-2` | `2` | `-2` |
| `MidpointRounding.ToNegativeInfinity` — floor | `2` | `3` | `-3` | `2` | `-3` |
| `MidpointRounding.ToPositiveInfinity` — ceiling | `3` | `4` | `-2` | `3` | `-2` |

Every mode is honoured wherever the call runs: in a server function, in the browser and inside a query (where it becomes
SQL) — a query sent to an external data source included: its plan carries the mode, so the Databricks and Snowflake
packages round half to even for a bare `Math.Round`, as C# does, and the directed modes as their floor, ceiling and
truncation. A source whose package does not list a mode (`Math.Round:ToEven` and friends in its `Functions`) is not
sent it; the compiler refuses the query there, naming `MidpointRounding.AwayFromZero` — the rule every source that
rounds takes. (Snowflake rounds half to even only a fixed-point number, so a `double` there needs `AwayFromZero`.)

⚠ A double rounds its BINARY value, as it does in C#: `Math.Round(2.675, 2, MidpointRounding.AwayFromZero)` is
`2.67`, because `2.675` is not exactly representable and sits just below the midpoint. For money, round a `decimal`.

### Abs, Sign, and Truncate   {#abs-sign-truncate}
`Math.Abs(x)` is the magnitude; `Math.Sign(x)` is `-1`/`0`/`1`. `Math.Truncate(x)` drops the fraction
**toward zero**, so `Math.Truncate(-2.9)` is `-2` — this is the difference from `Math.Floor`, which goes
toward negative infinity and gives `-3`. For a value already positive the two agree.

### Min, Max, and Clamp   {#min-max-clamp}
`Math.Min`/`Math.Max` return the smaller/larger of two numbers. `Math.Clamp(x, min, max)` pins `x` into the
`[min, max]` range — below `min` it returns `min`, above `max` it returns `max`. If you invert the bounds so
`min > max`, `Math.Clamp` **throws**: there is no range to clamp into, and returning a silent wrong value
would be worse. (When two values are numerically equal but differ in scale — `1.5` vs `1.50` — which one
`Min`/`Max` returns is fixed but rarely matters; use a format specifier if the displayed scale is important.)

### Pow and Sqrt are doubles, and are not exact   {#pow-sqrt}
`Math.Pow(x, y)` and `Math.Sqrt(x)` have **no exact decimal form** for most inputs — C# gives them no decimal
overload at all — so they are computed in binary floating point and **stay** there. `Math.Sqrt(2)` is
`1.4142135623730951`, the full float64 answer, identical in the browser and on the server.

Do not use them where you need the last cent to be provably right: a square root of a price is not a price.
Convert deliberately if you need to come back to money.

These are pure functions of their arguments, so they run **in the browser** with no round trip where a UI
action needs them ([Crossing the network](https://osysharp.com/reference/function/execution-side/)).

## Examples       {#examples}
```osy title="a bounded, rounded score" test app=math
// Clamp a raw score into range, then keep two decimals for display.
decimal Score(decimal raw) {
  return Math.Round(Math.Clamp(raw, 0m, 100m), 2);
}
```

```osy title="the exact answers, pinned" run app=math
[Test]
void Math_answers() {
  Assert.Equal(87.35m, Score(87.347m));
  Assert.Equal(100m, Score(140m));            // clamped to the ceiling
  Assert.Equal(0m, Score(-5m));               // clamped to the floor

  Assert.Equal(5, Math.Abs(-5));              // int in, INT out — the widest argument wins
  Assert.Equal(2.50m, Math.Abs(-2.50m));      // decimal in, decimal out — scale kept
  Assert.Equal(1, Math.Sign(2.5m));           // Sign returns an int
  Assert.Equal(0, Math.Sign(0m));
  Assert.Equal(-1, Math.Sign(-0.0001m));

  Assert.Equal(2m, Math.Round(2.5m));                                       // half to even, by default — as in C#
  Assert.Equal(4m, Math.Round(3.5m));
  Assert.Equal(2.34m, Math.Round(2.345m, 2));
  Assert.Equal(3m, Math.Round(2.5m, MidpointRounding.AwayFromZero));        // a half up, when you say so
  Assert.Equal(2.35m, Math.Round(2.345m, 2, MidpointRounding.AwayFromZero));
  Assert.Equal(-2m, Math.Round(-2.7m, 0, MidpointRounding.ToZero));
  Assert.Equal(-3m, Math.Round(-2.1m, 0, MidpointRounding.ToNegativeInfinity));
  Assert.Equal(3m, Math.Round(2.1m, 0, MidpointRounding.ToPositiveInfinity));
  Assert.Equal(2.0, Math.Round(2.5));                                      // a double rounds the same way
  Assert.Equal(3.0, Math.Round(2.5, MidpointRounding.AwayFromZero));        // …and honours the mode too

  Assert.Equal(-2m, Math.Truncate(-2.9m));    // toward zero…
  Assert.Equal(-3m, Math.Floor(-2.9m));       // …unlike Floor

  Assert.Equal(3, Math.Min(3, 7));
  Assert.Equal(7, Math.Max(3, 7));
  Assert.Equal(3, Math.Clamp(5, 1, 3));

  Assert.Equal(1024.0, Math.Pow(2, 10));      // Pow and Sqrt are DOUBLES, always
  Assert.Equal(4.0, Math.Sqrt(16));

  Assert.Equal(3.0, Math.Log10(1000));        // the logs are doubles too
  Assert.Equal(3.0, Math.Log2(8));
  Assert.Equal(3.0, Math.Log(8, 2));          // VALUE first — Log(2, 8) would be 0.333…
  Assert.Equal(1.0, Math.Exp(0));

  // The constants — no parentheses.
  Assert.Equal(3.141592653589793, Math.PI);
  Assert.Equal(2.718281828459045, Math.E);

  // …and the functions, which are never EXACTLY their mathematical answer: the third argument is the number of
  // decimal places both sides are rounded to before comparing.
  Assert.Equal(0.0, Math.Sin(Math.PI), 12);
  Assert.Equal(2.0, Math.Log(Math.Exp(2)), 12);
}
```

### Can I pass a string to `Math.*`?   {#string-arguments}
`Math.*` will coerce a **string** argument, and it reads it with the platform's one numeric-coercion grammar —
the same one [Convert](https://osysharp.com/reference/function/convert/) documents, including its `0` for text it cannot read. `Math.Abs("-1,000")` is
`1000`; `Math.Abs("(5)")` is `0`, not `5`. If the value came from a form field, convert it deliberately first
(`Convert.ToDecimal`) so the failure is visible where it happens rather than inside the arithmetic.

## See also       {#see-also}
- [decimal](https://osysharp.com/reference/types/decimal/) — what a decimal argument buys you, and where exactness matters
- [format specifiers](https://osysharp.com/reference/function/format-specifiers/) — rounding for DISPLAY (`F2`, `N0`), distinct from `Math.Round`
- [Convert](https://osysharp.com/reference/function/convert/) — coercing between numeric types
