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

Improve readability of Float::Printer::* docs #5438

Merged
merged 1 commit into from
Sep 25, 2018
Merged
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
6 changes: 4 additions & 2 deletions src/float/printer.cr
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
require "./printer/*"

# Float::Printer is based on Grisu3 algorithm described in the 2004 paper
# :nodoc:
#
# `Float::Printer` is based on Grisu3 algorithm described in the 2004 paper
# "Printing Floating-Point Numbers Quickly and Accurately with Integers" by
# Florian Loitsch.
module Float::Printer
extend self
BUFFER_SIZE = 128

# Converts Float *v* to a string representation and prints it onto *io*
# Converts `Float` *v* to a string representation and prints it onto *io*.
#
# It is used by `Float64#to_s` and it is probably not necessary to use
# this directly.
Expand Down
26 changes: 15 additions & 11 deletions src/float/printer/diy_fp.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# DiyFP is ported from the C++ "double-conversions" library.
#
# The following is their license:
# Copyright 2010 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -30,15 +31,18 @@
require "./ieee"

# This "Do It Yourself Floating Point" struct implements a floating-point number
# with a `UIht64` significand and an `Int32` exponent. Normalized DiyFP numbers will
# with a `UInt64` significand and an `Int32` exponent. Normalized `DiyFP` numbers will
# have the most significant bit of the significand set.
# Multiplication and Subtraction do not normalize their results.
# DiyFP is not designed to contain special Floats (NaN and Infinity).
#
# NOTE: `DiyFP` is not designed to contain special Floats (*NaN* and *Infinity*).
struct Float::Printer::DiyFP
SIGNIFICAND_SIZE = 64
# Also known as the significand

# Also known as the significand.
property frac : UInt64
# exponent

# Exponent.
property exp : Int32

def initialize(@frac, @exp)
Expand All @@ -52,26 +56,26 @@ struct Float::Printer::DiyFP
new frac.to_u64, exp
end

# Returns a new `DiyFP` caculated as self - *other*.
# Returns a new `DiyFP` caculated as `self - other`.
#
# The exponents of both numbers must be the same and the frac of self must be
# greater than the other.
# The exponents of both numbers must be the same and the `frac` of `self`
# must be greater than the *other*.
#
# This result is not normalized.
# NOTE: This result is not normalized.
def -(other : DiyFP)
self.class.new(frac - other.frac, exp)
end

MASK32 = 0xFFFFFFFF_u32

# Returns a new `DiyFP` caculated as self * *other*.
# Returns a new `DiyFP` caculated as `self * other`.
#
# Simply "emulates" a 128 bit multiplication.
# However: the resulting number only contains 64 bits. The least
# significant 64 bits are only used for rounding the most significant 64
# bits.
#
# This result is not normalized.
# NOTE: This result is not normalized.
def *(other : DiyFP)
a = frac >> 32
b = frac & MASK32
Expand Down Expand Up @@ -119,7 +123,7 @@ struct Float::Printer::DiyFP
new(frac, exp)
end

# Normalize such that the most signficiant bit of frac is set
# Normalize such that the most signficiant bit of `frac` is set.
def self.from_f_normalized(v : Float64 | Float32)
pre_normalized = from_f(v)
f = pre_normalized.frac
Expand Down
111 changes: 63 additions & 48 deletions src/float/printer/grisu3.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Grisu3 is ported from the C++ "double-conversions" library.
#
# The following is their license:
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -39,18 +40,21 @@ module Float::Printer::Grisu3
# outside the safe interval, or if we cannot prove that it is closer to the
# input than a neighboring representation of the same length.
#
# Input: * buffer pointer containing the digits of too_high / 10^kappa
# * the buffer's length
# * distance_too_high_w == (too_high - w).frac * unit
# * unsafe_interval == (too_high - too_low).frac * unit
# * rest = (too_high - buffer * 10^kappa).frac * unit
# * ten_kappa = 10^kappa * unit
# * unit = the common multiplier
# Output: returns true if the buffer is guaranteed to contain the closest
# representable number to the input.
# Modifies the generated digits in the buffer to approach (round towards) w.
def round_weed(buffer_p, length, distance_too_high_w, unsafe_interval, rest, ten_kappa, unit)
buffer = buffer_p.to_slice(128)
# Input:
# * *buffer_ptr*: buffer pointer containing the digits of `too_high / 10^kappa`
# * *length*: the buffer's length
# * *distance_too_high_w*: `(too_high - w).frac * unit`
# * *unsafe_interval*: `(too_high - too_low).frac * unit`
# * *rest*: `(too_high - buffer * 10^kappa).frac * unit`
# * *ten_kappa*: `10^kappa * unit`
# * *unit*: the common multiplier
#
# Output: returns `true` if the buffer is guaranteed to contain the closest
# representable number to the input.
#
# Modifies the generated digits in the buffer to approach (round towards) *w*.
def round_weed(buffer_ptr, length, distance_too_high_w, unsafe_interval, rest, ten_kappa, unit)
buffer = buffer_ptr.to_slice(128)
small_distance = distance_too_high_w - unit
big_distance = distance_too_high_w + unit

Expand Down Expand Up @@ -154,43 +158,51 @@ module Float::Printer::Grisu3
return (2 * unit <= rest) && (rest <= unsafe_interval - 4 * unit)
end

# Generates the digits of input number w.
# w is a floating-point number (DiyFp), consisting of a significand and an
# exponent. Its exponent is bounded by kMinimalTargetExponent and
# kMaximalTargetExponent.
# Hence -60 <= w.e() <= -32.
# Generates the digits of input number *w*.
#
# Returns false if it fails, in which case the generated digits in the buffer
# *w* is a floating-point number (`DiyFp`), consisting of a significand and an
# exponent. Its exponent is bounded by `kMinimalTargetExponent` and
# `kMaximalTargetExponent`. Hence:
# -60 <= w.e() <= -32
#
# Returns `false` if it fails, in which case the generated digits in the buffer
# should not be used.
#
# Preconditions:
# * low, w and high are correct up to 1 ulp (unit in the last place). That
# is, their error must be less than a unit of their last digits.
# * low.e() == w.e() == high.e()
# * low < w < high, and taking into account their error: low~ <= high~
# * kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
# Postconditions: returns false if procedure fails.
# otherwise:
# * buffer is not null-terminated, but len contains the number of digits.
# * buffer contains the shortest possible decimal digit-sequence
# such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the
# correct values of low and high (without their error).
# * if more than one decimal representation gives the minimal number of
# decimal digits then the one closest to W (where W is the correct value
# of w) is chosen.
# Remark: this procedure takes into account the imprecision of its input
# * *low*, *w* and *high* are correct up to 1 ulp (unit in the last place).
# That is, their error must be less than a unit of their last digits.
# * `low.e() == w.e() == high.e()`
# * `low < w < high`, and taking into account their error: `low~ <= high~`
# * `kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent`
#
# Postconditions: returns `false` if procedure fails, otherwise:
# * buffer is not null-terminated, but len contains the number of digits.
# * buffer contains the shortest possible decimal digit-sequence
# such that `LOW < buffer * 10^kappa < HIGH`, where LOW and HIGH are the
# correct values of low and high (without their error).
# * if more than one decimal representation gives the minimal number of
# decimal digits then the one closest to W (where W is the correct value
# of w) is chosen.
#
# NOTE: This procedure takes into account the imprecision of its input
# numbers. If the precision is not enough to guarantee all the postconditions
# then false is returned. This usually happens rarely (~0.5%).
# then `false` is returned. This usually happens rarely (~0.5%).
#
# Say, for the sake of example, that
# w.e() == -48, and w.f() == 0x1234567890abcdef
# w's value can be computed by w.f() * 2^w.e()
# We can obtain w's integral digits by simply shifting w.f() by -w.e().
# -> w's integral part is 0x1234
# w's fractional part is therefore 0x567890abcdef.
# Printing w's integral part is easy (simply print 0x1234 in decimal).
# Say, for the sake of example, that:
# w.e() == -48 && w.f() == 0x1234567890abcdef
#
# w's value can be computed by `w.f() * 2^w.e()`
#
# We can obtain w's integral digits by simply shifting `w.f()` by `-w.e()`.
#
# * -> w's integral part is `0x1234`
# * w's fractional part is therefore `0x567890abcdef`.
#
# Printing w's integral part is easy (simply print `0x1234` in decimal).
# In order to print its fraction we repeatedly multiply the fraction by 10 and
# get each digit. Example the first digit after the point would be computed by
# (0x567890abcdef * 10) >> 48. -> 3
# (0x567890abcdef * 10) >> 48. -> 3
#
# The whole thing becomes slightly more complicated because we want to stop
# once we have enough digits. That is, once the digits inside the buffer
# represent 'w' we can stop. Everything inside the interval low - high
Expand Down Expand Up @@ -287,14 +299,17 @@ module Float::Printer::Grisu3
# Provides a decimal representation of *v*.
#
# Returns a `Tuple` of `{status, decimal_exponent, length}`
# *status* will be true if it succeeds, otherwise the result cannot be
# *status* will be `true` if it succeeds, otherwise the result cannot be
# trusted.
#
# There will be *length* digits inside the buffer (not null-terminated).
# If the function returns satatus as true true then
# v == (buffer * 10^decimal_exponent).to_f
# The digits in the buffer are the shortest representation possible: no
# 0.09999999999999999 instead of 0.1. The shorter representation will even be
# chosen even if the longer one would be closer to *v*.
# If the function returns status as `true` then
# v == (buffer * 10^decimal_exponent).to_f
#
# The digits in the buffer are the shortest representation possible:
# no `0.09999999999999999` instead of `0.1`. The shorter representation will
# even be chosen even if the longer one would be closer to *v*.
#
# The last digit will be closest to the actual *v*. That is, even if several
# digits might correctly yield *v* when read again, the closest will be
# computed.
Expand Down
32 changes: 18 additions & 14 deletions src/float/printer/ieee.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# IEEE is ported from the C++ "double-conversions" library.
#
# The following is their license:
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -30,20 +31,21 @@
module Float::Printer::IEEE
extend self

EXPONENT_MASK_64 = 0x7FF0000000000000_u64
SIGNIFICAND_MASK_64 = 0x000FFFFFFFFFFFFF_u64
HIDDEN_BIT_64 = 0x0010000000000000_u64
PHYSICAL_SIGNIFICAND_SIZE_64 = 52 # Excludes the hidden bit
SIGNIFICAND_SIZE_64 = 53
EXPONENT_MASK_64 = 0x7FF0000000000000_u64
SIGNIFICAND_MASK_64 = 0x000FFFFFFFFFFFFF_u64
HIDDEN_BIT_64 = 0x0010000000000000_u64
# Excludes the hidden bit
PHYSICAL_SIGNIFICAND_SIZE_64 = 52
SIGNIFICAND_SIZE_64 = 53
EXPONENT_BIAS_64 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE_64
DENORMAL_EXPONENT_64 = -EXPONENT_BIAS_64 + 1
SIGN_MASK_64 = 0x8000000000000000_u64

EXPONENT_MASK_32 = 0x7F800000_u32
SIGNIFICAND_MASK_32 = 0x007FFFFF_u32
HIDDEN_BIT_32 = 0x00800000_u32
PHYSICAL_SIGNIFICAND_SIZE_32 = 23 # Excludes the hidden bit
SIGNIFICAND_SIZE_32 = 24
EXPONENT_MASK_32 = 0x7F800000_u32
SIGNIFICAND_MASK_32 = 0x007FFFFF_u32
HIDDEN_BIT_32 = 0x00800000_u32
# Excludes the hidden bit
PHYSICAL_SIGNIFICAND_SIZE_32 = 23
SIGNIFICAND_SIZE_32 = 24
EXPONENT_BIAS_32 = 0x7F + PHYSICAL_SIGNIFICAND_SIZE_32
DENORMAL_EXPONENT_32 = -EXPONENT_BIAS_32 + 1
SIGN_MASK_32 = 0x80000000_u32
Expand Down Expand Up @@ -89,9 +91,11 @@ module Float::Printer::IEEE
end

# Computes the two boundaries of *v*.
# The bigger boundary (m_plus) is normalized. The lower boundary has the same
# exponent as m_plus.
# Precondition: the value encoded by this Flaot must be greater than 0.
#
# The bigger boundary (*m_plus*) is normalized. The lower boundary has the same
# exponent as *m_plus*.
#
# Precondition: the value encoded by this `Float` must be greater than 0.
def normalized_boundaries(v : Float64)
w = DiyFP.from_f(v)
m_plus = DiyFP.new((w.frac << 1) + 1, w.exp - 1).normalize
Expand Down