logo: an integrated circuit
HC(1) General Commands Manual HC(1)

hcan arbitrary bit-width fixed-point number calculator

hc [program ...]

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.

All values are numbers in the form of binary two's complement fixed-point numbers, each with a , 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 . 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.

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

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

Binary 0 1
Octal 0 1 2 3 4 5 6 7
Decimal 0 1 2 3 4 5 6 7 8 9
Hexadecimal 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.

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, refers to any of the digits that are valid for the specified prefix, 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.

Variables can be used to store numeric values. Each variable is associated with an . 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.

An atom is either a number literal or an identifier.

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.

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

If hc is compiled with the 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:

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

On any IO error, or if an error is encountered during parsing or evaluation, the exit status will be otherwise .

hc mostly works like a typical calculator, one can enter expressions that will be evaluated:

> (2 + 7) * 0xd
 = 0b111_0101
 = 0o165
 = 0x75

Numbers can be given a specific width with a type specifier:

> 77u8
 = 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
 = 0b0001_1111
 = 0o037
 = 0x1f
> 32u8 + (-1)'u4
 = 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 * _
 = 0b101
 = 0o5
 = 0x5

This manual is written for hc 0.2.0.

bc(1), dc(1)

hc was created by Noah Hellman <noah@hllmn.net>

November 7, 2022