【问题标题】:What's the best way to represent System.Double as a sortable string?将 System.Double 表示为可排序字符串的最佳方法是什么?
【发布时间】:2011-10-12 00:51:14
【问题描述】:

在所有基础类型都是字符串的数据格式中,数字类型必须转换为可以按字母顺序进行比较的标准化字符串格式。例如,如果没有负数,则值 27short 可以表示为 00027

double 表示为字符串的最佳方式是什么?在我的情况下,我可以忽略否定,但我很好奇在任何一种情况下你会如何表示双重。

更新

根据 Jon Skeet 的建议,我现在正在使用它,虽然我不能 100% 确定它会正常工作:

static readonly string UlongFormatString = new string('0', ulong.MaxValue.ToString().Length);

public static string ToSortableString(this double n)
{
    return BitConverter.ToUInt64(BitConverter.GetBytes(BitConverter.DoubleToInt64Bits(n)), 0).ToString(UlongFormatString);
}

public static double DoubleFromSortableString(this string n)
{
    return BitConverter.Int64BitsToDouble(BitConverter.ToInt64(BitConverter.GetBytes(ulong.Parse(n)), 0));
}

更新 2

我已经确认了 Jon 的怀疑 - 使用此方法无法使用底片。下面是一些示例代码:

void Main()
{
    var a = double.MaxValue;
    var b = double.MaxValue/2;
    var c = 0d;
    var d = double.MinValue/2;
    var e = double.MinValue;
    Console.WriteLine(a.ToSortableString());
    Console.WriteLine(b.ToSortableString());
    Console.WriteLine(c.ToSortableString());
    Console.WriteLine(d.ToSortableString());
    Console.WriteLine(e.ToSortableString());
}

static class Test
{
    static readonly string UlongFormatString = new string('0', ulong.MaxValue.ToString().Length);
    public static string ToSortableString(this double n)
    {
        return BitConverter.ToUInt64(BitConverter.GetBytes(BitConverter.DoubleToInt64Bits(n)), 0).ToString(UlongFormatString);
    }
}

产生以下输出:

09218868437227405311
09214364837600034815
00000000000000000000
18437736874454810623
18442240474082181119

显然没有按预期排序。

更新 3

下面接受的答案是正确的。谢谢大家!

【问题讨论】:

  • 为什么要对字符串(=表示)而不是实际值进行排序?这几乎总是一个坏主意。
  • @Konrad 我在问题中解释了这一点 - “所有基础类型都是字符串”。具体来说,我使用的是 Lucene,但还有其他格式也需要按字母排序。

标签: c# .net sorting double


【解决方案1】:

考虑到巨大的范围(double.MaxValue 是 1.7976931348623157E+308),对于双打来说,填充可能相当尴尬。

字符串表示是否仍然必须是人类可读的,或者只是可逆的?

这提供了一个可逆的转换,导致一个相当短的字符串表示保留字典顺序 - 但它根本不明显 double 值来自字符串。

编辑:不要单独使用BitConverter.DoubleToInt64Bits。这颠倒了负值的顺序。

我确定您可以使用DoubleToInt64Bits 执行此转换,然后进行一些小操作,但不幸的是我无法使其工作现在,我有三个急于去公园的孩子......


为了使一切正确排序,负数需要以反码格式而不是符号大小存储(否则负数和正数以相反的顺序排序),并且需要翻转符号位(以进行负数排序小于正数)。这段代码应该可以解决问题:

static ulong EncodeDouble(double d)
{
    long ieee = System.BitConverter.DoubleToInt64Bits(d);
    ulong widezero = 0;
    return ((ieee < 0)? widezero: ((~widezero) >> 1)) ^ (ulong)~ieee;
}

static double DecodeDouble(ulong lex)
{
    ulong widezero = 0;
    long ieee = (long)(((0 <= (long)lex)? widezero: ((~widezero) >> 1)) ^ ~lex);
    return System.BitConverter.Int64BitsToDouble(ieee);
}

在这里演示:http://ideone.com/JPNPY

以下是字符串之间的完整解决方案:

static string EncodeDouble(double d)
{
    long ieee = System.BitConverter.DoubleToInt64Bits(d);
    ulong widezero = 0;
    ulong lex = ((ieee < 0)? widezero: ((~widezero) >> 1)) ^ (ulong)~ieee;
    return lex.ToString("X16");
}

static double DecodeDouble(string s)
{
    ulong lex = ulong.Parse(s, System.Globalization.NumberStyles.AllowHexSpecifier);
    ulong widezero = 0;
    long ieee = (long)(((0 <= (long)lex)? widezero: ((~widezero) >> 1)) ^ ~lex);
    return System.BitConverter.Int64BitsToDouble(ieee);
}

演示:http://ideone.com/pFciY

【讨论】:

  • 我知道问题说不处理否定是可以的,但无论如何,对于下一个出现的读者来说,在你的回答中值得一提。当然可以将人类可读的表示附加到原始位的 Base64(或类似)编码。
  • @Ben:我认为它们并没有受到真正的影响——如果你有一个处理负 Int64 值的字符串格式,它也适用于负双精度。
  • @Ben:为什么?应该没问题,只要您的 Int64 格式已经是可排序的。换句话说,它减少了按字典顺序格式化所有 Int64 值的问题,包括负数。
  • @Jon,这是一个很好的答案 - 老实说,我不太确定表示负数的最佳方法是考虑到正负极端在中间趋向于零。我想我可以转换为 ulong 并将值增加 long.MaxValue?
  • @Nathan:你实际上想减去long.MinValue(这样就变成了0)。这绝对是一个选择 - 再次,如果您对表示不是真正人类可读的表示感到满意:)
【解决方案2】:

我相信修改后的科学记数法,以指数为先,并使用下划线表示正数,将按照与数字相同的顺序进行词汇排序。

如果你愿意,你甚至可以附加正常的表示,因为后缀不会影响排序。

例子

E000M3    +3.0
E001M2.7  +27.0

很遗憾,它不适用于负数或负指数。您可以为指数引入偏差,就像 IEEE 格式在内部使用一样。

【讨论】:

    【解决方案3】:

    事实证明... org.apache.solr.util 包包含 NumberUtils 类。此类具有静态方法,可以完成将双精度数(和其他数据值)转换为可排序字符串(并返回)所需的一切。这些方法使用起来再简单不过了。几点说明:

    1. 当然,NumberUtils 是用 Java(不是 c#)编写的。我猜代码可以转换为c#...但是,我并不精通c#。源代码可在线获取。
    2. 生成的字符串(根本)不可打印。
    3. 代码中的 cmets 表明所有特殊情况(包括负数和无穷大)都应该正常工作。
    4. 我没有做过任何基准测试...但是,基于对代码的快速扫描,它应该非常快。

    下面的代码显示了使用这个库需要做什么。

    String key = NumberUtils.double2sortableStr(35.2);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-07
      • 2011-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多