3.2 Numbers
Pyret numbers are of two kinds: exact numbers and rough numbers (“roughnums”). Both are to base ten; real; and finite.
Exact numbers are arbitrarily precise rational numbers: these include integers and rational fractions. For integers whose magnitude is less than (num-expt(2, 53) - 1), Pyret internally uses JavaScript fixnums, in order to optimize basic arithmetic.
Roughnums are numbers that are necessarily or deliberately imprecise. These correspond to the same set of values covered by JavaScript fixnums (a.k.a. doubles), and thus cover a large but limited range (magnitude less than 1.7976931348623157e308).
Operations on exact numbers typically return exacts. However, if the operation can yield irrationals, and it is not possible to determine that a particular result is definitely rational, that result is returned as a roughnum. Thus, trigonometric functions on exact numbers typically yield roughnum answers, except for well-known edge cases such as the sine or cosine of zero. Fractional powers of rationals are usually roughnum, except for small roots where it can be ascertained that an exact root is possible.
Operations that are non-casting and with at least one argument that is roughnum automatically coerce the result to be a roughnum. This is known as roughnum contagion.
Exact numbers allow the usual comparison predicates. Roughnums do too, with the significant exception that trying to compare roughnums for equality throws an error.
An operation whose numerical result is not determinate or finite throws an error, with the message signaling either an overflow or some more specific problem.
3.2.1 Number Annotations
3.2.2 Number Literals
Literal exact rationals can be integers, fractions represented with a solidus, or decimals, with an optional exponent. In the following, the numerals on the same line denote the same Pyret number.
42 +42 -42 22/7 -22/7 2.718281828 +2.718281828 -2.718281828 1/2 0.5 6.022e23 +6.022e23 6.022e+23 +6.022e+23 -6.022e23 -6.022e+23 -6.022e-23
Exact numbers are of arbitrary precision.
Literal roughnums are represented with a leading tilde. They are either integers or decimals, with an optional exponent.
~42 ~+42 ~-42 ~2.718281828 ~+2.718281828 ~-2.718281828 ~6.022e23 ~+6.022e23 ~6.022e+23 ~+6.022e+23 ~-6.022e23 ~-6.022e+23 ~-6.022e-23
Roughnums cannot be made arbitrarily precise. The absolute value ranges between 0 and 1.7976931348623157e+308 (JS’s Number.MAX_VALUE) with a granularity of 5e-324 (JS’s Number.MIN_VALUE).
3.2.3 Number Functions
If both arguments are exact, returns a boolean. If either argument is roughnum, raises an error.
check: num-equal(2, 2) is true num-equal(2, 3) is false num-equal(1/2, 0.5) is true num-equal(1 / 2, 0.5) is true num-equal(1/3, 0.33) is false num-equal(1/3, ~0.33) raises "roughnums cannot be compared for equality" end
Throws an error on non-numeric arguments, which can be a useful alternative to equal-always in situations where the program shouldn’t compare non-numbers.
Returns the larger of the two arguments.
check: num-max(1, 2) is 2 num-max(2, ~3) is ~3 num-max(4, ~4) is ~4 num-max(~4, 4) is 4 num-max(-1.1, 0) is 0 end
Returns the smaller of the two arguments.
check: num-min(1, 2) is 1 num-min(2, ~3) is 2 num-min(4, ~4) is ~4 num-min(~4, 4) is 4 num-min(-1.1, 0) is -1.1 end
Returns the absolute value of the argument. The result is exact only if the argument is.
check: num-abs(2) is 2 num-abs(-2.1) is 2.1 num-abs(~2) is ~2 num-abs(~-2.1) is ~2.1 end
Returns the sine of the argument, usually as a roughnum. If the argument is exact 0, the result is exact 0 too.
check: num-sin(0) is 0 num-sin(1) is%(within-abs(0.01)) 0.84 end
Returns the cosine of the argument, usually as a roughnum. If the argument is exact 0, the result is exact 1.
check: num-cos(0) is 1 num-cos(1) is%(within-abs(0.01)) 0.54 end
Returns the tangent of the argument, usually as a roughnum. However, if the argument is exact 0, the result is exact 1.
check: num-tan(0) is 0 num-tan(1) is%(within-abs(0.01)) 1.56 end
Returns the arc sine of the argument, usually as a roughnum. However, if the argument is exact 0, the result is exact 0.
check: num-asin(0) is 0 num-asin(0.84) is%(within-abs(0.01)) 1 end
Returns the arc cosine of the argument, usually as a roughnum. However, if the argumet is exact 1, the result is exact 0.
check: num-acos(1) is 0 num-acos(0.54) is%(within-abs(0.01)) 1 end
Returns the arc tangent of the argument, usually as a roughnum. However, if the argumet is exact 0, the result is exact 0.
check: num-atan(0) is 0 num-atan(1.56) is%(within-abs(0.01)) 1 end
Returns the modulo of the first argument with respect to the second.
check: num-modulo(5, 2) is 1 num-modulo(-5, 2) is 1 num-modulo(-5, -2) is -1 num-modulo(7, 3) is 1 num-modulo(0, 5) is 0 num-modulo(-7, 3) is 2 end
It is useful for calculating if one number is a multiple of another, by checking for a zero remainder.
fun is-odd(n :: Number) -> Boolean: num-modulo(n, 2) == 0 where: is-odd(6) is true is-odd(3) is false end
Returns the integer part of its argument by cutting off any decimal part. Does not do any rounding.
check: num-truncate(3.14) is 3 num-truncate(-3.14) is -3 num-truncate(~3.14) is ~3 num-truncate(~-3.14) is ~-3 end
Returns the square root. If the argument is exact and a perfect square, the result is exact.
check: num-sqrt(4) is 2 num-sqrt(5) is%(within-abs(0.001)) ~2.236 num-sqrt(~4) is ~2 num-sqrt(~5) is%(within-abs(0.001)) ~2.236 num-sqrt(0.04) is 1/5 num-sqrt(-1) raises "negative argument" end
Returns the square.
check: num-sqr(4) is 16 num-sqr(5) is 25 num-sqr(-4) is 16 num-sqr(~4) is ~16 num-sqr(0.04) is 1/625 end
Returns the smallest integer greater than or equal to the argument. The result is exact even if the argument is rough.
check: num-ceiling(4.2) is 5 num-ceiling(-4.2) is -4 end
Returns the largest integer less than or equal to the argument. The result is exact even if the argument is rough.
check: num-floor(4.2) is 4 num-floor(-4.2) is -5 end
Returns the closest integer to the argument. The result is exact even if the argument is rough.
check: num-round(4.2) is 4 num-round(4.8) is 5 num-round(-4.2) is -4 num-round(-4.8) is -5 end
If the argument is midway between integers, returns the integer away from zero.
check: num-round(3.5) is 4 num-round(2.5) is 3 end
Similar to num-round, except that if the argument is midway between integers, returns the even integer.
check: num-round-even(3.5) is 4 num-round-even(2.5) is 2 end
Returns the natural logarithm (ln) of the argument, usually as a roughnum. However, if the argument is exact 1, the result is exact 0. If the argument is non-positive, an error is thrown.
check: num-log(1) is 0 num-log(0) raises "non-positive argument" num-log(-1) raises "non-positive argument" num-log(2.718281828) is%(within-abs(0.01)) 1 num-log(10) is%(within-abs(0.1)) 2.3 end
Returns e raised to the argument, usually as a roughnum. However, if the argument is exact 0, the result is exact 1.
check: num-exp(-1) is%(within-abs(0.0001)) (1 / num-exp(1)) num-exp(0) is 1 num-exp(1) is%(within-abs(0.0001)) 2.718281828 num-exp(3) is%(within-abs(0.0001)) num-expt(2.718281828, 3) num-exp(710) raises "overflow" end
Returns the first argument raised to the second argument. The result is exact if both arguments are exact, except that an error is thrown if the first argument is zero and the second is negative. Furthermore, if the first argument is exact 0 or 1, or the second argument is exact 0, then the result is exact even if the other argument is rough.
check: num-expt(3, 0) is 1 num-expt(1, 3) is 1 num-expt(0, 0) is 1 num-expt(0, 3) is 0 num-expt(0, -3) raises "division by zero" num-expt(2, 3) is 8 num-expt(2, -3) is 1/8 end
Same as num-exact. first argumetn is zero and the second is negative.
Given an exact num, returns the roughnum version of it. Given a roughnum, returns it directly.
check: num-is-roughnum(num-to-roughnum(3.14)) is true num-is-roughnum(num-to-roughnum(~3.14)) is true end
Returns true if argument is an exact integer.
check: num-is-integer(2) is true num-is-integer(1/2) is false num-is-integer(1.609) is false num-is-integer(~2) is false end
Returns true if argument is an exact rational.
check: num-is-rational(2) is true num-is-rational(1/2) is true num-is-rational(1.609) is true num-is-rational(~2) is false end
check: num-is-roughnum(2) is false num-is-roughnum(1/2) is false num-is-roughnum(1.609) is false num-is-roughnum(~2) is true end
Returns true if argument is greater than zero.
check: num-is-positive(~-2) is false num-is-positive(-2) is false num-is-positive(0) is false num-is-positive(-0) is false num-is-positive(2) is true num-is-positive(~2) is true end
Returns true if argument is less than zero.
check: num-is-negative(~-2) is true num-is-negative(-2) is true num-is-negative(0) is false num-is-negative(-0) is false num-is-negative(2) is false num-is-negative(~2) is false end
check: num-is-non-positive(~-2) is true num-is-non-positive(-2) is true num-is-non-positive(0) is true num-is-non-positive(-0) is true num-is-non-positive(2) is false num-is-non-positive(~2) is false end
Returns true if argument is greater than or equal to zero.
check: num-is-non-negative(~-2) is false num-is-non-negative(-2) is false num-is-non-negative(0) is true num-is-non-negative(-0) is true num-is-non-negative(2) is true num-is-non-negative(~2) is true end
Returns a string representing a literal form of the number.
check: num-to-string(2.5) is "5/2" num-to-string(2) is "2" num-to-string(2/3) is "2/3" num-to-string(~2.718) is "~2.718" num-to-string(~6.022e23) is "~6.022e+23" end
Converts the number to a string, providing digits precision in the output. If digits is positive, provides that many digits to the right of the decimal point (including adding zeroes beyond the actual precision of the number). If digits is negative, rounds that many positions to the left of the decimal, replacing them with zeroes.
Note that num-to-string-digits is only for formatting, and its output’s apparent precision may be unrelated to the actual precision of the input number, which may have been an approximation, or unrepresentable in decimal.
check: num-to-string-digits(2/3, 3) is "0.667" num-to-string-digits(-2/3, 3) is "-0.667" num-to-string-digits(5, 2) is "5.00" num-to-string-digits(5, 0) is "5" num-to-string-digits(555, -2) is "600" end
Returns a predicate that checks if the difference of its two arguments is less than tol.
check: 1 is%(num-within-abs(0.1)) 1 1 is%(num-within-abs(0.1)) ~1 ~3 is%(num-within-abs(0.1)) ~3 ~2 is-not%(num-within-abs(0.1)) ~3 ~2 is%(num-within-abs(1.1)) ~3 ~2 is-not%(num-within-abs(~1)) ~3 2 is-not%(num-within-abs(1)) ~3 5 is%(num-within-abs(4)) 3 num-within-abs(-0.1)(1, 1.05) raises "negative tolerance" end
Returns a predicate that checks that its first number argument is no more than the fraction tol off from its second argument.
This function is a.k.a. num-within.
check: 100000 is%(num-within-rel(0.1)) 95000 100000 is-not%(num-within-rel(0.1)) 85000 end
These comparison functions compare both numbers and structures, and are documented in Bounded Equalities.
3.2.4 Random Numbers
Returns a pseudo-random positive integer from 0 to max - 1.
check: fun between(min, max): lam(v): (v >= min) and (v <= max) end end for each(i from range(0, 100)): n = num-random(i) print(n) n satisfies between(0, i - 1) end end
Sets the random seed. Setting the seed to a particular number makes all future uses of random produce the same sequence of numbers. Useful for testing and debugging functions that have random behavior.
check: num-random-seed(0) n = num-random(1000) n2 = num-random(1000) n is-not n2 num-random-seed(0) n3 = num-random(1000) n3 is n n4 = num-random(1000) n4 is n2 end
3.2.5 Other Number Functions
A few other number functions are useful in limited cases that don’t come up in most programs.
Returns true if the argument is represented directly as a primitive JavaScript number (i.e., JavaScript double).
check: num-is-fixnum(10) is true num-is-fixnum(~10) is false num-is-fixnum(1000000000000000) is true num-is-fixnum(10000000000000000) is false num-is-fixnum(1.5) is false end
N.B. Pyret representes exact rationals that are non-integers as tuples, and hence even small rationals such as 1.5 are considered non-fixnum, although they could be represented as JavaScript doubles.