Last active
January 26, 2017 17:08
-
-
Save talentdeficit/a2af5786f551d98e0f2e2daa580eb0f5 to your computer and use it in GitHub Desktop.
optional Decimal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Postgrex.Extensions.Numeric do | |
@moduledoc false | |
import Postgrex.BinaryUtils, warn: false | |
use Postgrex.BinaryExtension, send: "numeric_send" | |
def init(opts) do | |
Keyword.get(opts, :numeric, Postgrex.Extensions.Numeric.Decimal) | |
end | |
def encode(handler) do | |
quote location: :keep do | |
numeric -> | |
data = unquote(handler).encode!(numeric) | |
[<<IO.iodata_length(data)::int32>> | data] | |
end | |
end | |
def decode(handler) do | |
quote location: :keep do | |
<<len :: int32, data :: binary-size(len)>> -> | |
unquote(handler).decode!(data) | |
end | |
end | |
end | |
defmodule Postgrex.Extensions.Numeric.Decimal do | |
@moduledoc false | |
import Postgrex.BinaryUtils, warn: false | |
def encode!(%Decimal{} = decimal) do | |
encode_numeric(decimal) | |
end | |
def encode!(n) when is_number(n) do | |
encode_numeric(Decimal.new(n)) | |
end | |
def decode!(data) do | |
decode_numeric(data) | |
end | |
## Helpers | |
def encode_numeric(%Decimal{coef: coef}) when coef in [:qNaN, :sNaN] do | |
<<0 :: int16, 0 :: int16, 0xC000 :: uint16, 0 :: int16>> | |
end | |
def encode_numeric(%Decimal{sign: sign, coef: coef, exp: exp}) do | |
sign = encode_sign(sign) | |
scale = -exp | |
{integer, float, scale} = split_parts(coef, scale) | |
integer_digits = encode_digits(integer, []) | |
float_digits = encode_float(float, scale) | |
digits = integer_digits ++ float_digits | |
num_digits = length(digits) | |
weight = max(length(integer_digits) - 1, 0) | |
bin = for digit <- digits, into: "", do: <<digit :: uint16>> | |
[<<num_digits :: int16, weight :: int16, sign :: uint16, scale :: int16>> | bin] | |
end | |
defp encode_sign(1), do: 0x0000 | |
defp encode_sign(-1), do: 0x4000 | |
defp split_parts(coef, scale) when scale >= 0 do | |
integer_base = pow10(scale) | |
{div(coef, integer_base), rem(coef, integer_base), scale} | |
end | |
defp split_parts(coef, scale) when scale < 0 do | |
integer_base = pow10(-scale) | |
{coef * integer_base, 0, 0} | |
end | |
defp encode_float(float, scale) do | |
{float_prefix, float_suffix} = float_padding(float, scale) | |
leading_zeros = List.duplicate(0, float_prefix) | |
float = float * pow10(float_suffix) | |
leading_zeros ++ encode_digits(float, []) | |
end | |
defp float_padding(num, scale) when num < 10000 and scale > 0 do | |
prefix = div(scale, 4) | |
suffix = rem(scale, 4) | |
if suffix == 0 do | |
{prefix - 1, 0} | |
else | |
{prefix, 4 - suffix} | |
end | |
end | |
defp float_padding(num, _scale) when num < 10, do: {0, 3} | |
defp float_padding(num, _scale) when num < 100, do: {0, 2} | |
defp float_padding(num, _scale) when num < 1000, do: {0, 1} | |
defp float_padding(num, _scale) when num < 10000, do: {0, 0} | |
defp float_padding(num, scale), do: float_padding(div(num, 10000), scale - 4) | |
defp encode_digits(coef, digits) when coef < 10000 do | |
[coef|digits] | |
end | |
defp encode_digits(coef, digits) do | |
digit = rem(coef, 10000) | |
coef = div(coef, 10000) | |
encode_digits(coef, [digit|digits]) | |
end | |
def decode_numeric(<<ndigits :: int16, weight :: int16, sign :: uint16, scale :: int16, tail :: binary>>) do | |
decode_numeric(ndigits, weight, sign, scale, tail) | |
end | |
defp decode_numeric(0, _weight, 0xC000, _scale, "") do | |
Decimal.new(1, :qNaN, 0) | |
end | |
defp decode_numeric(_num_digits, weight, sign, scale, bin) do | |
{value, weight} = decode_numeric_int(bin, weight, 0) | |
sign = decode_sign(sign) | |
coef = scale(value, (weight + 1) * 4 + scale) | |
Decimal.new(sign, coef, -scale) | |
end | |
defp decode_sign(0x0000), do: 1 | |
defp decode_sign(0x4000), do: -1 | |
defp scale(coef, 0), do: coef | |
defp scale(coef, diff) when diff < 0, do: div(coef, pow10(-diff)) | |
defp scale(coef, diff) when diff > 0, do: coef * pow10(diff) | |
Enum.reduce 0..100, 1, fn x, acc -> | |
defp pow10(unquote(x)), do: unquote(acc) | |
acc * 10 | |
end | |
defp pow10(num) when num > 100, do: pow10(100) * pow10(num-100) | |
defp decode_numeric_int("", weight, acc), do: {acc, weight} | |
defp decode_numeric_int(<<digit :: int16, tail :: binary>>, weight, acc) do | |
acc = (acc * 10000) + digit | |
decode_numeric_int(tail, weight - 1, acc) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment