package tablecloth-native

  1. Overview
  2. Docs

Module Tablecloth.FloatSource

A module for working with floating-point numbers. Valid syntax for floats includes:

  0.
  0.0
  42.
  42.0
  3.14
  0.1234
  123_456.123_456
  6.022e23   (* = (6.022 * 10^23) *)
  6.022e+23  (* = (6.022 * 10^23) *)
  1.602e−19  (* = (1.602 * 10^-19) *)
  1e3        (* = (1 * 10 ** 3) = 1000. *)

Without opening this module you can use the . suffixed operators e.g

 1. +. 2. /. 0.25 *. 2. = 17. 

But by opening this module locally you can use the un-suffixed operators

Float.((10.0 - 1.5 / 0.5) ** 3.0) = 2401.0

Historical Note: The particular details of floats (e.g. NaN) are specified by IEEE 754 which is literally hard-coded into almost all CPUs in the world.

Sourcetype t = float

Constants

Sourceval zero : t

The literal 0.0 as a named value

Sourceval one : t

The literal 1.0 as a named value

Sourceval nan : t

NaN as a named value. NaN stands for not a number.

Note comparing values with Float.nan will always return false even if the value you are comparing against is also NaN.

e.g

let isNotANmber x = Float.(x = nan) in
isNotANumber nan = false

For detecting Nan you should use Float.isNaN

Sourceval infinity : t

Positive infinity

Float.log ~base:10.0 0.0 = Float.infinity
Sourceval negativeInfinity : t

Negative infinity, see Float.infinity

Sourceval negative_infinity : t
Sourceval e : t

An approximation of Euler's number.

Sourceval pi : t

An approximation of pi.

Basic arithmetic and operators

Sourceval add : t -> t -> t

Addition for floating point numbers.

Float.add 3.14 3.14 = 6.28
Float.(3.14 + 3.14 = 6.28)

Although ints and floats support many of the same basic operations such as addition and subtraction you cannot add an int and a float directly which means you need to use functions like Int.toFloat or Float.roundToInt to convert both values to the same type.

So if you needed to add a List.length to a float for some reason, you could:

Float.add 3.14 (Int.toFloat (List.length [1,2,3])) = 6.14

or

Float.roundToInt 3.14 + List.length [1,2,3] = 6

Languages like Java and JavaScript automatically convert int values to float values when you mix and match. This can make it difficult to be sure exactly what type of number you are dealing with and cause unexpected behavior.

OCaml has opted for a design that makes all conversions explicit.

Sourceval (+) : t -> t -> t
Sourceval subtract : t -> t -> t

Subtract numbers

Float.subtract 4.0 3.0 = 1.0

Alternatively the - operator can be used:

Float.(4.0 - 3.0) = 1.0
Sourceval (-) : t -> t -> t
Sourceval multiply : t -> t -> t

Multiply numbers like

Float.multiply 2.0 7.0 = 14.0

Alternatively the operator * can be used:

Float.(2.0 * 7.0) = 14.0
Sourceval (*) : t -> t -> t
Sourceval divide : t -> by:t -> t

Floating-point division:

Float.divide 3.14 ~by:2.0 = 1.57

Alternatively the / operator can be used:

Float.(3.14 / 2.0) = 1.57
Sourceval (/) : t -> t -> t
Sourceval power : base:t -> exponent:t -> t

Exponentiation, takes the base first, then the exponent.

Float.power ~base:7.0 ~exponent:3.0 = 343.0

Alternatively the ** operator can be used:

Float.(7.0 ** 3.0) = 343.0
Sourceval (**) : t -> t -> t
Sourceval negate : t -> t

Flips the 'sign' of a float so that positive floats become negative and negative integers become positive. Zero stays as it is.

Float.negate 8 = (-8)
Float.negate (-7) = 7
Float.negate 0 = 0

Alternatively an operator is available:

Float.(~- 4.0) = (-4.0)
Sourceval (~-) : t -> t
Sourceval absolute : t -> t

Get the absolute value of a number.

Float.absolute 8. = 8.
Float.absolute (-7) = 7
Float.absolute 0 = 0
Sourceval maximum : t -> t -> t

Returns the larger of two floats, if both arguments are equal, returns the first argument

Float.maximum 7. 9. = 9.
Float.maximum (-4.) (-1.) = (-1.)

If either (or both) of the arguments are NaN, returns NaN

Float.(isNaN (maximum 7. nan) = true
Sourceval minimum : t -> t -> t

Returns the smaller of two floats, if both arguments are equal, returns the first argument

Float.minimum 7.0 9.0 = 7.0
Float.minimum (-4.0) (-1.0) = (-4.0)

If either (or both) of the arguments are NaN, returns NaN

Float.(isNaN (minimum 7. nan) = true
Sourceval clamp : t -> lower:t -> upper:t -> t

Clamps n within the inclusive lower and upper bounds.

Float.clamp ~lower:0. ~upper:8. 5. = 5.
Float.clamp ~lower:0. ~upper:8. 9. = 8.
Float.clamp ~lower:(-10.) ~upper:(-5.) 5. = -5.

Throws an Invalid_argument exception if lower > upper

Fancier math

Sourceval squareRoot : t -> t

Take the square root of a number.

Float.squareRoot 4.0 = 2.0
Float.squareRoot 9.0 = 3.0

squareRoot returns NaN when its argument is negative. See Float.nan for more.

Sourceval square_root : t -> t
Sourceval log : t -> base:t -> t

Calculate the logarithm of a number with a given base.

Float.log ~base:10. 100. = 2.
Float.log ~base:2. 256. = 8.

Checks

Sourceval isNaN : t -> bool

Determine whether a float is an undefined or unrepresentable number.

Float.isNaN (0.0 / 0.0) = true
Float.(isNaN (squareRoot (-1.0)) = true
Float.isNaN (1.0 / 0.0) = false  (* Float.infinity {b is} a number *)
Float.isNaN 1. = false

Note this function is more useful than it might seem since NaN does not equal Nan:

Float.(nan = nan) = false
Sourceval is_nan : t -> bool
Sourceval isFinite : t -> bool

Determine whether a float is finite number. True for any float except Infinity, -Infinity or NaN

Float.isFinite (0. / 0.) = false
Float.(isFinite (squareRoot (-1.)) = false
Float.isFinite (1. / 0.) = false
Float.isFinite 1. = true
Float.(isFinite nan) = false

Notice that NaN is not finite!

For a float n to be finite implies that Float.(not (isInfinite n || isNaN n)) evaluates to true.

Sourceval is_finite : t -> bool
Sourceval isInfinite : t -> bool

Determine whether a float is positive or negative infinity.

Float.isInfinite (0. / 0.) = false
Float.(isInfinite (squareRoot (-1.)) = false
Float.isInfinite (1. / 0.) = true
Float.isInfinite 1. = false
Float.(isInfinite nan) = false

Notice that NaN is not infinite!

For a float n to be finite implies that Float.(not (isInfinite n || isNaN n)) evaluates to true.

Sourceval is_infinite : t -> bool
Sourceval inRange : t -> lower:t -> upper:t -> bool

Checks if n is between lower and up to, but not including, upper. If lower is not specified, it's set to to 0.0.

Float.inRange ~lower:2. ~upper:4. 3. = true
Float.inRange ~lower:1. ~upper:2. 2. = false
Float.inRange ~lower:5.2 ~upper:7.9 9.6 = false

Throws an Invalid_argument exception if lower > upper

Sourceval in_range : t -> lower:t -> upper:t -> bool

Angles

Sourceval hypotenuse : t -> t -> t

hypotenuse x y returns the length of the hypotenuse of a right-angled triangle with sides of length x and y, or, equivalently, the distance of the point (x, y) to (0, 0).

Float.hypotenuse 3. 4. = 5.
Sourceval degrees : t -> t

Converts an angle in degrees to Float.radians.

Float.degrees 180. = v
Sourceval radians : t -> t

Convert a Float.t to radians.

Float.(radians pi) = 3.141592653589793

Note This function doesn't actually do anything to its argument, but can be useful to indicate intent when inter-mixing angles of different units within the same function.

Sourceval turns : t -> t

Convert an angle in turns into Float.radians.

One turn is equal to 360°.

Float.(turns (1. / 2.)) = pi
Float.(turns 1. = degrees 360.)

Polar coordinates

Sourceval fromPolar : (float * float) -> float * float

Convert polar coordinates (r, θ) to Cartesian coordinates (x,y).

Float.(fromPolar (squareRoot 2., degrees 45.)) = (1., 1.)
Sourceval from_polar : (float * float) -> float * float
Sourceval toPolar : (float * float) -> float * float

Convert Cartesian coordinates (x,y) to polar coordinates (r, θ).

Float.toPolar (3.0, 4.0) = (5.0, 0.9272952180016122)
Float.toPolar (5.0, 12.0) = (13.0, 1.1760052070951352)
Sourceval to_polar : (float * float) -> float * float
Sourceval cos : t -> t

Figure out the cosine given an angle in radians.

Float.(cos (degrees 60.)) = 0.5000000000000001
Float.(cos (radians (pi / 3.))) = 0.5000000000000001
Sourceval acos : t -> t

Figure out the arccosine for adjacent / hypotenuse in radians:

Float.(acos (radians 1.0 / 2.0)) = Float.radians 1.0471975511965979 (* 60° or pi/3 radians *)
Sourceval sin : t -> t

Figure out the sine given an angle in radians.

Float.(sin (degrees 30.)) = 0.49999999999999994
Float.(sin (radians (pi / 6.)) = 0.49999999999999994
Sourceval asin : t -> t

Figure out the arcsine for opposite / hypotenuse in radians:

Float.(asin (1.0 / 2.0)) = 0.5235987755982989 (* 30° or pi / 6 radians *)
Sourceval tan : t -> t

Figure out the tangent given an angle in radians.

Float.(tan (degrees 45.)) = 0.9999999999999999
Float.(tan (radians (pi / 4.)) = 0.9999999999999999
Float.(tan (pi / 4.)) = 0.9999999999999999
Sourceval atan : t -> t

This helps you find the angle (in radians) to an (x, y) coordinate, but in a way that is rarely useful in programming.

You probably want atan2 instead!

This version takes y / x as its argument, so there is no way to know whether the negative signs comes from the y or x value. So as we go counter-clockwise around the origin from point (1, 1) to (1, -1) to (-1,-1) to (-1,1) we do not get angles that go in the full circle:

Float.atan (1. /. 1.) = 0.7853981633974483  (* 45° or pi/4 radians *)
Float.atan (1. /. -1.) = -0.7853981633974483  (* 315° or 7 * pi / 4 radians *)
Float.atan (-1. /. -1.) = 0.7853981633974483 (* 45° or pi/4 radians *)
Float.atan (-1. /.  1.) = -0.7853981633974483 (* 315° or 7 * pi/4 radians *)

Notice that everything is between pi / 2 and -pi/2. That is pretty useless for figuring out angles in any sort of visualization, so again, check out Float.atan2 instead!

Sourceval atan2 : y:t -> x:t -> t

This helps you find the angle (in radians) to an (x, y) coordinate. So rather than saying Float.(atan (y / x)) you can Float.atan2 ~y ~x and you can get a full range of angles:

Float.atan2 ~y:1. ~x:1. = 0.7853981633974483  (* 45° or pi/4 radians *)
Float.atan2 ~y:1. ~x:(-1.) = 2.3561944901923449  (* 135° or 3 * pi/4 radians *)
Float.atan2 ~y:(-1.) ~x:(-1.) = -(2.3561944901923449) (* 225° or 5 * pi/4 radians *)
Float.atan2 ~y:(-1.) ~x:1.) = -(0.7853981633974483) (* 315° or 7 * pi/4 radians *)

Conversion

Sourcetype direction = [
  1. | `Zero
  2. | `AwayFromZero
  3. | `Up
  4. | `Down
  5. | `Closest of [ `Zero | `AwayFromZero | `Up | `Down | `ToEven ]
]
Sourceval round : ?direction:direction -> t -> t

Round a number, by default to the to the closest int with halves rounded `Up (towards positive infinity)

Float.round 1.2 = 1.0
Float.round 1.5 = 2.0
Float.round 1.8 = 2.0
Float.round -1.2 = -1.0
Float.round -1.5 = -1.0
Float.round -1.8 = -2.0

Other rounding strategies are available by using the optional ~direction label.

Towards zero

Float.round ~direction:`Zero 1.2 = 1.0
Float.round ~direction:`Zero 1.5 = 1.0
Float.round ~direction:`Zero 1.8 = 1.0
Float.round ~direction:`Zero (-1.2) = -1.0
Float.round ~direction:`Zero (-1.5) = -1.0
Float.round ~direction:`Zero (-1.8) = -1.0

Away from zero

Float.round ~direction:`AwayFromZero 1.2 = 1.0
Float.round ~direction:`AwayFromZero 1.5 = 1.0
Float.round ~direction:`AwayFromZero 1.8 = 1.0
Float.round ~direction:`AwayFromZero (-1.2) = -1.0
Float.round ~direction:`AwayFromZero (-1.5) = -1.0
Float.round ~direction:`AwayFromZero (-1.8) = -1.0

Towards infinity

This is also known as Float.ceiling

Float.round ~direction:`Up 1.2 = 1.0
Float.round ~direction:`Up 1.5 = 1.0
Float.round ~direction:`Up 1.8 = 1.0
Float.round ~direction:`Up (-1.2) = -1.0
Float.round ~direction:`Up (-1.5) = -1.0
Float.round ~direction:`Up (-1.8) = -1.0

Towards negative infinity

This is also known as Float.floor

List.map  ~f:(Float.round ~direction:`Down) [-1.8; -1.5; -1.2; 1.2; 1.5; 1.8] = [-2.0; -2.0; -2.0; 1.0 1.0 1.0]

To the closest integer

Rounding a number x to the closest integer requires some tie-breaking for when the fraction part of x is exactly 0.5.

Halves rounded towards zero

List.map  ~f:(Float.round ~direction:(`Closest `AwayFromZero)) [-1.8; -1.5; -1.2; 1.2; 1.5; 1.8] = [-2.0; -1.0; -1.0; 1.0 1.0 2.0]

Halves rounded away from zero

This method is often known as commercial rounding

List.map  ~f:(Float.round ~direction:(`Closest `AwayFromZero)) [-1.8; -1.5; -1.2; 1.2; 1.5; 1.8] = [-2.0; -2.0; -1.0; 1.0 2.0 2.0]

Halves rounded down

List.map  ~f:(Float.round ~direction:(`Closest `Down)) [-1.8; -1.5; -1.2; 1.2; 1.5; 1.8] = [-2.0; -2.0; -1.0; 1.0 1.0 2.0]

Halves rounded up

This is the default.

Float.round 1.5 is the same as Float.round ~direction:(`Closest `Up) 1.5

Halves rounded towards the closest even number

This tie-breaking rule is the default rounding mode using in

Float.round ~direction:(`Closest `ToEven) -1.5 = -2.0
Float.round ~direction:(`Closest `ToEven) -2.5 = -2.0
Sourceval floor : t -> t

Floor function, equivalent to Float.round ~direction:`Down.

Float.floor 1.2 = 1.0
Float.floor 1.5 = 1.0
Float.floor 1.8 = 1.0
Float.floor -1.2 = -2.0
Float.floor -1.5 = -2.0
Float.floor -1.8 = -2.0
Sourceval ceiling : t -> t

Ceiling function, equivalent to Float.round ~direction:`Up.

Float.ceiling 1.2 = 2.0
Float.ceiling 1.5 = 2.0
Float.ceiling 1.8 = 2.0
Float.ceiling -1.2 = (-1.0)
Float.ceiling -1.5 = (-1.0)
Float.ceiling -1.8 = (-1.0)
Sourceval truncate : t -> t

Ceiling function, equivalent to Float.round ~direction:`Zero.

Float.truncate 1.0 = 1
Float.truncate 1.2 = 1
Float.truncate 1.5 = 1
Float.truncate 1.8 = 1
Float.truncate (-1.2) = -1
Float.truncate (-1.5) = -1
Float.truncate (-1.8) = -1
Sourceval fromInt : int -> float

Convert an int to a float

Float.fromInt 5 = 5.0
Float.fromInt 0 = 0.0
Float.fromInt -7 = -7.0
Sourceval from_int : int -> float
Sourceval toInt : t -> int option

Converts a float to an Int by ignoring the decimal portion. See Float.truncate for examples.

Returns None when trying to round a float which can't be represented as an int such as Float.nan or Float.infinity or numbers which are too large or small.

Float.(toInt nan) = None
Float.(toInt infinity) = None

You probably want to use some form of Float.round prior to using this function.

Float.(round 1.6 |> toInt) = Some 2
Sourceval to_int : t -> int option