Skip to content
Open
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
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
64 changes: 64 additions & 0 deletions .github/workflows/Invalidations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Invalidations

on:
pull_request:

concurrency:
# Skip intermediate builds: always.
# Cancel intermediate builds: always.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
evaluate:
# Only run on PRs to the default branch.
# In the PR trigger above branches can be specified only explicitly whereas this check should work for master, main, or any other default branch
if: github.base_ref == github.event.repository.default_branch
runs-on: ubuntu-latest
steps:
- uses: julia-actions/setup-julia@v2
with:
version: '1'
- uses: actions/checkout@v4
- uses: julia-actions/julia-buildpkg@v1
- name: Overwrite Package Version # FIXME
run: >
julia -e '
lines = readlines("Project.toml")
open("Project.toml", "w") do f
for l in lines
if l == "version = \"0.9.0-dev\""
l = "version = \"0.8.4\""
end
println(f, l)
end
end'
- uses: julia-actions/julia-invalidations@v1
id: invs_pr

- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
- uses: julia-actions/julia-buildpkg@v1
- name: Overwrite Package Version # FIXME
run: >
julia -e '
lines = readlines("Project.toml")
open("Project.toml", "w") do f
for l in lines
if l == "version = \"0.9.0-dev\""
l = "version = \"0.8.4\""
end
println(f, l)
end
end'
- uses: julia-actions/julia-invalidations@v1
id: invs_default

- name: Report invalidation counts
run: |
echo "Invalidations on default branch: ${{ steps.invs_default.outputs.total }} (${{ steps.invs_default.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY
echo "This branch: ${{ steps.invs_pr.outputs.total }} (${{ steps.invs_pr.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY
- name: Check if the PR does increase number of invalidations
if: steps.invs_pr.outputs.total > steps.invs_default.outputs.total
run: exit 1
12 changes: 10 additions & 2 deletions .github/workflows/TagBot.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
name: TagBot
on:
schedule:
- cron: 0 * * * *
issue_comment:
types:
- created
workflow_dispatch:
inputs:
lookback:
default: 3
permissions:
contents: write
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1
Expand Down
44 changes: 19 additions & 25 deletions .github/workflows/UnitTest.yml
Original file line number Diff line number Diff line change
@@ -1,55 +1,49 @@
name: Unit test

on:
create:
tags:
push:
branches:
- master
- release-*
tags: ['*']
pull_request:
schedule:
- cron: '20 00 1 * *'
workflow_dispatch:

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
julia-version: ['1.0', '1', 'nightly']
os: [ubuntu-latest, windows-latest, macOS-latest]
julia-version: ['1.0', '1.6', '1', 'nightly']
os: [ubuntu-latest, windows-latest, macos-13]
julia-arch: [x64]
# only test one 32-bit job
include:
- os: ubuntu-latest
- os: ubuntu-latest # only test one 32-bit job
julia-version: '1'
julia-arch: x86
- os: macos-latest
julia-version: '1'
julia-arch: aarch64
- os: macos-latest
julia-version: 'nightly'
julia-arch: aarch64

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: "Set up Julia"
uses: julia-actions/setup-julia@v1
uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.julia-version }}
arch: ${{ matrix.julia-arch }}

- name: Cache artifacts
uses: actions/cache@v1
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
uses: julia-actions/cache@v2
- name: "Unit Test"
uses: julia-actions/julia-runtest@master
uses: julia-actions/julia-runtest@v1

- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }} # required
fail_ci_if_error: true
file: lcov.info


76 changes: 76 additions & 0 deletions .github/workflows/UnitTestArm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Unit test for Arm

on:
push:
branches:
- master
- release-*
tags: ['*']
pull_request:
workflow_dispatch:
permissions:
actions: write
contents: read
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
julia-version: ['1.0', '1.6', '1', 'nightly']
os: [ubuntu-latest]
distro: [ubuntu_latest]
arch: [aarch64]

steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.julia-version }}
- uses: julia-actions/cache@v2
- name: Download Julia Binary
run: >
julia -e '
using Pkg; Pkg.add("JSON"); using JSON;
if "${{ matrix.julia-version }}" == "nightly";
url = "https://julialangnightlies-s3.julialang.org/bin/linux/${{ matrix.arch }}/julia-latest-linux-${{ matrix.arch }}.tar.gz";
else;
path = download("https://julialang-s3.julialang.org/bin/versions.json");
json = JSON.parsefile(path);
try rm(path) catch end;
vspec = Pkg.Types.VersionSpec("${{ matrix.julia-version }}");
a(f) = f["arch"] == "${{ matrix.arch }}" && f["os"] == "linux" && !occursin("musl", f["triplet"]);
m = filter(json) do v; vn = VersionNumber(v[1]); vn in vspec && isempty(vn.prerelease) && any(a, v[2]["files"]); end;
v = sort(VersionNumber.(keys(m)))[end];
url = filter(a, json[string(v)]["files"])[1]["url"];
end;
download(url, "/tmp/julia-aarch64.tar.gz");'

- name: Extract Julia Files
run: |
mkdir -p /home/runner/work/julia/
tar -xf /tmp/julia-aarch64.tar.gz --strip-components=1 -C /home/runner/work/julia/
rm /tmp/julia-aarch64.tar.gz

- uses: uraimo/run-on-arch-action@v2.7.2
name: Unit Test
with:
arch: ${{ matrix.arch }}
distro: ${{ matrix.distro }}
dockerRunArgs: |
-v "/home/runner/work/julia:/home/runner/work/julia"
-v "/home/runner/.julia/registries:/root/.julia/registries"
--net=host
install: |
ln -s /home/runner/work/julia/bin/julia /usr/local/bin/julia
echo /home/runner/work/julia/lib > /etc/ld.so.conf.d/julia.conf
mkdir -p /root/.julia/registries/General
run: |
julia --compile=min -O0 -e 'using InteractiveUtils; versioninfo();'
julia --project=. --check-bounds=yes --color=yes -e 'using Pkg; Pkg.build(); Pkg.test(coverage=true)'
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }} # required
fail_ci_if_error: true
file: lcov.info
9 changes: 7 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
name = "FixedPointNumbers"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"
version = "0.8.5"

[deps]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
JET = "0.9, 0.10, 0.11"
StableRNGs = "1"
julia = "1"

[extras]
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
test = ["JET", "StableRNGs", "Test"]
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ To construct such a number, use `1.3N4f12`, `N4f12(1.3)`, `convert(N4f12, 1.3)`,
`Normed{UInt16,12}(1.3)`, or `reinterpret(N4f12, 0x14cc)`.
The last syntax means to construct a `N4f12` from the `UInt16` value `0x14cc`.

To read a number from its textual representation, use `parse(N4f12, "1.3")`, or
`tryparse(N4f12, "1.3")` which returns `nothing` instead of throwing when the
string is malformed or out of range.

More generally, an arbitrary number of bits from any of the standard unsigned
integer widths can be used for the fractional part. For example:
`Normed{UInt32,16}`, `Normed{UInt64,3}`, `Normed{UInt128,7}`.
Expand Down
81 changes: 77 additions & 4 deletions src/FixedPointNumbers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox,
big, rationalize, float, trunc, round, floor, ceil, bswap, clamp,
div, fld, rem, mod, mod1, fld1, min, max, minmax,
signed, unsigned, copysign, flipsign, signbit,
rand, length
length

import Statistics # for _mean_promote
import Random: Random, AbstractRNG, SamplerType, rand!

using Base.Checked: checked_add, checked_sub, checked_div
using Base.Checked: checked_add, checked_sub, checked_mul, checked_div

using Base: @pure

Expand Down Expand Up @@ -83,6 +84,71 @@ function rationalize(::Type{Ti}, x::FixedPoint; tol::Real=eps(x)) where Ti <: In
tol <= eps(x) ? Rational{Ti}(x) : rationalize(Ti, float(x), tol)
end

# parsing

# true if `cu[lo:hi]` is a nonempty run of ASCII decimal digits
_alldigits(cu, lo::Int, hi::Int) = lo <= hi && all(UInt8('0') <= cu[i] <= UInt8('9') for i in lo:hi)

# true if `cu[lo:hi]` is an optional sign followed by ASCII decimal digits
_isdecint(cu, lo::Int, hi::Int) = lo <= hi && _alldigits(cu, lo + (cu[lo] ∈ UInt8.(('-', '+'))), hi)

function Base.tryparse(::Type{X}, s::AbstractString) where {T, f, X <: FixedPoint{T,f}}
bitwidth(T) > 64 && return _tryparse_bf(X, s)
IT = bitwidth(T) <= 32 ? Int64 : Int128
cu = codeunits(s)
ncu = lastindex(cu)
dot = findfirst(==(UInt8('.')), cu)
if isnothing(dot)
# plain integer; anything else (exponent, whitespace, …) goes to BigFloat
_isdecint(cu, 1, ncu) || return _tryparse_bf(X, s)
n = tryparse(IT, s)
isnothing(n) && return _tryparse_bf(X, s)
n < 0 && T <: Unsigned && return nothing
return _try_convert(X, n)
end
iplo, iphi = 1, dot - 1 # integer part, may carry a sign
fplo, fphi = dot + 1, ncu # fractional part, digits only
ipempty = iphi < iplo
fpempty = fphi < fplo
ipempty && fpempty && return nothing
# the fast decimal path requires plain digits on both sides of the dot;
# whitespace, exponents and other syntax fall back to BigFloat parsing
ipempty || _isdecint(cu, iplo, iphi) || return _tryparse_bf(X, s)
fpempty || _alldigits(cu, fplo, fphi) || return _tryparse_bf(X, s)
neg = !ipempty && cu[iplo] == UInt8('-')
neg && T <: Unsigned && return nothing
# trailing fractional zeros leave the value unchanged but enlarge the
# denominator, so drop them to keep more inputs on the integer fast path
while fphi >= fplo && cu[fphi] == UInt8('0')
fphi -= 1
end
nd = fphi < fplo ? 0 : fphi - fplo + 1
ip = ipempty ? zero(IT) : tryparse(IT, SubString(s, 1, dot - 1))
fp = nd == 0 ? zero(IT) : tryparse(IT, SubString(s, dot + 1, fphi))
(isnothing(ip) || isnothing(fp)) && return _tryparse_bf(X, s)
try
d = one(IT)
for _ in 1:nd
d = checked_mul(d, IT(10))
end
num = checked_add(checked_mul(abs(ip), d), fp)
return _try_convert(X, (neg ? -num : num) // d)
catch e
e isa OverflowError && return _tryparse_bf(X, s)
rethrow()
end
end

function _tryparse_bf(::Type{X}, s::AbstractString) where {X <: FixedPoint}
r = tryparse(BigFloat, s)
isnothing(r) ? nothing : _try_convert(X, r)
end

function _convert(::Type{X}, x) where {X <: FixedPoint}
y = _try_convert(X, x)
isnothing(y) ? throw_converterror(X, x) : y
end

"""
isapprox(x::FixedPoint, y::FixedPoint; rtol=0, atol=max(eps(x), eps(y)))

Expand Down Expand Up @@ -326,8 +392,15 @@ scaledual(::Type{Tdual}, x::AbstractArray{T}) where {Tdual, T <: FixedPoint} =
throw(ArgumentError("$X is $bitstring type representing $n values from $Xmin to $Xmax; cannot represent $x"))
end

rand(::Type{T}) where {T <: FixedPoint} = reinterpret(T, rand(rawtype(T)))
rand(::Type{T}, sz::Dims) where {T <: FixedPoint} = reinterpret(T, rand(rawtype(T), sz))
function Random.rand(r::AbstractRNG, ::SamplerType{X}) where X <: FixedPoint
X(rand(r, rawtype(X)), 0)
end

function rand!(r::AbstractRNG, A::Array{X}, ::SamplerType{X}) where {T, X <: FixedPoint{T}}
At = unsafe_wrap(Array, reinterpret(Ptr{T}, pointer(A)), size(A))
Random.rand!(r, At, SamplerType{T}())
A
end

if VERSION >= v"1.1" # work around https://github.com/JuliaLang/julia/issues/34121
include("precompile.jl")
Expand Down
Loading