Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Low-level Constructors #14

Closed
quinnj opened this issue Jun 11, 2015 · 10 comments
Closed

Low-level Constructors #14

quinnj opened this issue Jun 11, 2015 · 10 comments

Comments

@quinnj
Copy link
Collaborator

quinnj commented Jun 11, 2015

@stevengj, what are the options for low level construction of a Dec64 type? I poked around the source code, but it seems like pretty much everything funnels through parse. I ask because I'm looking at ODBC interop, where the ODBC SQL type is defined as

const SQL_MAX_NUMERIC_LEN = 16
immutable SQLNumeric
    precision::SQLCHAR
    scale::SQLSCHAR
    sign::SQLCHAR
    val::NTuple{SQL_MAX_NUMERIC_LEN,SQLCHAR}
end

which the ODBC driver manager would fill in the bits, and then ODBC.jl defines the appropriate conversion routine from SQLNumeric to Dec64. Any thoughts or perhaps I need to dig into the Intel header files a little.

@stevengj
Copy link
Member

I think you'd have to poke around in the Intel libraries (although I'd like to investigate whether we should switch to the IBM library).

@stevengj
Copy link
Member

stevengj commented Mar 30, 2017

I don't see any exported functions, but there are some undocumented internal functions that may do what you want, e.g. (from bid_internal.h):

//   No overflow/underflow checking                                             
//                                                                              
__BID_INLINE__ BID_UINT64
fast_get_BID64 (BID_UINT64 sgn, int expon, BID_UINT64 coeff) {
  BID_UINT64 r, mask;

  mask = 1;
  mask <<= EXPONENT_SHIFT_SMALL64;

  // check whether coefficient fits in 10*5+3 bits                              
  if (coeff < mask) {
    r = expon;
    r <<= EXPONENT_SHIFT_SMALL64;
    r |= (coeff | sgn);
    return r;
  }
  // special format                                                             

  // eliminate the case coeff==10^16 after rounding                             
  if (coeff == 10000000000000000ull) {
    r = expon + 1;
    r <<= EXPONENT_SHIFT_SMALL64;
    r |= (1000000000000000ull | sgn);
    return r;
  }

  r = expon;
  r <<= EXPONENT_SHIFT_LARGE64;
  r |= (sgn | SPECIAL_ENCODING_MASK64);
  // add coeff, without leading bits                                            
  mask = (mask >> 2) - 1;
  coeff &= mask;
  r |= coeff;

  return r;
}

This is inlined, and so it can't be called from the C shared library, but it looks easy enough to rewrite in Julia.

@jmkuhn
Copy link
Contributor

jmkuhn commented May 14, 2020

@quinnj - Assuming this constructor is still useful for you, please look over the following proposed API and let me know if it does what you need and if you would suggest any changes.

help?> Dec64
search: Dec64

  Dec64(x::Union{Real, AbstractString} [, mode::RoundingMode])
  Dec64(sign::Int, exponent::Int, significand::UInt64)

  ⋮

  Dec64(sign, exponent, significand) returns sign * significand * 10^exponent.
  sign must be -1, 0, or +1 with 0 only allowed if significand is also 0.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  ⋮
  
  julia> Dec64(-1, -4, UInt64(71875))
  -7.1875

The 3 argument version is designed to be efficient so it usually doesn't require any multiplication, division, or exponentiation. It will use division if the supplied significand >= maxintfloat(Dec64) which is 1e16. It will use multiplication if the supplied exponent < -398. This version will not silently overflow to infinity, underflow to zero, or round an inexact. If it can't exactly represent the value you supply, it will throw an error. The current version doesn't have a way to create Inf or NaN.

Some more examples:

julia> Dec64(0, 0, UInt64(0))
0.0

julia> Dec64(1, 10, UInt64(0))
0.0

julia> Dec64(-1, 0, UInt64(0))
-0.0

julia> Dec64(-1, -2, UInt64(500))
-5.0

julia> Dec64(1, 2, UInt64(5))
500.0

julia> Dec64(1, 0, UInt64(maxintfloat(Dec64) - 1))
9.999999999999999e15

julia> Dec64(1, 369, UInt64(maxintfloat(Dec64) - 1))
9.999999999999999e384

julia> Dec64(1, 369, UInt64(maxintfloat(Dec64)))
ERROR: OverflowError: overflow error
Stacktrace:
 [1] Dec64(::Int64, ::Int64, ::UInt64) at /Users/john.m.kuhn/julia_dev/DecFP_llconst/dev/DecFP/src/DecFP.jl:630
 [2] top-level scope at REPL[11]:1

julia> Dec64(1, 0, UInt64(maxintfloat(Dec64)) + 1)
ERROR: InexactError: Dec64(10000000000000001)
Stacktrace:
 [1] Dec64(::Int64, ::Int64, ::UInt64) at /Users/john.m.kuhn/julia_dev/DecFP_llconst/dev/DecFP/src/DecFP.jl:626
 [2] top-level scope at REPL[12]:1

@stevengj
Copy link
Member

stevengj commented May 15, 2020

Maybe define DecXX([sign::Integer], significand::Integer, exponent::Integer) and take the sign from the significand argument if sign is not present.

@quinnj
Copy link
Collaborator Author

quinnj commented May 15, 2020

This looks great! Yeah, I'm actually rewriting ODBC.jl now and still doing conversions to/from strings for Dec64 and SQL_NUMERIC types. It'd be great to be able to work w/ the scaled integers directly and this API looks great.

As ODBC.jl also allows sending data to the database, it would also be nice to have the inverse of this functionality: given a DecX, what is the scaled integer + exponent? Is that already available somehow?

@jmkuhn
Copy link
Contributor

jmkuhn commented May 15, 2020

There is an unexported function that returns integer + exponent. The current version ignores sign. I can add sign and export this.

julia> DecFP.sigexp(Dec64(-30))
(30, 0)

julia> DecFP.sigexp(d64"123.456")
(123456, -3)

This function is available in the current master, but not yet in a released version.

@jmkuhn
Copy link
Contributor

jmkuhn commented May 15, 2020

With this new constructor and a public dumper, we will need to handle Inf and NaN. One possibility is to extend the sign parameter with -1, 0, +1 indicating sign, ±2 => ±Inf and 3 => NaN. I'm open to other suggestions.

@stevengj
Copy link
Member

Why would you need Inf and NaN, since we already have compile-time constants for those?

@jmkuhn
Copy link
Contributor

jmkuhn commented May 18, 2020

My original thinking was that if we have a pair of functions, sigexp() that takes a DecimalFloatingPoint and returns a tuple of sign, significand, and exponent, and Decxx() that take sign, significand, and exponent returning a DecimalFloatingPoint it would be good to have them round-trippable.

x = Decxx(some_value)
x1 = Decxx(sigexp(x)...)
isequal(x, x1)

But, we don't need the constructor to be able to handle NaN or Inf and the interface is cleaner if we don't.

sigexp() will need to handle NaN and Inf and I will open a new issue to discuss that API.

@jmkuhn
Copy link
Contributor

jmkuhn commented May 25, 2020

Closed by #129.

@jmkuhn jmkuhn closed this as completed May 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants