fixed_decimal.cr
fixed_decimal
Binary fixed-point numbers for Crystal, backed by 128-bit integers.
Fixed(N)is signed and wraps anInt128.UFixed(N)is unsigned and wraps aUInt128.
The low N bits are the fraction and the remaining 128 - N bits are the integer part (including the sign for Fixed). N is checked at compile time: 0..127 for Fixed, 0..128 for UFixed.
Installation
-
Add the dependency to your
shard.yml:dependencies: fixed_decimal: github: plambert/fixed_decimal.cr -
Run
shards install
Usage
require "fixed_decimal"
price = Fixed(64).new("19.99")
total = price * 3 # => 59.97
total.format(10, 2, '0') # => "0000059.97"
total.format("%+.4f") # => "+59.9700"
(Fixed(16).new(1) / 3).to_s # => "0.33333"
UFixed(128).new("0.75").to_f64 # => 0.75
Fixed(8).new(300).to_u8? # => nil
Semantics
- Rounding is always round-half-to-even: multiplication, division, parsing, conversion from floats and between fixed-point types, and
#format. - Addition, subtraction, and multiplication or division by an integer are exact when the result is in range.
- Any result that is out of range raises
OverflowError, as the built-in integer types do.&+and&-wrap instead. - Division by zero raises
DivisionByZeroError. - Arithmetic works between values of the same type and with any
Inton either side. Convert explicitly to mix fraction widths:Fixed(32).new(some_fixed_16). - Comparison is exact across all fixed-point types,
IntandFloat, and equal values hash equally.
Conversions
- Constructors take any
Int,Float,Stringor fixed-point value..parse?returnsnilinstead of raising. #to_i8through#to_u128truncate toward zero and raiseOverflowError; the?variants returnniland the!variants wrap.#to_f64and#to_f32are correctly rounded;#to_f32raises when the value is beyondFloat32::MAX,#to_f32?returnsnil, and#to_f32!returns infinity.require "fixed_decimal/big"adds exact#to_big_r,#to_big_d,#to_big_f, truncating#to_big_i, and constructors fromBigInt,BigRational,BigDecimalandBigFloat. It needs libgmp.
Strings
#to_s prints the shortest decimal that parses back to the same value, with at least one fraction digit ("1.0") unless N is zero.
Parsing accepts an optional sign, digits with single underscores between them, an optional fraction, and an optional exponent: "-1_000.25", ".5", "6.02e23".
#format(width, decimal, padding) and #format(spec) imitate sprintf:
| Call | Result |
|---|---|
x.format(8, 2) |
" -3.25" |
x.format(-8, 2) |
"-3.25 " |
x.format(8, 3, '0') |
"-003.250" |
x.format("%+010.3f") |
"-00003.250" |
x.format("%f") |
"-3.250000" |
x.format("8") |
" -3.25" |
In a spec, the % and the f are optional. The flags are -, +, space, 0 and #. Without f, a missing precision means the shortest form rather than six digits.
Development
shards install
crystal spec
The specs check every operation against an independent BigRational reference across a range of fraction widths, using boundary and seeded pseudo-random values.
Contributing
- Fork it (https://github.com/plambert/fixed_decimal.cr/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- Paul M. Lambert - creator and maintainer
fixed_decimal.cr
- 0
- 0
- 0
- 0
- 1
- about 8 hours ago
- September 24, 2026
MIT License
Thu, 24 Sep 2026 03:03:49 GMT