3.13. kink/NUM

Companion mod for num vals.

3.13.1. type num

A num val is a floating point decimal number.

A num val consists of (Mantissa, Scale). The number represented by the num val is Mantissa * (10 ** Scale).

• Mantissa is a signed integer number which represents the decimal digits of the num val.

• Scale is a signed integer number which represents the negation of the base-10 exponent of the magnitude of the num val.

The runtime system must support at least the range [-(2**63), (2**63) - 1] for Mantissa, and the range [-(2**31), (2**31) - 1] for Scale. Exceeding these limits may result in an exception.

Note: the Kink runtime on the JVM supports an arbitrary range for Mantissa.

Example: positive and negative nums

stdout.print_line(1.230.mantissa.repr)    # => 12300
stdout.print_line(1.230.scale.repr)       # => 3

stdout.print_line((-1.230).mantissa.repr) # => (-12300)
stdout.print_line((-1.230).scale.repr)    # => 3

Negative scales are also possible. Example:

:Num <- 123000.by_scale(-3)
stdout.print_line(Num.repr)           # => (123000.by_scale(-3))
stdout.print_line(Num.mantissa.repr)  # => 123
stdout.print_line(Num.scale.repr)     # => (-3)

Note that there are multiple num val representations for one number. For example, 420 can be represented as (Mantissa=420, Scale=0), (Mantissa=42000, Scale=2) or (Mantissa=42, Scale=-1) and so on. They are treated as equal by == (op_eq) or != (op_ne) operators.

Example:

stdout.print_line((420 == 420.00).repr) # => true

Bit operations take only int nums as operands. Nonnegative int nums are treated as if infinite 0 bits precede. Negative int nums are treated as if they are represented as two's complement, and infinite 1 bits precede.

Example:

stdout.print_line('{%b}'.format(~ 0b_1010 & 0b_1111_1111)) # => 11110101

There are no NaN or infinite num vals. There is no distinction between positive zero and negative zero.

Num.mantissa

Num.mantissa returns the mantissa of the Num.

The result of Num.mantissa is an int num.

Num.scale

Num.scale returns the scale of the Num.

The result of Num.scale is an int num.

Num.precision

Num.precision returns the number of decimal digits of the Num.mantissa.

TODO is the method neeeded?

Num.int?

Num.int? returns whether the Num is an int num.

A num is an int num when the scale is 0.

Num.op_add(Arg_num)

Num.op_add returns the sum of Num and Arg_num.

The scale of the result will be the bigger one of Num.scale and Arg_num.scale.

Example:

:Sum <- 1.234 + 0.12
stdout.print_line(Sum.repr)       # => 1.354
stdout.print_line(Sum.scale.repr) # => 3

Num.op_sub(Arg_num)

Num.op_sub returns the difference of Num and Arg_num.

The scale of the result will be the bigger one of Num.scale and Arg_num.scale.

Example:

:Diff <- 1.234 - 0.12
stdout.print_line(Diff.repr)        # => 1.114
stdout.print_line(Diff.scale.repr)  # => 3

Num.op_mul(Arg_num)

Num.op_mul returns the production of Num and Arg_num.

The scale of the result will be Num.scale + Arg_num.scale.

Example:

:Prod <- 1.2 * 3.45
stdout.print_line(Prod.repr)        # => 4.140
stdout.print_line(Prod.scale.repr)  # => 3

Num.op_div(Divisor)

Num.op_div makes a num_division val of (Dividend=Num, Divisor).

Divisor must be a num, and must not be equal to 0.

Example:

stdout.print_line((10 / 3).floor(3).repr) # => 3.333
stdout.print_line((10 / 3).ceil(3).repr)  # => 3.334

TODO add documentation of num_division val

Num.op_intdiv(Arg_num) Num.op_rem(Arg_num)

Num.op_intdiv and op_rem perform integral division; op_intdiv returns the quotient, and op_rem returns the remainder.

Precondition:

• Arg_num must not be equal to 0 for both op_intdiv and op_rem.

Given nums X and Y (which is not equal to zero), X // Y is Q and X % Y is R, they suffice the following conditions.

• Y * Q + R == X

• Q.scale == 0

• R.scale == X.scale

• R >= 0 && R < Y.abs

The rounding rule in this definition is equivalent to DIV and MOD of R6RS Scheme. See:

http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-14.html#node_sec_11.7.3.1

Example:

:attempt_intdiv <- {(:X :Y)
  :Q = X // Y
  :R = X % Y
  stdout.print_line('{} // {} = {}, remainder: {}'.format(X.repr Y.repr Q.repr R.repr))
}

attempt_intdiv(10 3)
# => 10 // 3 = 3, remainder: 1

attempt_intdiv(10 (-3))
# => 10 // (-3) = (-3), remainder: 1

attempt_intdiv((-10) 3)
# => (-10) // 3 = (-4), remainder: 2

attempt_intdiv((-10) (-3))
# => (-10) // (-3) = 4, remainder: 2

attempt_intdiv(10 1.5)
# => 10 // 1.5 = 6, remainder: 1.0

attempt_intdiv(5.5 3)
# => 5.5 // 3 = 1, remainder: 2.5

attempt_intdiv(5.5 1.5)
# => 5.5 // 1.5 = 3, remainder: 1.0

Num.op_minus

Num.op_minus returns a num val the abs of which is equal to one of Num, and the sign is flipped.

Example:

stdout.print_line((-42).repr)     # => (-42)
stdout.print_line((-(-42)).repr)  # => 42
stdout.print_line((-0).repr)      # => 0

Num.op_or(Arg_num)

Num.op_or does bit OR operation for Num and Arg_num.

Both Num and Arg_num must be int nums.

The result will be an int num.

Num.op_xor(Arg_num)

Num.op_or does bit XOR operation for Num and Arg_num.

Both Num and Arg_num must be int nums.

The result will be an int num.

Num.op_and(Arg_num)

Num.op_or does bit AND operation for Num and Arg_num.

Both Num and Arg_num must be int nums.

The result will be an int num.

Num.op_not

Num.op_not does bit NOT operation for Num.

Num must be an int num.

The result will be an int num.

Num.op_shl(Bit_count)

Num.op_shl does bit SHIFT-LEFT operation for Num.

Precondition:

• Both Num and Bit_count must be int nums.

If Bit_count is positive, Num.op_shl shifts Bit_count bits toward left.

If Bit_count is negative, Num.op_shl shifts (-Bit_count) bits toward right, discarding underflown bits.

If Bit_count is zero, Num.op_shl returns Num.

The result will be an int num.

Num.op_shr(Bit_count)

Num.op_shr does bit SHIFT-RIGHT operation for Num.

Precondition:

• Both Num and Bit_count must be int nums.

If Bit_count is positive, Num.op_shr shifts Bit_count bits toward right, discarding underflown bits.

If Bit_count is negative, Num.op_shr shifts (-Bit_count) bits toward left.

If Bit_count is zero, Num.op_shr returns Num.

The result will be an int num.

Num.by_scale(New_scale)

Num.by_scale returns a num which is equal to Num and the scale of which is changed to New_scale.

New_scale must be an int num, and the number of Num must be able to be represented with New_scale.

Num.round

Calling Num.round is equivalent to calling Num/1.

TODO add documentation of num_division val

Num.abs

Num.abs returns the absolute value of Num.

If Num is nonnegative, Num.abs returns Num.

If Num is negative, Num.abs returns (-Num).

Num.op_eq(Arg_num) Num.op_ne(Arg_num)

Num.op_eq and Num.op_ne return whether or not the number represented by two num vals are equal, irrespective of scales of them.

Arg_num must be a num val.

Example:

stdout.print_line((42 == 42.000).repr)  # => true
stdout.print_line((42 != 42.000).repr)  # => false

stdout.print_line((42 == 24).repr)  # => false
stdout.print_line((42 != 24).repr)  # => true

Num.op_lt(Arg_num) Num.op_le(Arg_num) Num.op_gt(Arg_num) Num.op_ge(Arg_num)

Num.op_lt, Num.op_le, Num.op_gt and Num.op_ge compare two num vals numerically, then return true or false.

Arg_num must be a num val.

Example:

:compare <- {(:Num :Arg_num)
  [:Lt? :Le? :Gt? :Ge?] = [
    Num < Arg_num
    Num <= Arg_num
    Num > Arg_num
    Num >= Arg_num
  ]
  stdout.print_line(
    'Lt?={} Le?={} Gt?={} Ge?={}'.format(Lt?.repr Le?.repr Gt?.repr Ge?.repr))
}

compare(42 42.000)  # => Lt?=false Le?=true Gt?=false Ge?=true
compare(42 84)      # => Lt?=true Le?=true Gt?=false Ge?=false
compare(42 21)      # => Lt?=false Le?=false Gt?=true Ge?=true

Num.times

Num.times returns an iter from 0 to Num-1, incrementing 1 for each element.

Precondition:

• Num must be an int num

• Num must be greater than or equal to 0

Example:

5.times.each{(:N)
  stdout.print_line(N.repr)
}
# Output:
#   0
#   1
#   2
#   3
#   4

Num.up

Num.up returns an infinite iter from Num, incrementing 1 for each element.

Example:

0.5.up.take_front(5).each{(:N)
  stdout.print_line(N.repr)
}
# Output:
#   0.5
#   1.5
#   2.5
#   3.5
#   4.5

Num.down

Num.down returns an infinite iter from Num, decrementing 1 for each element.

Example:

9.5.down.take_front(5).each{(:N)
  stdout.print_line(N.repr)
}
# Output:
#   9.5
#   8.5
#   7.5
#   6.5
#   5.5

Num.show(...[$config={}])

Num.show returns a str for UI.

The optional fun param $config is a fun which takes a num_show_conf as an arg. Num.show passes a num_show_conf val to $config to configure the output. If $config is not specified, an empty fun {} is used as the default val.

Example:

stdout.print_line(32767.show)  # => 32767
stdout.print_line(32767.show{(:S) S.spec('x') })    # => 7fff
stdout.print_line(32767.show{(:S) S.radix(16) })    # => 7fff
stdout.print_line(32767.show{(:S) S.group_sep(3) }) # => 32,767

show methods are usually called from Str.format.

TODO add documentation of num_show_conf vals.

Num.repr

Num.repr returns a str such as "1.2500", "(-42)" or "(1.by_scale(-10))".

3.13.2. NUM.num?(Val)

NUM.num? returns whether the Val is a num val.

3.13.3. NUM.parse_int(Str ...[Radix=10])

NUM.parse_int parses Str as a representation of an int num with the base Radix, and returns a single-element vec of the num. If Str is not a valid representation of an int num, the fun returns an empty vec.

The syntax of an int num representation is as follows:

• Zero or one rune representing a sign: '-' or '+'

• One or more digits: '0' to '9', 'a' to 'z' and 'A' to 'Z'

Example usage:

:NUM.require_from('kink/')

stdout.print_line(NUM.parse_int('42').repr)
# => [42]

stdout.print_line(NUM.parse_int('ff' 16).repr)
# => [127]

stdout.print_line(NUM.parse_int('*').repr)
# => []

3.13.4. NUM.parse_decimal(Str)

NUM.parse_decimal parses Str as a representation of a decimal number, and returns a 1ary vec of the num. If Str is not a valid representation of a decimal number, it returns an empty vec.

The syntax of a decimal number representation is as follows:

• Optionally starts with a sign: '-' or '+'

• Zero or more digits of the integral part: '0' to '9'

• Point '.' and zero or more digits of the fraction part: '0' to '9'

Str must include at least one digit in the integral part or the fraction part.

The scale of the result num is the count of the digits of the fraction part.

Example:

:NUM.require_from('kink/')

stdout.print_line(NUM.parse_decimal('1.2300').repr)
# => [1.2300]

stdout.print_line(NUM.parse_decimal('.387').repr)
# => [0.387]

stdout.print_line(NUM.parse_decimal('-42.').repr)
# => [(-42)]

stdout.print_line(NUM.parse_decimal('*').repr)
# => []