3.14. kink/NUM

Companion mod for num vals.

3.14.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.require_from('kink/')
:Num <- NUM.new(123 (-3))
stdout.print_line(Num.repr)           # => NUM.new(123 (-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.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_div val of (Dividend=Num, Divisor=Divisor).

Preconditions:

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

The result can be rounded by methods such as .floor and .ceil.

Example:

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

See kink/NUM_DIV for rounding.

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.round

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

Example:

stdout.print_line(1.2345.round.floor(2).repr) # => 1.23

See kink/NUM_DIV for details of rounding.

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(...[$show_config={}])

Num.show returns a str representation of the Num, which is intended to be used in UI messages.

The optional fun param $show_config is a fun which takes a num_show_conf as an arg. Num.show passes a num_show_conf val to $show_config to configure the output. If $show_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 method is usually called from Str.format.

Example:

stdout.print_line('{} {%04x}'.format(42 255))
# => 42 00ff

For details of the output and the configuration, see type num_show_conf.

Num.repr

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

3.14.2. NUM.num?(Val)

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

3.14.3. NUM.new(Mantissa Scale)

NUM.new makes a num with the given Mantissa and Scale.

Preconditions:

• Mantissa must be an int num

• Scale must be an int num

Example:

:NUM.require_from('kink/')

stdout.print_line(NUM.new(12345 2).repr)  # => 123.45

3.14.4. 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.14.5. 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)
# => []

3.14.6. type num_show_conf

Num_show_conf is a subtype of show_conf (see kink/FORMAT), which is used to configure the output of Num.show.

The output of Num.show method is divided in to cases.

1) When the scale of the num is less than or equal to 0

In this case, the output will be “{sign}{grouped_padded_integral_digits}”.

2) When the scale of the num is greater than 0

In this case the radix must be 10, and the output will be “{sign}{grouped_padded_integral_digits}{decimal_sep}{fractional_digits}”.

§ Part: {sign}

If the num is less than 0, {sign} part is the non-negative sign set by .minus_sign method.

If the num is greater than or equal to 0, {sign} part is the non-negative sign set by .minus_sign method.

§ Part: {grouped_padded_integral_digits}

Firstly, the integral part of the num is converted to digits with the radix set by .radix method. 0-9 and a-z characters are used as digits.

Secondly, "0" is padded to the left of the digits so that the length is greater than or equal to the minimum number of the integral part set by .pad_zero method.

Finally, the digits are grouped using the group length set by .group_len, and the grouping separator set by .group_sep method is inserted between groups.

§ Part: {decimal_sep}

The one set by .decimal_sep method.

§ Part: {fractional_digits}

The decimal digits of the fractional part of the num.

The trailing zeros are not omitted, thus the length of the part is always equal to the scale of the num.

Num_show_conf.locale(Locale_name)

Num_show_conf.locale imports configuration specific to the specified locale.

Precondition:

• Locale_name must be a str

.locale configures .decimal_sep and .group_sep.

If .locale method is not called, Num.show does not use any locale information. So, even if you want to use the runtime default locale, you must call Num_show_conf.locale explicitly.

Example:

:RUNTIME.require_from('kink/')
:Shown <- 3776.24.show{(:S)
  S.locale(RUNTIME.locale_name)
}
stdout.print_line(Shown)
# Output varies by the runtime default locale

Num_show_conf.spec(Spec)

Num_show_conf.spec configures the output of Num.show method using the Spec str.

Preconditions:

• Spec must be a str

• Spec must be a conjunction of the following fields

§ Plus sign field: "+"

Uses "+" as the non-negative sign.

§ Grouping field: ","

Uses 3 as the grouping length.

§ Padding field: "0{Min_int_digits}"

Uses Min_int_digits as the minimum number of the integral part.

{Min_int_digits} must be 1 or more decimal digits which represents a positive int num.

§ Binary field: "b"

Uses 2 as the radix.

§ Octal field: "o"

Uses 8 as the radix.

§ Decimal field: "d"

Uses 10 as the radix.

§ Headecimal field: "x"

Uses 16 as the radix.

§ Examples

stdout.print_line('{%+04f}'.format(255))
# => +00ff

stdout.print_line('{%,}'.format(12345.6789))
# => 12,345.6789

stdout.print_line('{%b}'.format(42))
# => 101010

stdout.print_line('{%o}'.format(42))
# => 52

stdout.print_line('{%d}'.format(42))
# => 42

stdout.print_line('{%x}'.format(42))
# => 2a

Num_show_conf.plus_sign(Plus_sign)

Num_show_conf.plus_sign sets Plus_sign as the sign of non-negative numbers.

Precondition:

• Plus_sign must be a str

The default value is an empty str "".

Example:

stdout.print_line(42.show{(:S)
  S.plus_sign('+')
})
# => +42

stdout.print_line(0.show{(:S)
  S.plus_sign('+')
})
# => +0

stdout.print_line((-10).show{(:S)
  S.plus_sign('+')
})
# => -10

Num_show_conf.minus_sign(Minus_sign)

Num_show_conf.minus_sign sets Minus_sign as the sign of negative numbers.

Precondition:

• Minus_sign must be a str

The default value is "-".

Example:

stdout.print_line((-10).show{(:S)
  S.minus_sign('minus ')
})
# => minus 10

stdout.print_line(42.show{(:S)
  S.minus_sign('minus ')
})
# => 42

stdout.print_line(0.show{(:S)
  S.minus_sign('minus ')
})
# => 0

Num_show_conf.group_sep(Group_sep)

Num_show_conf.group_sep sets Group_sep as the digit grouping separator.

Precondition:

• Group_sep must be a str

The default value is ",".

Example:

stdout.print_line(12345.6789.show{(:S)
  S.group_sep('_')
  S.group_len(3)
})
# => 12_345.6789

stdout.print_line(37767.show{(:S)
  S.group_sep('_')
  S.group_len(2)
  S.radix(16)
})
# => 7f_ff

Num_show_conf.group_len(Group_len)

Num_show_conf.group_len sets Group_len as the length of digits groups.

Precondition:

• Group_len must be an int num bigger than or equal to 1

The default value is the plus infinity. Thus, by default, the grouping separator is not displayed.

Example:

stdout.print_line(12345.6789.show{(:S)
  S.group_len(3)
})
# => 12,345.6789

Num_show_conf.decimal_sep(Decimal_sep)

Num_show_conf.decimal_sep sets Decimal_sep as the separator between th integral part and the fractional part.

Precondition:

• Decimal_sep must be a str

Example:

stdout.print_line(12345.6789.show{(:S)
  S.decimal_sep(' . ')
})
# => 12345 . 6789

Num_show_conf.pad_zero(Min_int_digits)

Num_show_conf.pad_zero sets Min_int_digits as the minimum number of the integral part to be output.

Precondition:

• Min_int_digits must be an int num greater than or equal to 1

If the digits of the integral part of the num is fewer than Min_int_digits, "0" is padded to the head so that the number of the digits of the displayed integral part is equal to Min_int_digits.

The default value is 1.

Example:

stdout.print_line(12345.6789.show{(:S)
  S.pad_zero(8)
})
# => 00012345.6789

stdout.print_line((-42).show{(:S)
  S.pad_zero(4)
})
# => -0042

stdout.print_line(255.show{(:S)
  S.pad_zero(4)
  S.radix(16)
})
# => 00ff

Num_show_conf.radix(Radix)

Num_show_conf.radix sets Radix as the radix of the result of .show method.

Precondition:

• Radix must be an int num in the range [2, 36]

• If the num is not an int, Radix must be 10

The default value is 10.

Example:

stdout.print_line(255.show{(:S)
  S.radix(2)
})
# => 11111111

stdout.print_line(255.show{(:S)
  S.radix(16)
})
# => ff

stdout.print_line((-255).show{(:S)
  S.radix(16)
})
# => -ff