Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions packages/fixed-points/src/__tests__/placeholder-test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { __placeholder } from '..';

describe('@solana/fixed-points scaffold', () => {
it('exports a placeholder symbol until the real API lands', () => {
expect(typeof __placeholder).toBe('symbol');
describe('@solana/fixed-points', () => {
it('has runtime tests that arrive with the upcoming factory PR', () => {
expect(true).toBe(true);
});
});
35 changes: 35 additions & 0 deletions packages/fixed-points/src/__typetests__/binary-core-typetest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { BinaryFixedPoint } from '../binary/core';

// [DESCRIBE] BinaryFixedPoint.
{
// It preserves the Signedness, TotalBits, and FractionalBits type parameters.
{
const value = {} as BinaryFixedPoint<'signed', 16, 15>;
value.signedness satisfies 'signed';
value.totalBits satisfies 16;
value.fractionalBits satisfies 15;
value.kind satisfies 'binaryFixedPoint';
value.raw satisfies bigint;
}

// A concretely-parameterised value satisfies a generically-parameterised type.
{
const value = {} as BinaryFixedPoint<'signed', 16, 15>;
value satisfies BinaryFixedPoint<'signed', number, number>;
}

// Fields are readonly.
{
const value = {} as BinaryFixedPoint<'signed', 16, 8>;
// @ts-expect-error fractionalBits is readonly.
value.fractionalBits = 16;
// @ts-expect-error kind is readonly.
value.kind = 'decimalFixedPoint';
// @ts-expect-error raw is readonly.
value.raw = 1n;
// @ts-expect-error signedness is readonly.
value.signedness = 'unsigned';
// @ts-expect-error totalBits is readonly.
value.totalBits = 32;
}
Comment thread
lorisleiva marked this conversation as resolved.
}
35 changes: 35 additions & 0 deletions packages/fixed-points/src/__typetests__/decimal-core-typetest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { DecimalFixedPoint } from '../decimal/core';

// [DESCRIBE] DecimalFixedPoint.
{
// It preserves the Signedness, TotalBits, and Decimals type parameters.
{
const value = {} as DecimalFixedPoint<'unsigned', 64, 6>;
value.signedness satisfies 'unsigned';
value.totalBits satisfies 64;
value.decimals satisfies 6;
value.kind satisfies 'decimalFixedPoint';
value.raw satisfies bigint;
}

// A concretely-parameterised value satisfies a generically-parameterised type.
{
const value = {} as DecimalFixedPoint<'unsigned', 64, 6>;
value satisfies DecimalFixedPoint<'unsigned', number, number>;
}

// Fields are readonly.
{
const value = {} as DecimalFixedPoint<'unsigned', 64, 6>;
// @ts-expect-error decimals is readonly.
value.decimals = 16;
// @ts-expect-error kind is readonly.
value.kind = 'binaryFixedPoint';
// @ts-expect-error raw is readonly.
value.raw = 1n;
// @ts-expect-error signedness is readonly.
value.signedness = 'signed';
// @ts-expect-error totalBits is readonly.
value.totalBits = 32;
}
Comment thread
lorisleiva marked this conversation as resolved.
}

This file was deleted.

36 changes: 36 additions & 0 deletions packages/fixed-points/src/binary/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Signedness } from '../signedness';

/**
* A fixed-point number whose scale is a power of 2. The stored `raw` bigint
* represents the mathematical value `raw / 2 ** fractionalBits`.
*
* Binary fixed-point is the fastest fractional representation to compute
* with — rescaling is a bit shift — so it is the preferred choice for
* audio samples, graphics, probabilities, and any other quantity where
* performance matters and the scale does not need to align with decimal
* digits.
*
* @typeParam TSignedness - Whether the value can be negative.
* @typeParam TTotalBits - The total number of bits used to store the raw value.
* @typeParam TFractionalBits - The number of bits to the right of the binary point.
*
* @example
* A 16-bit signed Q1.15 audio sample:
* ```ts
* type AudioSample = BinaryFixedPoint<'signed', 16, 15>;
* ```
*
* @see {@link DecimalFixedPoint}
* @see {@link Signedness}
*/
export type BinaryFixedPoint<
TSignedness extends Signedness,
TTotalBits extends number,
TFractionalBits extends number,
> = {
readonly fractionalBits: TFractionalBits;
readonly kind: 'binaryFixedPoint';
readonly raw: bigint;
readonly signedness: TSignedness;
readonly totalBits: TTotalBits;
};
1 change: 1 addition & 0 deletions packages/fixed-points/src/binary/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './core';
30 changes: 30 additions & 0 deletions packages/fixed-points/src/decimal/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Signedness } from '../signedness';

/**
* A fixed-point number whose scale is a power of 10. The stored `raw` bigint
* represents the mathematical value `raw / 10 ** decimals`.
*
* Decimal fixed-point is the natural representation for quantities that
* users reason about in base-10 terms, such as token amounts, currency, or
* probabilities with decimal precision.
*
* @typeParam TSignedness - Whether the value can be negative.
* @typeParam TTotalBits - The total number of bits used to store the raw value.
* @typeParam TDecimals - The number of decimal digits to the right of the decimal point.
*
* @example
* An unsigned 64-bit USDC amount with 6 decimals of precision:
* ```ts
* type Usdc = DecimalFixedPoint<'unsigned', 64, 6>;
* ```
*
* @see {@link BinaryFixedPoint}
* @see {@link Signedness}
*/
export type DecimalFixedPoint<TSignedness extends Signedness, TTotalBits extends number, TDecimals extends number> = {
readonly decimals: TDecimals;
readonly kind: 'decimalFixedPoint';
readonly raw: bigint;
readonly signedness: TSignedness;
readonly totalBits: TTotalBits;
};
1 change: 1 addition & 0 deletions packages/fixed-points/src/decimal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './core';
13 changes: 4 additions & 9 deletions packages/fixed-points/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@
*
* @packageDocumentation
*/

/**
* Placeholder export that exists solely to give this package tree-shakable
* contents while its real API is implemented in follow-up PRs. It will be
* removed as soon as the first real export lands.
*
* @internal
*/
export const __placeholder: unique symbol = Symbol('@solana/fixed-points/placeholder');
export * from './binary';
export * from './decimal';
export * from './rounding';
export * from './signedness';
18 changes: 18 additions & 0 deletions packages/fixed-points/src/rounding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Rounding mode used by fixed-point operations that must coerce an exact
* mathematical result into a value with fewer bits of precision. Applies to
* factories that accept lossy inputs, as well as to downscaling rescales and
* divisions.
*
* - `'floor'` rounds toward negative infinity.
* - `'ceil'` rounds toward positive infinity.
* - `'trunc'` rounds toward zero, discarding the fractional part.
* - `'round'` rounds to the nearest representable value, with ties rounded
* away from zero. That is, `0.5` rounds to `1`, `-0.5` rounds to `-1`, and
* `-1.5` rounds to `-2`. This is symmetric around zero and differs from
* JavaScript's `Math.round`, which breaks ties toward positive infinity.
* - `'strict'` rejects any input that would require rounding and throws
* `SOLANA_ERROR__FIXED_POINTS__STRICT_MODE_PRECISION_LOSS` instead of
* coercing the result.
*/
export type RoundingMode = 'ceil' | 'floor' | 'round' | 'strict' | 'trunc';
12 changes: 12 additions & 0 deletions packages/fixed-points/src/signedness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Whether a fixed-point number can represent negative values.
*
* - `'signed'` fixed-point numbers use two's-complement semantics and can
* represent both negative and non-negative values.
* - `'unsigned'` fixed-point numbers can only represent non-negative values
* but get one extra bit of positive range.
*
* @see {@link BinaryFixedPoint}
* @see {@link DecimalFixedPoint}
*/
export type Signedness = 'signed' | 'unsigned';
Loading