Giter Site home page Giter Site logo

halfintegers.jl's Introduction

HalfIntegers

PkgEval CI codecov

This package provides data types for half-integer numbers. Here, any number n/2 where n is an integer is considered a half-integer – contrary to the common definition, n does not have to be odd, i.e., the integers are a subset of the half-integers.

For example, the HalfInt type provided by this package can be used to represent numbers n/2 where n is an Int. Likewise, there exist half-integer types for all of Julia’s signed and unsigned integer types, e.g., HalfInt8, HalfUInt128, and BigHalfInt for arbitrarily large half-integers. All half-integer types are subtypes of the abstract type HalfInteger.

Installation

HalfIntegers.jl is compatible with Julia ≥ 1.0. It can be installed by typing

] add HalfIntegers

in the Julia REPL or via

using Pkg; Pkg.add("HalfIntegers")

Basic usage

HalfIntegers can be created from any other number type by using constructors or convert:

julia> HalfInt(-2.5)
-5/2

julia> convert(HalfUInt16, 7//2)
7/2

julia> BigHalfInt(2)
2

Another way of creating an HalfInteger is the half function:

julia> half(11)
11/2

julia> half(HalfInt8, -3)
-3/2

HalfInteger types support all standard arithmetic operations. Furthermore, this package defines the function twice. For any number x, the function twice returns the number 2x. For HalfInteger types, it returns an Integer type.

julia> twice(1.5)
3.0

julia> twice(HalfInt64(1.5))
3

julia> typeof(ans)
Int64

The Half{T<:Integer} type

All concrete half-integer types provided by this package are actually just aliases for Half{T} with a specific T:

julia> typeof(HalfInt64(1/2))
Half{Int64}

The type Half{T} accepts arbitrary <:Integer types as parameter. It can be used to define half-integers based on other (non-standard) integers. For example, since HalfInt etc. are based on standard integer arithmetic, they are subject to integer overflow. If you prefer checked arithmetic, you can use the SaferIntegers package and use Half{SafeInt} instead of HalfInt.

Alternatives to this package

  • FixedPointNumbers.jl implements numbers with a fixed number of fraction bits. Thus, the Fixed{Int,1} type from FixedPointNumbers can be considered equivalent to the HalfInt type from this package. However, the types behave differently in some ways. For example, multiplying two Fixed{T,f} numbers results in another Fixed{T,f} number, whereas multiplying two HalfIntegers results in a floating-point number.

halfintegers.jl's People

Contributors

sostock avatar jishnub avatar hyrodium avatar jutho avatar

Stargazers

Weishi Wang avatar Páll Haraldsson avatar Ujjwal Panda avatar John Lapeyre avatar  avatar STYLIANOS IORDANIS avatar ebigram avatar ultimatile avatar Jan Vandepitte avatar Ejaaz Merali avatar Xiu-zhe (Roger) Luo avatar  avatar  avatar  avatar

Watchers

Morten Piibeleht avatar  avatar

halfintegers.jl's Issues

Hashing on Julia 1.10

Due to a change in hash behavior in Julia 1.10, some things do not work anymore:

Hashing: Test Failed at /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1496
  Expression: #= /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1496 =# @inferred(hash(HalfInt128(5 / 2))) === hash(5 // 2)
   Evaluated: 0xb795033f6f2a0674 === 0x16b7f40fdbb2c57d

Hashing: Test Failed at /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1496
  Expression: #= /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1496 =# @inferred(hash(HalfUInt128(5 / 2))) === hash(5 // 2)
   Evaluated: 0xb795033f6f2a0674 === 0x16b7f40fdbb2c57d

Hashing: Test Failed at /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1496
  Expression: #= /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1496 =# @inferred(hash(BigHalfInt(5 / 2))) === hash(5 // 2)
   Evaluated: 0xb795033f6f2a0674 === 0x16b7f40fdbb2c57d

Hashing: Test Failed at /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1499
  Expression: #= /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1499 =# @inferred(hash(half(typemax(Int64)))) === hash(typemax(Int64) // 2)
   Evaluated: 0x720825ff5d41ca6f === 0x0fc531c308ea6ce8

Hashing: Test Failed at /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1499
  Expression: #= /home/sebastian/Development/HalfIntegers.jl/test/runtests.jl:1499 =# @inferred(hash(half(typemax(UInt64)))) === hash(typemax(UInt64) // 2)
   Evaluated: 0x30d54b97d39feaa7 === 0xf0300825c92981a4

Edit: Some of the failures are due to a bugs in the new implementation, some are due to an intended change in behavior.

Extension of gcd and lcm to HalfIntegers

After all, gcd and lcm are well-defined for rational numbers

Hmm.. it is not entirely clear what the definition is actually. LCM and GCD both reduce down to divisibility, and for me, the way to generalize is, following Wikipedia, via integral domains.

Based on that, currently from the code lcm(3/2, 2) = 6, but AFAICT it should be 3, since 3/2 | 3 (3/2 * 2 = 3) and 2 | 3 (2 * 3/2 = 3).

As for rationals, all rational numbers are common multiples of any pair of rational numbers, since every rational (other than 0) divides every other rational? There is a related issue: JuliaLang/julia#27039

Originally posted by @mortenpi in #2 (comment)

Add tests for Half{<:SafeInteger}

The README states that using Half{SafeInt} will protect against overflows/underflows. Therefore, I think, we should check (as part of the test suite) that it actually does, even though I don’t see how it couldn’t.

Performance of hashing

FWIW, there does seem to be a small benefit to the custom hash function:

julia> using WignerSymbols, HalfIntegers, BenchmarkTools

julia> x = rand(-1000:1000, 10000);

julia> xfloat = x/2;

julia> xrat = x.//2;

julia> xhalf = half.(x);

julia> xwig = WignerSymbols.HalfInteger.(x, 2);

julia> y = hash.(x);

julia> @benchmark map!(hash, $y, $x)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     26.254 μs (0.00% GC)
  median time:      27.258 μs (0.00% GC)
  mean time:        28.118 μs (0.00% GC)
  maximum time:     142.009 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> @benchmark map!(hash, $y, $xfloat)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     36.866 μs (0.00% GC)
  median time:      38.995 μs (0.00% GC)
  mean time:        39.603 μs (0.00% GC)
  maximum time:     132.436 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> @benchmark map!(hash, $y, $xrat)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     96.417 μs (0.00% GC)
  median time:      103.466 μs (0.00% GC)
  mean time:        106.371 μs (0.00% GC)
  maximum time:     300.126 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> @benchmark map!(hash, $y, $xhalf)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     175.342 μs (0.00% GC)
  median time:      177.345 μs (0.00% GC)
  mean time:        184.564 μs (0.00% GC)
  maximum time:     602.389 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> @benchmark map!(hash, $y, $xwig)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     93.089 μs (0.00% GC)
  median time:      94.630 μs (0.00% GC)
  mean time:        98.162 μs (0.00% GC)
  maximum time:     249.477 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

So the custom hash performs as the one for rational, which however is still much slower than the one for Float64. The far better solution thus seems to be to just convert to Float64:

julia> @benchmark map!(z->hash(float(z)), $y, $xhalf)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     43.134 μs (0.00% GC)
  median time:      44.856 μs (0.00% GC)
  mean time:        46.925 μs (0.00% GC)
  maximum time:     163.442 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

Originally posted by @Jutho in #2 (comment)

A few comments on the code

Sorry for the delay, but it also looks great to me! I really like the half function. In WignerSymols you have to do HalfInteger(1, 2), but that gets old quickly. half seems like a perfect compromise between brevity and clarity.

I looked through the code and here a few observations / questions:

  • I am wondering whether it is wise to allow the arithmetic to leave the half-integer space (I'm thinking about multiplication and division between HalfIntegers). I think it would be better to error in those situations, to make sure that you don't accidentally write code that should do everything with Halfs, but ends up using floats in the intermediate calculations.

  • I find defining lcm and gcd for HalfIntegers a bit strange. Do you actually have a use case for these definitions?

  • Base.decompose appears to be unexported and undocumented, so I am not sure if it is a good idea to rely on it. FWIW, I don't actually understand what it's even doing.

  • I see you have some doctests. Do you think it would be worth setting up Documenter?

  • I don't think the @evals are necessary in the tests?

The only thing I see that we have in WignerSymbols that we don't have here is an implementation of Base.hash. @Jutho, would it be worth bringing it over?

We also define one and zero there, but those are not really necessary -- the general method from Base which falls back to the constructor works nicely.

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Should there be an `OddHalfInt` type?

Such that +(::OddHalfInt, ::OddHalfInt) and -(::OddHalfInt, ::OddHalfInt) would lead to an Int result in either case. Implementation wise, these may wrap HalfInt numbers with an additional check upon construction.

Multiplication and division

I don’t think it would be wise to make standard arithmetic operations error on HalfIntegers. After all, you probably do want to use them in calculations, and division of two Ints does also not throw an error.

That's a good point, I forgot that there is a precedent for this with integer division. With that in mind, I think it's fine if the division of Halfs is consistent with that and also results in a float.

But it still feels weird that multiplication would leave the space. And it does that only occasionally (i.e. not if one factor is an integer). So it means that you really have to mentally keep track of types in your expressions.

Rational would be slightly better since you would not lose precision at least, so it might be a good compromise. I still think an argument can be made that if you do arithmetic with half-integers, you don't want to end up with floating point numbers accidentally.

Originally posted by @mortenpi in #2 (comment)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.