HC(1) | General Commands Manual | HC(1) |
NAME
hc
— an arbitrary
bit-width fixed-point number calculator
SYNOPSIS
hc |
[program ...] |
DESCRIPTION
hc
is a simple desk calculator for integer
and fixed-point two's complement calculations. It has some similarities with
e.g. bc(1) and
dc(1).
When hc
is invoked with arguments, all
arguments are concatenated, parsed and evaluated as a program.
hc
will then print the result before exiting. The
behavior is similar if input is provided via standard input. If input is
provided via both arguments and standard input, the arguments are
prioritized. In the absence of input, hc
will enter
an interactive mode with a REPL, i.e. a read-eval-print-loop.
Numbers in hc
are by default rational
numbers with infinite precision. They can be thought of as binary two's
complement fixed-point numbers with either infinite or a specified number of
integer and fractional bits.
Numbers can be entered in either decimal, binary, octal or hexadecimal numerals. The result is also shown in multiple bases.
hc
is meant to be used as a desk
calculator and may be useful for e.g.
- converting numbers between different bases,
- performing operations on two's complement numbers with any bit width or precision,
- performing rational number calculations, with both input and output having mixed bases,
- converting between fractions and rational numbers with repeating digits.
hc
differs from
bc(1) in several
ways:
- input base is selected with prefix, no need to set ibase, expressions can therefore contain multiple values in different bases,
- output is always printed in multiple bases, no need to set obase,
- negative values are always shown as two's complement numbers in binary, octal and hexadecimal,
- length and scale is specified in number of binary digits instead of decimal digits,
- length and scale can be infinite, infinitively repeating digits can be used in input and are shown in output,
- fewer bases are supported: only binary, octal, decimal and hexadecimal,
- no user functions, strings or control flow.
Types
All values are numbers in the form of binary two's complement fixed-point numbers, each with a signedness, integer width and fractional width. The signedness can be either unspecified (default), unsigned or signed. Each width is either unspecified (default) or a non-negative integer.
Numbers with an unspecified signedness will normally act as a signed number, but if used in an operation with a signed or unsigned number it may inherit the other operand's signedness.
Numbers with an unspecified integer width can be arbitrarily large, but may inherit a limited width and may be truncated if used in an operation together with an operand that has a fixed integer width.
Similarly, numbers with an unspecified fractional width can be arbitarily precise, but will inherit a limited width and may be truncated if used in an operation together with an operand that has a fixed fractional width.
The type of a number can be specified by providing a type specifier. A specifier may contain a prefix, an integer width m and a fractional width f. The fractional width must be prepended with a ⟨period⟩ character. The prefix i or u specifies a signed or unsigned integer, respectively. Similarly, the prefix q or uq specifies a signed or unsigned fixed-point number. The table below lists all possible variants of type specifiers.
Specifier | Signedness | Integer width | Fractional width |
i | signed | unspecified | 0 |
im | signed | m | 0 |
u | unsigned | unspecified | 0 |
um | unsigned | m | 0 |
q | signed | unspecified | unspecified |
qm | signed | m | unspecified |
q.f | signed | unspecified | f |
qm.f | signed | m | f |
uq | unsigned | unspecified | unspecified |
uqm | unsigned | m | unspecified |
uq.f | unsigned | unspecified | f |
uqm.f | unsigned | m | f |
m.f | unspecified | m | f |
.f | unspecified | unspecified | f |
m | unspecified | m | unspecified |
Literals
All literals are non-negative numbers. Number tokens may have an integer part and a fractional part separated by a radix point represented by the ⟨period⟩ character. Either the integer part or the fractional part may be omitted but not both. The period is only required if there is a fractional part.
The number may start with a prefix that determines the base of the number. The digits in the integer and fractional part must be valid in the base specified by the prefix. If no prefix is specified, the decimal base is assumed. The below table list the prefix and valid digits for each base. The prefix is case-sensitive while the alphabetic digits are not.
Name | Prefix | Digits |
Binary | 0b | 0 1 |
Octal | 0o | 0 1 2 3 4 5 6 7 |
Decimal | 0d | 0 1 2 3 4 5 6 7 8 9 |
Hexadecimal | 0x | 0 1 2 3 4 5 6 7 8 9 a b c d e f |
If a number has a prefix that corresponds to the binary, octal or hexadecimal base; the integer part may start with the maximal digit of the base enclosed in parentheses. This will indicate that the digit is infinitively repeating. If the number is signed, this will represent a finite negative number.
The fractional part may contain an infinitively repeating sequence of digits. The sequence must only appear once at the end of the fractional part and be enclosed in parentheses.
Scientific notation can be used for numbers in the decimal base. The number may end with the character ⟨e⟩ or ⟨E⟩ followed by an integer exponent. The number will then be multiplied by 10 to the power of the specified exponent.
The full grammar of a number is listed below, prefix refers to any of the prefixes in the above table, base_digit refers to any of the digits that are valid for the specified prefix, base_digit_max refers to the largest valid digit for the specified prefix.
number : decimal | non_decimal ; decimal : stem | stem e exponent | stem E exponent ; exponent : integer | - integer ; non_decimal : prefix stem | prefix ( base_digit_max ) stem ; stem : integer | . fraction | integer . | integer . fraction ; integer : base_digit | integer base_digit ; fraction : base_digit | integer base_digit | integer ( integer ) ;
A number literal may be immediately followed by a type specifier in order to specify the type of the number. If the type specifier does not have a prefix, it must be prepended with an ⟨apostrophe⟩ character.
Identifiers and Variables
Variables can be used to store numeric values. Each variable is associated with an identifier. An identifer consists of alphanumeric characters and underscores but may not start with a digit. An identifer may also not be identical to any type.
Variables may be assigned the value of an expression with the ⟨=⟩ operator. The value of a variable may thereafter be referenced by its identifier.
The _ variable has special behavior, similar to the Ans variable on a traditional calculator. Every time an outer expression is evaluated, the resulting value will be stored in this variable. Evaluations of intermediary inner expressions within an outer expression do not cause the variable to be overwritten, i.e. the variable will not change during the evaulation of a recursive expression.
Atoms
An atom is either a number literal or an identifier.
Program
An hc
program is a sequence of expressions
separated by semicolons. Semicolons are not required to terminate an
expression, it is only required to separate multiple expressions.
Each expression in the program will be evaluated from left to right, and the result of each expression will be displayed in that order. Each time an expression is evaluated, the resulting value will automatically be stored in the _ variable.
When hc
is in interactive mode, the input
prompt accepts a program with either a single or multiple expressions.
Expressions
Atoms can be used together with operators to form expressions. A single atom is the most basic expression. There are prefix unary and infix binary operators. Expressions can also be grouped by enclosing with parentheses.
For symmetric binary operations of two operands with differing types, a max_type between the two operands' types is determined. Before the operation is performed, both of the operands are casted to this type. The three properties of the new type, signedness, integer width, and fractional width, are all determined independently according to the following rules:
- If a property is set to unspecified for both types, the resulting property will be unspecified.
- If a property is set for one operand and unspecified for the other, the resulting type will inherit the set property.
- If both operands have a set property and they differ,
- for signedness, the resulting type will be signed,
- for a width, the resulting width will be the maximum of the two operands' widths.
Below is a table of all valid expressions in order of decreasing precedence. Operators not separated by horizontal lines have the same precedence and are grouped according to their associativity.
Syntax | Name | Type of Result | Associativity |
( expr ) | Grouping | type of expr | N/A |
expr ' type | Casting | type | N/A |
- expr | Negation | type of expr | N/A |
! expr | Bitwise Not | ||
lexpr ** rexpr | Exponentiation | type of lexpr | Left |
lexpr * rexpr | Multiplication | max_type(lexpr, rexpr) | Left |
lexpr / rexpr | Division | ||
lexpr % rexpr | Remainder | ||
lexpr + rexpr | Addition | max_type(lexpr, rexpr) | Left |
lexpr - rexpr | Subtraction | ||
lexpr << rexpr | Left shift | type of lexpr | Left |
lexpr >> rexpr | Right shift | ||
lexpr & rexpr | Bitwise And | max_type(lexpr, rexpr) | Left |
lexpr ^ rexpr | Bitwise Xor | max_type(lexpr, rexpr) | Left |
lexpr | rexpr | Bitwise Or | max_type(lexpr, rexpr) | Left |
ident = expr | Assignment | type of expr | Left |
FILES
History
If hc
is compiled with the
readline
feature, hc
will read and write to a history file to
maintain history between sessions. The history file will be named
history and will be placed in a cache directory,
whose location is dependent on platform:
Platform | Location | Example |
Linux | $XDG_CACHE_HOME/hc or $HOME/.cache/hc | /home/noah/.cache/hc |
macOS | $HOME/Library/Caches/hc | /Users/noah/Library/Caches/hc |
Windows | ${FOLDERID_LocalAppData}/hc | C:\Users\noah\AppData\Local\hc |
EXIT STATUS
On any IO error, or if an error is encountered during parsing or evaluation, the exit status will be 1 otherwise 0.
EXAMPLES
hc
mostly works like a typical calculator,
one can enter expressions that will be evaluated:
> (2 + 7) * 0xd 117 = 0b111_0101 = 0o165 = 0x75
Numbers can be given a specific width with a type specifier:
> 77u8 77 = 0b0100_1101 = 0o115 = 0x4d
For negative numbers, the non-decimal representations will display the two's complement value:
> -77i8 -77 (= 179) = 0b1011_0011 = 0o263 = 0xb3
The signedness of a number affects how it is extended when operands differ in width:
> 32u8 + (-1)'i4 31 = 0b0001_1111 = 0o037 = 0x1f > 32u8 + (-1)'u4 47 = 0b0010_1111 = 0o057 = 0x2fa
Values may not only have integer values, they may also contain a fractional part:
> 10/4 2.5 (= 5/2) = 0b10.1 = 0o2.4 = 0x2.8
By default, the precision is infinite, if a value cannot be represented by a finite number of digits, infinitively repeating sequences of digits will be enclosed with parentheses:
> 1/3 0.(3) (= 1/3) = 0b0.(01) = 0o0.(25) = 0x0.(5)
If a fractional width is specified, the fractional part will be truncated:
> 1/3q.8 0.332_031_25 (= 85/256) = 0b0.0101_0101 = 0o0.252 = 0x0.55
Negative numbers with an unspecified integer width will have an infinitively repeating digit in the integer part for non-decimal bases:
> -5.25 -5.25 (= -21/4) = 0b(1)010.11 = 0o(7)2.6 = 0x(f)a.c
Values assigned to variables may be used in later expressions:
> r = 2.5; pi = 7**7 / 4**9; 2.5 (= 5/2) = 0b10.1 = 0o2.4 = 0x2.8 3.141_567_230_224_609_375 (= 823543/262144) = 0b11.0010_0100_0011_1101_11 = 0o3.1103_67 = 0x3.243d_c > pi * r**2 19.634_795_188_903_808_593_75 (= 20588575/1048576) = 0b1_0011.1010_0010_1000_0001_1111 = 0o23.5050_076 = 0x13.a281_f
The _ variable is automatically assigned to the latest result:
> 5 / 2 2.5 (= 5/2) = 0b10.1 = 0o2.4 = 0x2.8 > 2 * _ 5 = 0b101 = 0o5 = 0x5
VERSION
This manual is written for hc
0.2.0.
SEE ALSO
AUTHORS
hc
was created by Noah
Hellman
<noah@hllmn.net>
November 7, 2022 |